michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: this.EXPORTED_SYMBOLS = ["PlacesUIUtils"]; michael@0: michael@0: var Ci = Components.interfaces; michael@0: var Cc = Components.classes; michael@0: var Cr = Components.results; michael@0: var Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", michael@0: "resource://gre/modules/PluralForm.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", michael@0: "resource://gre/modules/PrivateBrowsingUtils.jsm"); michael@0: michael@0: #ifdef MOZ_SERVICES_SYNC michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Weave", michael@0: "resource://services-sync/main.js"); michael@0: #endif michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() { michael@0: Cu.import("resource://gre/modules/PlacesUtils.jsm"); michael@0: return PlacesUtils; michael@0: }); michael@0: michael@0: this.PlacesUIUtils = { michael@0: ORGANIZER_LEFTPANE_VERSION: 7, michael@0: ORGANIZER_FOLDER_ANNO: "PlacesOrganizer/OrganizerFolder", michael@0: ORGANIZER_QUERY_ANNO: "PlacesOrganizer/OrganizerQuery", michael@0: michael@0: LOAD_IN_SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar", michael@0: DESCRIPTION_ANNO: "bookmarkProperties/description", michael@0: michael@0: TYPE_TAB_DROP: "application/x-moz-tabbrowser-tab", michael@0: michael@0: /** michael@0: * Makes a URI from a spec, and do fixup michael@0: * @param aSpec michael@0: * The string spec of the URI michael@0: * @returns A URI object for the spec. michael@0: */ michael@0: createFixedURI: function PUIU_createFixedURI(aSpec) { michael@0: return URIFixup.createFixupURI(aSpec, Ci.nsIURIFixup.FIXUP_FLAG_NONE); michael@0: }, michael@0: michael@0: getFormattedString: function PUIU_getFormattedString(key, params) { michael@0: return bundle.formatStringFromName(key, params, params.length); michael@0: }, michael@0: michael@0: /** michael@0: * Get a localized plural string for the specified key name and numeric value michael@0: * substituting parameters. michael@0: * michael@0: * @param aKey michael@0: * String, key for looking up the localized string in the bundle michael@0: * @param aNumber michael@0: * Number based on which the final localized form is looked up michael@0: * @param aParams michael@0: * Array whose items will substitute #1, #2,... #n parameters michael@0: * in the string. michael@0: * michael@0: * @see https://developer.mozilla.org/en/Localization_and_Plurals michael@0: * @return The localized plural string. michael@0: */ michael@0: getPluralString: function PUIU_getPluralString(aKey, aNumber, aParams) { michael@0: let str = PluralForm.get(aNumber, bundle.GetStringFromName(aKey)); michael@0: michael@0: // Replace #1 with aParams[0], #2 with aParams[1], and so on. michael@0: return str.replace(/\#(\d+)/g, function (matchedId, matchedNumber) { michael@0: let param = aParams[parseInt(matchedNumber, 10) - 1]; michael@0: return param !== undefined ? param : matchedId; michael@0: }); michael@0: }, michael@0: michael@0: getString: function PUIU_getString(key) { michael@0: return bundle.GetStringFromName(key); michael@0: }, michael@0: michael@0: get _copyableAnnotations() [ michael@0: this.DESCRIPTION_ANNO, michael@0: this.LOAD_IN_SIDEBAR_ANNO, michael@0: PlacesUtils.POST_DATA_ANNO, michael@0: PlacesUtils.READ_ONLY_ANNO, michael@0: ], michael@0: michael@0: /** michael@0: * Get a transaction for copying a uri item (either a bookmark or a history michael@0: * entry) from one container to another. michael@0: * michael@0: * @param aData michael@0: * JSON object of dropped or pasted item properties michael@0: * @param aContainer michael@0: * The container being copied into michael@0: * @param aIndex michael@0: * The index within the container the item is copied to michael@0: * @return A nsITransaction object that performs the copy. michael@0: * michael@0: * @note Since a copy creates a completely new item, only some internal michael@0: * annotations are synced from the old one. michael@0: * @see this._copyableAnnotations for the list of copyable annotations. michael@0: */ michael@0: _getURIItemCopyTransaction: michael@0: function PUIU__getURIItemCopyTransaction(aData, aContainer, aIndex) michael@0: { michael@0: let transactions = []; michael@0: if (aData.dateAdded) { michael@0: transactions.push( michael@0: new PlacesEditItemDateAddedTransaction(null, aData.dateAdded) michael@0: ); michael@0: } michael@0: if (aData.lastModified) { michael@0: transactions.push( michael@0: new PlacesEditItemLastModifiedTransaction(null, aData.lastModified) michael@0: ); michael@0: } michael@0: michael@0: let keyword = aData.keyword || null; michael@0: let annos = []; michael@0: if (aData.annos) { michael@0: annos = aData.annos.filter(function (aAnno) { michael@0: return this._copyableAnnotations.indexOf(aAnno.name) != -1; michael@0: }, this); michael@0: } michael@0: michael@0: return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(aData.uri), michael@0: aContainer, aIndex, aData.title, michael@0: keyword, annos, transactions); michael@0: }, michael@0: michael@0: /** michael@0: * Gets a transaction for copying (recursively nesting to include children) michael@0: * a folder (or container) and its contents from one folder to another. michael@0: * michael@0: * @param aData michael@0: * Unwrapped dropped folder data - Obj containing folder and children michael@0: * @param aContainer michael@0: * The container we are copying into michael@0: * @param aIndex michael@0: * The index in the destination container to insert the new items michael@0: * @return A nsITransaction object that will perform the copy. michael@0: * michael@0: * @note Since a copy creates a completely new item, only some internal michael@0: * annotations are synced from the old one. michael@0: * @see this._copyableAnnotations for the list of copyable annotations. michael@0: */ michael@0: _getFolderCopyTransaction: michael@0: function PUIU__getFolderCopyTransaction(aData, aContainer, aIndex) michael@0: { michael@0: function getChildItemsTransactions(aChildren) michael@0: { michael@0: let transactions = []; michael@0: let index = aIndex; michael@0: aChildren.forEach(function (node, i) { michael@0: // Make sure that items are given the correct index, this will be michael@0: // passed by the transaction manager to the backend for the insertion. michael@0: // Insertion behaves differently for DEFAULT_INDEX (append). michael@0: if (aIndex != PlacesUtils.bookmarks.DEFAULT_INDEX) { michael@0: index = i; michael@0: } michael@0: michael@0: if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) { michael@0: if (node.livemark && node.annos) { michael@0: transactions.push( michael@0: PlacesUIUtils._getLivemarkCopyTransaction(node, aContainer, index) michael@0: ); michael@0: } michael@0: else { michael@0: transactions.push( michael@0: PlacesUIUtils._getFolderCopyTransaction(node, aContainer, index) michael@0: ); michael@0: } michael@0: } michael@0: else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) { michael@0: transactions.push(new PlacesCreateSeparatorTransaction(-1, index)); michael@0: } michael@0: else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE) { michael@0: transactions.push( michael@0: PlacesUIUtils._getURIItemCopyTransaction(node, -1, index) michael@0: ); michael@0: } michael@0: else { michael@0: throw new Error("Unexpected item under a bookmarks folder"); michael@0: } michael@0: }); michael@0: return transactions; michael@0: } michael@0: michael@0: if (aContainer == PlacesUtils.tagsFolderId) { // Copying a tag folder. michael@0: let transactions = []; michael@0: if (aData.children) { michael@0: aData.children.forEach(function(aChild) { michael@0: transactions.push( michael@0: new PlacesTagURITransaction(PlacesUtils._uri(aChild.uri), michael@0: [aData.title]) michael@0: ); michael@0: }); michael@0: } michael@0: return new PlacesAggregatedTransaction("addTags", transactions); michael@0: } michael@0: michael@0: if (aData.livemark && aData.annos) { // Copying a livemark. michael@0: return this._getLivemarkCopyTransaction(aData, aContainer, aIndex); michael@0: } michael@0: michael@0: let transactions = getChildItemsTransactions(aData.children); michael@0: if (aData.dateAdded) { michael@0: transactions.push( michael@0: new PlacesEditItemDateAddedTransaction(null, aData.dateAdded) michael@0: ); michael@0: } michael@0: if (aData.lastModified) { michael@0: transactions.push( michael@0: new PlacesEditItemLastModifiedTransaction(null, aData.lastModified) michael@0: ); michael@0: } michael@0: michael@0: let annos = []; michael@0: if (aData.annos) { michael@0: annos = aData.annos.filter(function (aAnno) { michael@0: return this._copyableAnnotations.indexOf(aAnno.name) != -1; michael@0: }, this); michael@0: } michael@0: michael@0: return new PlacesCreateFolderTransaction(aData.title, aContainer, aIndex, michael@0: annos, transactions); michael@0: }, michael@0: michael@0: /** michael@0: * Gets a transaction for copying a live bookmark item from one container to michael@0: * another. michael@0: * michael@0: * @param aData michael@0: * Unwrapped live bookmarkmark data michael@0: * @param aContainer michael@0: * The container we are copying into michael@0: * @param aIndex michael@0: * The index in the destination container to insert the new items michael@0: * @return A nsITransaction object that will perform the copy. michael@0: * michael@0: * @note Since a copy creates a completely new item, only some internal michael@0: * annotations are synced from the old one. michael@0: * @see this._copyableAnnotations for the list of copyable annotations. michael@0: */ michael@0: _getLivemarkCopyTransaction: michael@0: function PUIU__getLivemarkCopyTransaction(aData, aContainer, aIndex) michael@0: { michael@0: if (!aData.livemark || !aData.annos) { michael@0: throw new Error("node is not a livemark"); michael@0: } michael@0: michael@0: let feedURI, siteURI; michael@0: let annos = []; michael@0: if (aData.annos) { michael@0: annos = aData.annos.filter(function (aAnno) { michael@0: if (aAnno.name == PlacesUtils.LMANNO_FEEDURI) { michael@0: feedURI = PlacesUtils._uri(aAnno.value); michael@0: } michael@0: else if (aAnno.name == PlacesUtils.LMANNO_SITEURI) { michael@0: siteURI = PlacesUtils._uri(aAnno.value); michael@0: } michael@0: return this._copyableAnnotations.indexOf(aAnno.name) != -1 michael@0: }, this); michael@0: } michael@0: michael@0: return new PlacesCreateLivemarkTransaction(feedURI, siteURI, aData.title, michael@0: aContainer, aIndex, annos); michael@0: }, michael@0: michael@0: /** michael@0: * Constructs a Transaction for the drop or paste of a blob of data into michael@0: * a container. michael@0: * @param data michael@0: * The unwrapped data blob of dropped or pasted data. michael@0: * @param type michael@0: * The content type of the data michael@0: * @param container michael@0: * The container the data was dropped or pasted into michael@0: * @param index michael@0: * The index within the container the item was dropped or pasted at michael@0: * @param copy michael@0: * The drag action was copy, so don't move folders or links. michael@0: * @returns An object implementing nsITransaction that can perform michael@0: * the move/insert. michael@0: */ michael@0: makeTransaction: michael@0: function PUIU_makeTransaction(data, type, container, index, copy) michael@0: { michael@0: switch (data.type) { michael@0: case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER: michael@0: if (copy) { michael@0: return this._getFolderCopyTransaction(data, container, index); michael@0: } michael@0: michael@0: // Otherwise move the item. michael@0: return new PlacesMoveItemTransaction(data.id, container, index); michael@0: break; michael@0: case PlacesUtils.TYPE_X_MOZ_PLACE: michael@0: if (copy || data.id == -1) { // Id is -1 if the place is not bookmarked. michael@0: return this._getURIItemCopyTransaction(data, container, index); michael@0: } michael@0: michael@0: // Otherwise move the item. michael@0: return new PlacesMoveItemTransaction(data.id, container, index); michael@0: break; michael@0: case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR: michael@0: if (copy) { michael@0: // There is no data in a separator, so copying it just amounts to michael@0: // inserting a new separator. michael@0: return new PlacesCreateSeparatorTransaction(container, index); michael@0: } michael@0: michael@0: // Otherwise move the item. michael@0: return new PlacesMoveItemTransaction(data.id, container, index); michael@0: break; michael@0: default: michael@0: if (type == PlacesUtils.TYPE_X_MOZ_URL || michael@0: type == PlacesUtils.TYPE_UNICODE || michael@0: type == this.TYPE_TAB_DROP) { michael@0: let title = type != PlacesUtils.TYPE_UNICODE ? data.title michael@0: : data.uri; michael@0: return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(data.uri), michael@0: container, index, title); michael@0: } michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Shows the bookmark dialog corresponding to the specified info. michael@0: * michael@0: * @param aInfo michael@0: * Describes the item to be edited/added in the dialog. michael@0: * See documentation at the top of bookmarkProperties.js michael@0: * @param aWindow michael@0: * Owner window for the new dialog. michael@0: * michael@0: * @see documentation at the top of bookmarkProperties.js michael@0: * @return true if any transaction has been performed, false otherwise. michael@0: */ michael@0: showBookmarkDialog: michael@0: function PUIU_showBookmarkDialog(aInfo, aParentWindow) { michael@0: // Preserve size attributes differently based on the fact the dialog has michael@0: // a folder picker or not. If the picker is visible, the dialog should michael@0: // be resizable since it may not show enough content for the folders michael@0: // hierarchy. michael@0: let hasFolderPicker = !("hiddenRows" in aInfo) || michael@0: aInfo.hiddenRows.indexOf("folderPicker") == -1; michael@0: // Use a different chrome url, since this allows to persist different sizes, michael@0: // based on resizability of the dialog. michael@0: let dialogURL = hasFolderPicker ? michael@0: "chrome://browser/content/places/bookmarkProperties2.xul" : michael@0: "chrome://browser/content/places/bookmarkProperties.xul"; michael@0: michael@0: let features = michael@0: "centerscreen,chrome,modal,resizable=" + (hasFolderPicker ? "yes" : "no"); michael@0: michael@0: aParentWindow.openDialog(dialogURL, "", features, aInfo); michael@0: return ("performed" in aInfo && aInfo.performed); michael@0: }, michael@0: michael@0: _getTopBrowserWin: function PUIU__getTopBrowserWin() { michael@0: return Services.wm.getMostRecentWindow("navigator:browser"); michael@0: }, michael@0: michael@0: /** michael@0: * Returns the closet ancestor places view for the given DOM node michael@0: * @param aNode michael@0: * a DOM node michael@0: * @return the closet ancestor places view if exists, null otherwsie. michael@0: */ michael@0: getViewForNode: function PUIU_getViewForNode(aNode) { michael@0: let node = aNode; michael@0: michael@0: // The view for a of which its associated menupopup is a places michael@0: // view, is the menupopup. michael@0: if (node.localName == "menu" && !node._placesNode && michael@0: node.lastChild._placesView) michael@0: return node.lastChild._placesView; michael@0: michael@0: while (node instanceof Ci.nsIDOMElement) { michael@0: if (node._placesView) michael@0: return node._placesView; michael@0: if (node.localName == "tree" && node.getAttribute("type") == "places") michael@0: return node; michael@0: michael@0: node = node.parentNode; michael@0: } michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * By calling this before visiting an URL, the visit will be associated to a michael@0: * TRANSITION_TYPED transition (if there is no a referrer). michael@0: * This is used when visiting pages from the history menu, history sidebar, michael@0: * url bar, url autocomplete results, and history searches from the places michael@0: * organizer. If this is not called visits will be marked as michael@0: * TRANSITION_LINK. michael@0: */ michael@0: markPageAsTyped: function PUIU_markPageAsTyped(aURL) { michael@0: PlacesUtils.history.markPageAsTyped(this.createFixedURI(aURL)); michael@0: }, michael@0: michael@0: /** michael@0: * By calling this before visiting an URL, the visit will be associated to a michael@0: * TRANSITION_BOOKMARK transition. michael@0: * This is used when visiting pages from the bookmarks menu, michael@0: * personal toolbar, and bookmarks from within the places organizer. michael@0: * If this is not called visits will be marked as TRANSITION_LINK. michael@0: */ michael@0: markPageAsFollowedBookmark: function PUIU_markPageAsFollowedBookmark(aURL) { michael@0: PlacesUtils.history.markPageAsFollowedBookmark(this.createFixedURI(aURL)); michael@0: }, michael@0: michael@0: /** michael@0: * By calling this before visiting an URL, any visit in frames will be michael@0: * associated to a TRANSITION_FRAMED_LINK transition. michael@0: * This is actually used to distinguish user-initiated visits in frames michael@0: * so automatic visits can be correctly ignored. michael@0: */ michael@0: markPageAsFollowedLink: function PUIU_markPageAsFollowedLink(aURL) { michael@0: PlacesUtils.history.markPageAsFollowedLink(this.createFixedURI(aURL)); michael@0: }, michael@0: michael@0: /** michael@0: * Allows opening of javascript/data URI only if the given node is michael@0: * bookmarked (see bug 224521). michael@0: * @param aURINode michael@0: * a URI node michael@0: * @param aWindow michael@0: * a window on which a potential error alert is shown on. michael@0: * @return true if it's safe to open the node in the browser, false otherwise. michael@0: * michael@0: */ michael@0: checkURLSecurity: function PUIU_checkURLSecurity(aURINode, aWindow) { michael@0: if (PlacesUtils.nodeIsBookmark(aURINode)) michael@0: return true; michael@0: michael@0: var uri = PlacesUtils._uri(aURINode.uri); michael@0: if (uri.schemeIs("javascript") || uri.schemeIs("data")) { michael@0: const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties"; michael@0: var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"]. michael@0: getService(Ci.nsIStringBundleService). michael@0: createBundle(BRANDING_BUNDLE_URI). michael@0: GetStringFromName("brandShortName"); michael@0: michael@0: var errorStr = this.getString("load-js-data-url-error"); michael@0: Services.prompt.alert(aWindow, brandShortName, errorStr); michael@0: return false; michael@0: } michael@0: return true; michael@0: }, michael@0: michael@0: /** michael@0: * Get the description associated with a document, as specified in a michael@0: * element. michael@0: * @param doc michael@0: * A DOM Document to get a description for michael@0: * @returns A description string if a META element was discovered with a michael@0: * "description" or "httpequiv" attribute, empty string otherwise. michael@0: */ michael@0: getDescriptionFromDocument: function PUIU_getDescriptionFromDocument(doc) { michael@0: var metaElements = doc.getElementsByTagName("META"); michael@0: for (var i = 0; i < metaElements.length; ++i) { michael@0: if (metaElements[i].name.toLowerCase() == "description" || michael@0: metaElements[i].httpEquiv.toLowerCase() == "description") { michael@0: return metaElements[i].content; michael@0: } michael@0: } michael@0: return ""; michael@0: }, michael@0: michael@0: /** michael@0: * Retrieve the description of an item michael@0: * @param aItemId michael@0: * item identifier michael@0: * @returns the description of the given item, or an empty string if it is michael@0: * not set. michael@0: */ michael@0: getItemDescription: function PUIU_getItemDescription(aItemId) { michael@0: if (PlacesUtils.annotations.itemHasAnnotation(aItemId, this.DESCRIPTION_ANNO)) michael@0: return PlacesUtils.annotations.getItemAnnotation(aItemId, this.DESCRIPTION_ANNO); michael@0: return ""; michael@0: }, michael@0: michael@0: /** michael@0: * Gives the user a chance to cancel loading lots of tabs at once michael@0: */ michael@0: _confirmOpenInTabs: michael@0: function PUIU__confirmOpenInTabs(numTabsToOpen, aWindow) { michael@0: const WARN_ON_OPEN_PREF = "browser.tabs.warnOnOpen"; michael@0: var reallyOpen = true; michael@0: michael@0: if (Services.prefs.getBoolPref(WARN_ON_OPEN_PREF)) { michael@0: if (numTabsToOpen >= Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")) { michael@0: // default to true: if it were false, we wouldn't get this far michael@0: var warnOnOpen = { value: true }; michael@0: michael@0: var messageKey = "tabs.openWarningMultipleBranded"; michael@0: var openKey = "tabs.openButtonMultiple"; michael@0: const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties"; michael@0: var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"]. michael@0: getService(Ci.nsIStringBundleService). michael@0: createBundle(BRANDING_BUNDLE_URI). michael@0: GetStringFromName("brandShortName"); michael@0: michael@0: var buttonPressed = Services.prompt.confirmEx( michael@0: aWindow, michael@0: this.getString("tabs.openWarningTitle"), michael@0: this.getFormattedString(messageKey, [numTabsToOpen, brandShortName]), michael@0: (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) + michael@0: (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1), michael@0: this.getString(openKey), null, null, michael@0: this.getFormattedString("tabs.openWarningPromptMeBranded", michael@0: [brandShortName]), michael@0: warnOnOpen michael@0: ); michael@0: michael@0: reallyOpen = (buttonPressed == 0); michael@0: // don't set the pref unless they press OK and it's false michael@0: if (reallyOpen && !warnOnOpen.value) michael@0: Services.prefs.setBoolPref(WARN_ON_OPEN_PREF, false); michael@0: } michael@0: } michael@0: michael@0: return reallyOpen; michael@0: }, michael@0: michael@0: /** aItemsToOpen needs to be an array of objects of the form: michael@0: * {uri: string, isBookmark: boolean} michael@0: */ michael@0: _openTabset: function PUIU__openTabset(aItemsToOpen, aEvent, aWindow) { michael@0: if (!aItemsToOpen.length) michael@0: return; michael@0: michael@0: // Prefer the caller window if it's a browser window, otherwise use michael@0: // the top browser window. michael@0: var browserWindow = null; michael@0: browserWindow = michael@0: aWindow && aWindow.document.documentElement.getAttribute("windowtype") == "navigator:browser" ? michael@0: aWindow : this._getTopBrowserWin(); michael@0: michael@0: var urls = []; michael@0: let skipMarking = browserWindow && PrivateBrowsingUtils.isWindowPrivate(browserWindow); michael@0: for (let item of aItemsToOpen) { michael@0: urls.push(item.uri); michael@0: if (skipMarking) { michael@0: continue; michael@0: } michael@0: michael@0: if (item.isBookmark) michael@0: this.markPageAsFollowedBookmark(item.uri); michael@0: else michael@0: this.markPageAsTyped(item.uri); michael@0: } michael@0: michael@0: // whereToOpenLink doesn't return "window" when there's no browser window michael@0: // open (Bug 630255). michael@0: var where = browserWindow ? michael@0: browserWindow.whereToOpenLink(aEvent, false, true) : "window"; michael@0: if (where == "window") { michael@0: // There is no browser window open, thus open a new one. michael@0: var uriList = PlacesUtils.toISupportsString(urls.join("|")); michael@0: var args = Cc["@mozilla.org/supports-array;1"]. michael@0: createInstance(Ci.nsISupportsArray); michael@0: args.AppendElement(uriList); michael@0: browserWindow = Services.ww.openWindow(aWindow, michael@0: "chrome://browser/content/browser.xul", michael@0: null, "chrome,dialog=no,all", args); michael@0: return; michael@0: } michael@0: michael@0: var loadInBackground = where == "tabshifted" ? true : false; michael@0: // For consistency, we want all the bookmarks to open in new tabs, instead michael@0: // of having one of them replace the currently focused tab. Hence we call michael@0: // loadTabs with aReplace set to false. michael@0: browserWindow.gBrowser.loadTabs(urls, loadInBackground, false); michael@0: }, michael@0: michael@0: openContainerNodeInTabs: michael@0: function PUIU_openContainerInTabs(aNode, aEvent, aView) { michael@0: let window = aView.ownerWindow; michael@0: michael@0: let urlsToOpen = PlacesUtils.getURLsForContainerNode(aNode); michael@0: if (!this._confirmOpenInTabs(urlsToOpen.length, window)) michael@0: return; michael@0: michael@0: this._openTabset(urlsToOpen, aEvent, window); michael@0: }, michael@0: michael@0: openURINodesInTabs: function PUIU_openURINodesInTabs(aNodes, aEvent, aView) { michael@0: let window = aView.ownerWindow; michael@0: michael@0: let urlsToOpen = []; michael@0: for (var i=0; i < aNodes.length; i++) { michael@0: // Skip over separators and folders. michael@0: if (PlacesUtils.nodeIsURI(aNodes[i])) michael@0: urlsToOpen.push({uri: aNodes[i].uri, isBookmark: PlacesUtils.nodeIsBookmark(aNodes[i])}); michael@0: } michael@0: this._openTabset(urlsToOpen, aEvent, window); michael@0: }, michael@0: michael@0: /** michael@0: * Loads the node's URL in the appropriate tab or window or as a web michael@0: * panel given the user's preference specified by modifier keys tracked by a michael@0: * DOM mouse/key event. michael@0: * @param aNode michael@0: * An uri result node. michael@0: * @param aEvent michael@0: * The DOM mouse/key event with modifier keys set that track the michael@0: * user's preferred destination window or tab. michael@0: * @param aView michael@0: * The controller associated with aNode. michael@0: */ michael@0: openNodeWithEvent: michael@0: function PUIU_openNodeWithEvent(aNode, aEvent, aView) { michael@0: let window = aView.ownerWindow; michael@0: this._openNodeIn(aNode, window.whereToOpenLink(aEvent, false, true), window); michael@0: }, michael@0: michael@0: /** michael@0: * Loads the node's URL in the appropriate tab or window or as a michael@0: * web panel. michael@0: * see also openUILinkIn michael@0: */ michael@0: openNodeIn: function PUIU_openNodeIn(aNode, aWhere, aView) { michael@0: let window = aView.ownerWindow; michael@0: this._openNodeIn(aNode, aWhere, window); michael@0: }, michael@0: michael@0: _openNodeIn: function PUIU_openNodeIn(aNode, aWhere, aWindow) { michael@0: if (aNode && PlacesUtils.nodeIsURI(aNode) && michael@0: this.checkURLSecurity(aNode, aWindow)) { michael@0: let isBookmark = PlacesUtils.nodeIsBookmark(aNode); michael@0: michael@0: if (!PrivateBrowsingUtils.isWindowPrivate(aWindow)) { michael@0: if (isBookmark) michael@0: this.markPageAsFollowedBookmark(aNode.uri); michael@0: else michael@0: this.markPageAsTyped(aNode.uri); michael@0: } michael@0: michael@0: // Check whether the node is a bookmark which should be opened as michael@0: // a web panel michael@0: if (aWhere == "current" && isBookmark) { michael@0: if (PlacesUtils.annotations michael@0: .itemHasAnnotation(aNode.itemId, this.LOAD_IN_SIDEBAR_ANNO)) { michael@0: let browserWin = this._getTopBrowserWin(); michael@0: if (browserWin) { michael@0: browserWin.openWebPanel(aNode.title, aNode.uri); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: aWindow.openUILinkIn(aNode.uri, aWhere, { michael@0: inBackground: Services.prefs.getBoolPref("browser.tabs.loadBookmarksInBackground") michael@0: }); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Helper for guessing scheme from an url string. michael@0: * Used to avoid nsIURI overhead in frequently called UI functions. michael@0: * michael@0: * @param aUrlString the url to guess the scheme from. michael@0: * michael@0: * @return guessed scheme for this url string. michael@0: * michael@0: * @note this is not supposed be perfect, so use it only for UI purposes. michael@0: */ michael@0: guessUrlSchemeForUI: function PUIU_guessUrlSchemeForUI(aUrlString) { michael@0: return aUrlString.substr(0, aUrlString.indexOf(":")); michael@0: }, michael@0: michael@0: getBestTitle: function PUIU_getBestTitle(aNode, aDoNotCutTitle) { michael@0: var title; michael@0: if (!aNode.title && PlacesUtils.nodeIsURI(aNode)) { michael@0: // if node title is empty, try to set the label using host and filename michael@0: // PlacesUtils._uri() will throw if aNode.uri is not a valid URI michael@0: try { michael@0: var uri = PlacesUtils._uri(aNode.uri); michael@0: var host = uri.host; michael@0: var fileName = uri.QueryInterface(Ci.nsIURL).fileName; michael@0: // if fileName is empty, use path to distinguish labels michael@0: if (aDoNotCutTitle) { michael@0: title = host + uri.path; michael@0: } else { michael@0: title = host + (fileName ? michael@0: (host ? "/" + this.ellipsis + "/" : "") + fileName : michael@0: uri.path); michael@0: } michael@0: } michael@0: catch (e) { michael@0: // Use (no title) for non-standard URIs (data:, javascript:, ...) michael@0: title = ""; michael@0: } michael@0: } michael@0: else michael@0: title = aNode.title; michael@0: michael@0: return title || this.getString("noTitle"); michael@0: }, michael@0: michael@0: get leftPaneQueries() { michael@0: // build the map michael@0: this.leftPaneFolderId; michael@0: return this.leftPaneQueries; michael@0: }, michael@0: michael@0: // Get the folder id for the organizer left-pane folder. michael@0: get leftPaneFolderId() { michael@0: let leftPaneRoot = -1; michael@0: let allBookmarksId; michael@0: michael@0: // Shortcuts to services. michael@0: let bs = PlacesUtils.bookmarks; michael@0: let as = PlacesUtils.annotations; michael@0: michael@0: // This is the list of the left pane queries. michael@0: let queries = { michael@0: "PlacesRoot": { title: "" }, michael@0: "History": { title: this.getString("OrganizerQueryHistory") }, michael@0: "Downloads": { title: this.getString("OrganizerQueryDownloads") }, michael@0: "Tags": { title: this.getString("OrganizerQueryTags") }, michael@0: "AllBookmarks": { title: this.getString("OrganizerQueryAllBookmarks") }, michael@0: "BookmarksToolbar": michael@0: { title: null, michael@0: concreteTitle: PlacesUtils.getString("BookmarksToolbarFolderTitle"), michael@0: concreteId: PlacesUtils.toolbarFolderId }, michael@0: "BookmarksMenu": michael@0: { title: null, michael@0: concreteTitle: PlacesUtils.getString("BookmarksMenuFolderTitle"), michael@0: concreteId: PlacesUtils.bookmarksMenuFolderId }, michael@0: "UnfiledBookmarks": michael@0: { title: null, michael@0: concreteTitle: PlacesUtils.getString("UnsortedBookmarksFolderTitle"), michael@0: concreteId: PlacesUtils.unfiledBookmarksFolderId }, michael@0: }; michael@0: // All queries but PlacesRoot. michael@0: const EXPECTED_QUERY_COUNT = 7; michael@0: michael@0: // Removes an item and associated annotations, ignoring eventual errors. michael@0: function safeRemoveItem(aItemId) { michael@0: try { michael@0: if (as.itemHasAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) && michael@0: !(as.getItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) in queries)) { michael@0: // Some extension annotated their roots with our query annotation, michael@0: // so we should not delete them. michael@0: return; michael@0: } michael@0: // removeItemAnnotation does not check if item exists, nor the anno, michael@0: // so this is safe to do. michael@0: as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO); michael@0: as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO); michael@0: // This will throw if the annotation is an orphan. michael@0: bs.removeItem(aItemId); michael@0: } michael@0: catch(e) { /* orphan anno */ } michael@0: } michael@0: michael@0: // Returns true if item really exists, false otherwise. michael@0: function itemExists(aItemId) { michael@0: try { michael@0: bs.getItemIndex(aItemId); michael@0: return true; michael@0: } michael@0: catch(e) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Get all items marked as being the left pane folder. michael@0: let items = as.getItemsWithAnnotation(this.ORGANIZER_FOLDER_ANNO); michael@0: if (items.length > 1) { michael@0: // Something went wrong, we cannot have more than one left pane folder, michael@0: // remove all left pane folders and continue. We will create a new one. michael@0: items.forEach(safeRemoveItem); michael@0: } michael@0: else if (items.length == 1 && items[0] != -1) { michael@0: leftPaneRoot = items[0]; michael@0: // Check that organizer left pane root is valid. michael@0: let version = as.getItemAnnotation(leftPaneRoot, this.ORGANIZER_FOLDER_ANNO); michael@0: if (version != this.ORGANIZER_LEFTPANE_VERSION || michael@0: !itemExists(leftPaneRoot)) { michael@0: // Invalid root, we must rebuild the left pane. michael@0: safeRemoveItem(leftPaneRoot); michael@0: leftPaneRoot = -1; michael@0: } michael@0: } michael@0: michael@0: if (leftPaneRoot != -1) { michael@0: // A valid left pane folder has been found. michael@0: // Build the leftPaneQueries Map. This is used to quickly access them, michael@0: // associating a mnemonic name to the real item ids. michael@0: delete this.leftPaneQueries; michael@0: this.leftPaneQueries = {}; michael@0: michael@0: let items = as.getItemsWithAnnotation(this.ORGANIZER_QUERY_ANNO); michael@0: // While looping through queries we will also check for their validity. michael@0: let queriesCount = 0; michael@0: let corrupt = false; michael@0: for (let i = 0; i < items.length; i++) { michael@0: let queryName = as.getItemAnnotation(items[i], this.ORGANIZER_QUERY_ANNO); michael@0: michael@0: // Some extension did use our annotation to decorate their items michael@0: // with icons, so we should check only our elements, to avoid dataloss. michael@0: if (!(queryName in queries)) michael@0: continue; michael@0: michael@0: let query = queries[queryName]; michael@0: query.itemId = items[i]; michael@0: michael@0: if (!itemExists(query.itemId)) { michael@0: // Orphan annotation, bail out and create a new left pane root. michael@0: corrupt = true; michael@0: break; michael@0: } michael@0: michael@0: // Check that all queries have valid parents. michael@0: let parentId = bs.getFolderIdForItem(query.itemId); michael@0: if (items.indexOf(parentId) == -1 && parentId != leftPaneRoot) { michael@0: // The parent is not part of the left pane, bail out and create a new michael@0: // left pane root. michael@0: corrupt = true; michael@0: break; michael@0: } michael@0: michael@0: // Titles could have been corrupted or the user could have changed his michael@0: // locale. Check title and eventually fix it. michael@0: if (bs.getItemTitle(query.itemId) != query.title) michael@0: bs.setItemTitle(query.itemId, query.title); michael@0: if ("concreteId" in query) { michael@0: if (bs.getItemTitle(query.concreteId) != query.concreteTitle) michael@0: bs.setItemTitle(query.concreteId, query.concreteTitle); michael@0: } michael@0: michael@0: // Add the query to our cache. michael@0: this.leftPaneQueries[queryName] = query.itemId; michael@0: queriesCount++; michael@0: } michael@0: michael@0: // Note: it's not enough to just check for queriesCount, since we may michael@0: // find an invalid query just after accounting for a sufficient number of michael@0: // valid ones. As well as we can't just rely on corrupt since we may find michael@0: // less valid queries than expected. michael@0: if (corrupt || queriesCount != EXPECTED_QUERY_COUNT) { michael@0: // Queries number is wrong, so the left pane must be corrupt. michael@0: // Note: we can't just remove the leftPaneRoot, because some query could michael@0: // have a bad parent, so we have to remove all items one by one. michael@0: items.forEach(safeRemoveItem); michael@0: safeRemoveItem(leftPaneRoot); michael@0: } michael@0: else { michael@0: // Everything is fine, return the current left pane folder. michael@0: delete this.leftPaneFolderId; michael@0: return this.leftPaneFolderId = leftPaneRoot; michael@0: } michael@0: } michael@0: michael@0: // Create a new left pane folder. michael@0: var callback = { michael@0: // Helper to create an organizer special query. michael@0: create_query: function CB_create_query(aQueryName, aParentId, aQueryUrl) { michael@0: let itemId = bs.insertBookmark(aParentId, michael@0: PlacesUtils._uri(aQueryUrl), michael@0: bs.DEFAULT_INDEX, michael@0: queries[aQueryName].title); michael@0: // Mark as special organizer query. michael@0: as.setItemAnnotation(itemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aQueryName, michael@0: 0, as.EXPIRE_NEVER); michael@0: // We should never backup this, since it changes between profiles. michael@0: as.setItemAnnotation(itemId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1, michael@0: 0, as.EXPIRE_NEVER); michael@0: // Add to the queries map. michael@0: PlacesUIUtils.leftPaneQueries[aQueryName] = itemId; michael@0: return itemId; michael@0: }, michael@0: michael@0: // Helper to create an organizer special folder. michael@0: create_folder: function CB_create_folder(aFolderName, aParentId, aIsRoot) { michael@0: // Left Pane Root Folder. michael@0: let folderId = bs.createFolder(aParentId, michael@0: queries[aFolderName].title, michael@0: bs.DEFAULT_INDEX); michael@0: // We should never backup this, since it changes between profiles. michael@0: as.setItemAnnotation(folderId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1, michael@0: 0, as.EXPIRE_NEVER); michael@0: // Disallow manipulating this folder within the organizer UI. michael@0: bs.setFolderReadonly(folderId, true); michael@0: michael@0: if (aIsRoot) { michael@0: // Mark as special left pane root. michael@0: as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO, michael@0: PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION, michael@0: 0, as.EXPIRE_NEVER); michael@0: } michael@0: else { michael@0: // Mark as special organizer folder. michael@0: as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aFolderName, michael@0: 0, as.EXPIRE_NEVER); michael@0: PlacesUIUtils.leftPaneQueries[aFolderName] = folderId; michael@0: } michael@0: return folderId; michael@0: }, michael@0: michael@0: runBatched: function CB_runBatched(aUserData) { michael@0: delete PlacesUIUtils.leftPaneQueries; michael@0: PlacesUIUtils.leftPaneQueries = { }; michael@0: michael@0: // Left Pane Root Folder. michael@0: leftPaneRoot = this.create_folder("PlacesRoot", bs.placesRoot, true); michael@0: michael@0: // History Query. michael@0: this.create_query("History", leftPaneRoot, michael@0: "place:type=" + michael@0: Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY + michael@0: "&sort=" + michael@0: Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING); michael@0: michael@0: // Downloads. michael@0: this.create_query("Downloads", leftPaneRoot, michael@0: "place:transition=" + michael@0: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD + michael@0: "&sort=" + michael@0: Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING); michael@0: michael@0: // Tags Query. michael@0: this.create_query("Tags", leftPaneRoot, michael@0: "place:type=" + michael@0: Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY + michael@0: "&sort=" + michael@0: Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING); michael@0: michael@0: // All Bookmarks Folder. michael@0: allBookmarksId = this.create_folder("AllBookmarks", leftPaneRoot, false); michael@0: michael@0: // All Bookmarks->Bookmarks Toolbar Query. michael@0: this.create_query("BookmarksToolbar", allBookmarksId, michael@0: "place:folder=TOOLBAR"); michael@0: michael@0: // All Bookmarks->Bookmarks Menu Query. michael@0: this.create_query("BookmarksMenu", allBookmarksId, michael@0: "place:folder=BOOKMARKS_MENU"); michael@0: michael@0: // All Bookmarks->Unfiled Bookmarks Query. michael@0: this.create_query("UnfiledBookmarks", allBookmarksId, michael@0: "place:folder=UNFILED_BOOKMARKS"); michael@0: } michael@0: }; michael@0: bs.runInBatchMode(callback, null); michael@0: michael@0: delete this.leftPaneFolderId; michael@0: return this.leftPaneFolderId = leftPaneRoot; michael@0: }, michael@0: michael@0: /** michael@0: * Get the folder id for the organizer left-pane folder. michael@0: */ michael@0: get allBookmarksFolderId() { michael@0: // ensure the left-pane root is initialized; michael@0: this.leftPaneFolderId; michael@0: delete this.allBookmarksFolderId; michael@0: return this.allBookmarksFolderId = this.leftPaneQueries["AllBookmarks"]; michael@0: }, michael@0: michael@0: /** michael@0: * If an item is a left-pane query, returns the name of the query michael@0: * or an empty string if not. michael@0: * michael@0: * @param aItemId id of a container michael@0: * @returns the name of the query, or empty string if not a left-pane query michael@0: */ michael@0: getLeftPaneQueryNameFromId: function PUIU_getLeftPaneQueryNameFromId(aItemId) { michael@0: var queryName = ""; michael@0: // If the let pane hasn't been built, use the annotation service michael@0: // directly, to avoid building the left pane too early. michael@0: if (Object.getOwnPropertyDescriptor(this, "leftPaneFolderId").value === undefined) { michael@0: try { michael@0: queryName = PlacesUtils.annotations. michael@0: getItemAnnotation(aItemId, this.ORGANIZER_QUERY_ANNO); michael@0: } michael@0: catch (ex) { michael@0: // doesn't have the annotation michael@0: queryName = ""; michael@0: } michael@0: } michael@0: else { michael@0: // If the left pane has already been built, use the name->id map michael@0: // cached in PlacesUIUtils. michael@0: for (let [name, id] in Iterator(this.leftPaneQueries)) { michael@0: if (aItemId == id) michael@0: queryName = name; michael@0: } michael@0: } michael@0: return queryName; michael@0: }, michael@0: michael@0: shouldShowTabsFromOtherComputersMenuitem: function() { michael@0: // If Sync isn't configured yet, then don't show the menuitem. michael@0: return Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED && michael@0: Weave.Svc.Prefs.get("firstSync", "") != "notReady"; michael@0: }, michael@0: michael@0: shouldEnableTabsFromOtherComputersMenuitem: function() { michael@0: // The tabs engine might never be inited (if services.sync.registerEngines michael@0: // is modified), so make sure we avoid undefined errors. michael@0: return Weave.Service.isLoggedIn && michael@0: Weave.Service.engineManager.get("tabs") && michael@0: Weave.Service.engineManager.get("tabs").enabled; michael@0: }, michael@0: }; michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(PlacesUIUtils, "RDF", michael@0: "@mozilla.org/rdf/rdf-service;1", michael@0: "nsIRDFService"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(PlacesUIUtils, "localStore", function() { michael@0: return PlacesUIUtils.RDF.GetDataSource("rdf:local-store"); michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ellipsis", function() { michael@0: return Services.prefs.getComplexValue("intl.ellipsis", michael@0: Ci.nsIPrefLocalizedString).data; michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(PlacesUIUtils, "useAsyncTransactions", function() { michael@0: try { michael@0: return Services.prefs.getBoolPref("browser.places.useAsyncTransactions"); michael@0: } michael@0: catch(ex) { } michael@0: return false; michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "URIFixup", michael@0: "@mozilla.org/docshell/urifixup;1", michael@0: "nsIURIFixup"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "bundle", function() { michael@0: const PLACES_STRING_BUNDLE_URI = michael@0: "chrome://browser/locale/places/places.properties"; michael@0: return Cc["@mozilla.org/intl/stringbundle;1"]. michael@0: getService(Ci.nsIStringBundleService). michael@0: createBundle(PLACES_STRING_BUNDLE_URI); michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "focusManager", michael@0: "@mozilla.org/focus-manager;1", michael@0: "nsIFocusManager"); michael@0: michael@0: /** michael@0: * This is a compatibility shim for old PUIU.ptm users. michael@0: * michael@0: * If you're looking for transactions and writing new code using them, directly michael@0: * use the transactions objects exported by the PlacesUtils.jsm module. michael@0: * michael@0: * This object will be removed once enough users are converted to the new API. michael@0: */ michael@0: XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ptm", function() { michael@0: // Ensure PlacesUtils is imported in scope. michael@0: PlacesUtils; michael@0: michael@0: return { michael@0: aggregateTransactions: function(aName, aTransactions) michael@0: new PlacesAggregatedTransaction(aName, aTransactions), michael@0: michael@0: createFolder: function(aName, aContainer, aIndex, aAnnotations, michael@0: aChildItemsTransactions) michael@0: new PlacesCreateFolderTransaction(aName, aContainer, aIndex, aAnnotations, michael@0: aChildItemsTransactions), michael@0: michael@0: createItem: function(aURI, aContainer, aIndex, aTitle, aKeyword, michael@0: aAnnotations, aChildTransactions) michael@0: new PlacesCreateBookmarkTransaction(aURI, aContainer, aIndex, aTitle, michael@0: aKeyword, aAnnotations, michael@0: aChildTransactions), michael@0: michael@0: createSeparator: function(aContainer, aIndex) michael@0: new PlacesCreateSeparatorTransaction(aContainer, aIndex), michael@0: michael@0: createLivemark: function(aFeedURI, aSiteURI, aName, aContainer, aIndex, michael@0: aAnnotations) michael@0: new PlacesCreateLivemarkTransaction(aFeedURI, aSiteURI, aName, aContainer, michael@0: aIndex, aAnnotations), michael@0: michael@0: moveItem: function(aItemId, aNewContainer, aNewIndex) michael@0: new PlacesMoveItemTransaction(aItemId, aNewContainer, aNewIndex), michael@0: michael@0: removeItem: function(aItemId) michael@0: new PlacesRemoveItemTransaction(aItemId), michael@0: michael@0: editItemTitle: function(aItemId, aNewTitle) michael@0: new PlacesEditItemTitleTransaction(aItemId, aNewTitle), michael@0: michael@0: editBookmarkURI: function(aItemId, aNewURI) michael@0: new PlacesEditBookmarkURITransaction(aItemId, aNewURI), michael@0: michael@0: setItemAnnotation: function(aItemId, aAnnotationObject) michael@0: new PlacesSetItemAnnotationTransaction(aItemId, aAnnotationObject), michael@0: michael@0: setPageAnnotation: function(aURI, aAnnotationObject) michael@0: new PlacesSetPageAnnotationTransaction(aURI, aAnnotationObject), michael@0: michael@0: editBookmarkKeyword: function(aItemId, aNewKeyword) michael@0: new PlacesEditBookmarkKeywordTransaction(aItemId, aNewKeyword), michael@0: michael@0: editBookmarkPostData: function(aItemId, aPostData) michael@0: new PlacesEditBookmarkPostDataTransaction(aItemId, aPostData), michael@0: michael@0: editLivemarkSiteURI: function(aLivemarkId, aSiteURI) michael@0: new PlacesEditLivemarkSiteURITransaction(aLivemarkId, aSiteURI), michael@0: michael@0: editLivemarkFeedURI: function(aLivemarkId, aFeedURI) michael@0: new PlacesEditLivemarkFeedURITransaction(aLivemarkId, aFeedURI), michael@0: michael@0: editItemDateAdded: function(aItemId, aNewDateAdded) michael@0: new PlacesEditItemDateAddedTransaction(aItemId, aNewDateAdded), michael@0: michael@0: editItemLastModified: function(aItemId, aNewLastModified) michael@0: new PlacesEditItemLastModifiedTransaction(aItemId, aNewLastModified), michael@0: michael@0: sortFolderByName: function(aFolderId) michael@0: new PlacesSortFolderByNameTransaction(aFolderId), michael@0: michael@0: tagURI: function(aURI, aTags) michael@0: new PlacesTagURITransaction(aURI, aTags), michael@0: michael@0: untagURI: function(aURI, aTags) michael@0: new PlacesUntagURITransaction(aURI, aTags), michael@0: michael@0: /** michael@0: * Transaction for setting/unsetting Load-in-sidebar annotation. michael@0: * michael@0: * @param aBookmarkId michael@0: * id of the bookmark where to set Load-in-sidebar annotation. michael@0: * @param aLoadInSidebar michael@0: * boolean value. michael@0: * @returns nsITransaction object. michael@0: */ michael@0: setLoadInSidebar: function(aItemId, aLoadInSidebar) michael@0: { michael@0: let annoObj = { name: PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO, michael@0: type: Ci.nsIAnnotationService.TYPE_INT32, michael@0: flags: 0, michael@0: value: aLoadInSidebar, michael@0: expires: Ci.nsIAnnotationService.EXPIRE_NEVER }; michael@0: return new PlacesSetItemAnnotationTransaction(aItemId, annoObj); michael@0: }, michael@0: michael@0: /** michael@0: * Transaction for editing the description of a bookmark or a folder. michael@0: * michael@0: * @param aItemId michael@0: * id of the item to edit. michael@0: * @param aDescription michael@0: * new description. michael@0: * @returns nsITransaction object. michael@0: */ michael@0: editItemDescription: function(aItemId, aDescription) michael@0: { michael@0: let annoObj = { name: PlacesUIUtils.DESCRIPTION_ANNO, michael@0: type: Ci.nsIAnnotationService.TYPE_STRING, michael@0: flags: 0, michael@0: value: aDescription, michael@0: expires: Ci.nsIAnnotationService.EXPIRE_NEVER }; michael@0: return new PlacesSetItemAnnotationTransaction(aItemId, annoObj); michael@0: }, michael@0: michael@0: //////////////////////////////////////////////////////////////////////////// michael@0: //// nsITransactionManager forwarders. michael@0: michael@0: beginBatch: function() michael@0: PlacesUtils.transactionManager.beginBatch(null), michael@0: michael@0: endBatch: function() michael@0: PlacesUtils.transactionManager.endBatch(false), michael@0: michael@0: doTransaction: function(txn) michael@0: PlacesUtils.transactionManager.doTransaction(txn), michael@0: michael@0: undoTransaction: function() michael@0: PlacesUtils.transactionManager.undoTransaction(), michael@0: michael@0: redoTransaction: function() michael@0: PlacesUtils.transactionManager.redoTransaction(), michael@0: michael@0: get numberOfUndoItems() michael@0: PlacesUtils.transactionManager.numberOfUndoItems, michael@0: get numberOfRedoItems() michael@0: PlacesUtils.transactionManager.numberOfRedoItems, michael@0: get maxTransactionCount() michael@0: PlacesUtils.transactionManager.maxTransactionCount, michael@0: set maxTransactionCount(val) michael@0: PlacesUtils.transactionManager.maxTransactionCount = val, michael@0: michael@0: clear: function() michael@0: PlacesUtils.transactionManager.clear(), michael@0: michael@0: peekUndoStack: function() michael@0: PlacesUtils.transactionManager.peekUndoStack(), michael@0: michael@0: peekRedoStack: function() michael@0: PlacesUtils.transactionManager.peekRedoStack(), michael@0: michael@0: getUndoStack: function() michael@0: PlacesUtils.transactionManager.getUndoStack(), michael@0: michael@0: getRedoStack: function() michael@0: PlacesUtils.transactionManager.getRedoStack(), michael@0: michael@0: AddListener: function(aListener) michael@0: PlacesUtils.transactionManager.AddListener(aListener), michael@0: michael@0: RemoveListener: function(aListener) michael@0: PlacesUtils.transactionManager.RemoveListener(aListener) michael@0: } michael@0: });