From 6d614170cbfa958564eb5f824234ad5a9e484344 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 05:06:10 -0500 Subject: Revert "Add Basilisk" This reverts commit e72ef92b5bdc43cd2584198e2e54e951b70299e8. --- .../basilisk/components/places/PlacesUIUtils.jsm | 1760 ------------------ .../places/content/bookmarkProperties.js | 683 ------- .../places/content/bookmarkProperties.xul | 43 - .../components/places/content/bookmarksPanel.js | 23 - .../components/places/content/bookmarksPanel.xul | 54 - .../places/content/browserPlacesViews.js | 1959 -------------------- .../components/places/content/controller.js | 1723 ----------------- .../places/content/downloadsViewOverlay.xul | 47 - .../places/content/editBookmarkOverlay.js | 1148 ------------ .../places/content/editBookmarkOverlay.xul | 188 -- .../components/places/content/history-panel.js | 94 - .../components/places/content/history-panel.xul | 95 - .../basilisk/components/places/content/menu.xml | 617 ------ .../components/places/content/moveBookmarks.js | 65 - .../components/places/content/moveBookmarks.xul | 53 - .../components/places/content/organizer.css | 7 - .../basilisk/components/places/content/places.css | 37 - .../basilisk/components/places/content/places.js | 1387 -------------- .../basilisk/components/places/content/places.xul | 438 ----- .../components/places/content/placesOverlay.xul | 229 --- .../components/places/content/sidebarUtils.js | 104 -- .../basilisk/components/places/content/tree.xml | 791 -------- .../basilisk/components/places/content/treeView.js | 1708 ----------------- application/basilisk/components/places/jar.mn | 34 - application/basilisk/components/places/moz.build | 11 - 25 files changed, 13298 deletions(-) delete mode 100644 application/basilisk/components/places/PlacesUIUtils.jsm delete mode 100644 application/basilisk/components/places/content/bookmarkProperties.js delete mode 100644 application/basilisk/components/places/content/bookmarkProperties.xul delete mode 100644 application/basilisk/components/places/content/bookmarksPanel.js delete mode 100644 application/basilisk/components/places/content/bookmarksPanel.xul delete mode 100644 application/basilisk/components/places/content/browserPlacesViews.js delete mode 100644 application/basilisk/components/places/content/controller.js delete mode 100644 application/basilisk/components/places/content/downloadsViewOverlay.xul delete mode 100644 application/basilisk/components/places/content/editBookmarkOverlay.js delete mode 100644 application/basilisk/components/places/content/editBookmarkOverlay.xul delete mode 100644 application/basilisk/components/places/content/history-panel.js delete mode 100644 application/basilisk/components/places/content/history-panel.xul delete mode 100644 application/basilisk/components/places/content/menu.xml delete mode 100644 application/basilisk/components/places/content/moveBookmarks.js delete mode 100644 application/basilisk/components/places/content/moveBookmarks.xul delete mode 100644 application/basilisk/components/places/content/organizer.css delete mode 100644 application/basilisk/components/places/content/places.css delete mode 100644 application/basilisk/components/places/content/places.js delete mode 100644 application/basilisk/components/places/content/places.xul delete mode 100644 application/basilisk/components/places/content/placesOverlay.xul delete mode 100644 application/basilisk/components/places/content/sidebarUtils.js delete mode 100644 application/basilisk/components/places/content/tree.xml delete mode 100644 application/basilisk/components/places/content/treeView.js delete mode 100644 application/basilisk/components/places/jar.mn delete mode 100644 application/basilisk/components/places/moz.build (limited to 'application/basilisk/components/places') diff --git a/application/basilisk/components/places/PlacesUIUtils.jsm b/application/basilisk/components/places/PlacesUIUtils.jsm deleted file mode 100644 index 5faf820c6..000000000 --- a/application/basilisk/components/places/PlacesUIUtils.jsm +++ /dev/null @@ -1,1760 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 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"); -Cu.import("resource://gre/modules/Timer.jsm"); - -Cu.import("resource://gre/modules/PlacesUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", - "resource://gre/modules/PluralForm.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", - "resource://gre/modules/PrivateBrowsingUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", - "resource:///modules/RecentWindow.jsm"); - -// PlacesUtils exposes multiple symbols, so we can't use defineLazyModuleGetter. -Cu.import("resource://gre/modules/PlacesUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "PlacesTransactions", - "resource://gre/modules/PlacesTransactions.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "CloudSync", - "resource://gre/modules/CloudSync.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Weave", - "resource://services-sync/main.js"); - -const gInContentProcess = Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT; -const FAVICON_REQUEST_TIMEOUT = 60 * 1000; -// Map from windows to arrays of data about pending favicon loads. -let gFaviconLoadDataMap = new Map(); - -// copied from utilityOverlay.js -const TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab"; - -let InternalFaviconLoader = { - /** - * This gets called for every inner window that is destroyed. - * In the parent process, we process the destruction ourselves. In the child process, - * we notify the parent which will then process it based on that message. - */ - observe(subject, topic, data) { - let innerWindowID = subject.QueryInterface(Ci.nsISupportsPRUint64).data; - this.removeRequestsForInner(innerWindowID); - }, - - /** - * Actually cancel the request, and clear the timeout for cancelling it. - */ - _cancelRequest({uri, innerWindowID, timerID, callback}, reason) { - // Break cycle - let request = callback.request; - delete callback.request; - // Ensure we don't time out. - clearTimeout(timerID); - try { - request.cancel(); - } catch (ex) { - Cu.reportError("When cancelling a request for " + uri.spec + " because " + reason + ", it was already canceled!"); - } - }, - - /** - * Called for every inner that gets destroyed, only in the parent process. - */ - removeRequestsForInner(innerID) { - for (let [window, loadDataForWindow] of gFaviconLoadDataMap) { - let newLoadDataForWindow = loadDataForWindow.filter(loadData => { - let innerWasDestroyed = loadData.innerWindowID == innerID; - if (innerWasDestroyed) { - this._cancelRequest(loadData, "the inner window was destroyed or a new favicon was loaded for it"); - } - // Keep the items whose inner is still alive. - return !innerWasDestroyed; - }); - // Map iteration with for...of is safe against modification, so - // now just replace the old value: - gFaviconLoadDataMap.set(window, newLoadDataForWindow); - } - }, - - /** - * Called when a toplevel chrome window unloads. We use this to tidy up after ourselves, - * avoid leaks, and cancel any remaining requests. The last part should in theory be - * handled by the inner-window-destroyed handlers. We clean up just to be on the safe side. - */ - onUnload(win) { - let loadDataForWindow = gFaviconLoadDataMap.get(win); - if (loadDataForWindow) { - for (let loadData of loadDataForWindow) { - this._cancelRequest(loadData, "the chrome window went away"); - } - } - gFaviconLoadDataMap.delete(win); - }, - - /** - * Remove a particular favicon load's loading data from our map tracking - * load data per chrome window. - * - * @param win - * the chrome window in which we should look for this load - * @param filterData ({innerWindowID, uri, callback}) - * the data we should use to find this particular load to remove. - * - * @return the loadData object we removed, or null if we didn't find any. - */ - _removeLoadDataFromWindowMap(win, {innerWindowID, uri, callback}) { - let loadDataForWindow = gFaviconLoadDataMap.get(win); - if (loadDataForWindow) { - let itemIndex = loadDataForWindow.findIndex(loadData => { - return loadData.innerWindowID == innerWindowID && - loadData.uri.equals(uri) && - loadData.callback.request == callback.request; - }); - if (itemIndex != -1) { - let loadData = loadDataForWindow[itemIndex]; - loadDataForWindow.splice(itemIndex, 1); - return loadData; - } - } - return null; - }, - - /** - * Create a function to use as a nsIFaviconDataCallback, so we can remove cancelling - * information when the request succeeds. Note that right now there are some edge-cases, - * such as about: URIs with chrome:// favicons where the success callback is not invoked. - * This is OK: we will 'cancel' the request after the timeout (or when the window goes - * away) but that will be a no-op in such cases. - */ - _makeCompletionCallback(win, id) { - return { - onComplete(uri) { - let loadData = InternalFaviconLoader._removeLoadDataFromWindowMap(win, { - uri, - innerWindowID: id, - callback: this, - }); - if (loadData) { - clearTimeout(loadData.timerID); - } - delete this.request; - }, - }; - }, - - ensureInitialized() { - if (this._initialized) { - return; - } - this._initialized = true; - - Services.obs.addObserver(this, "inner-window-destroyed", false); - Services.ppmm.addMessageListener("Toolkit:inner-window-destroyed", msg => { - this.removeRequestsForInner(msg.data); - }); - }, - - loadFavicon(browser, principal, uri) { - this.ensureInitialized(); - let win = browser.ownerGlobal; - if (!gFaviconLoadDataMap.has(win)) { - gFaviconLoadDataMap.set(win, []); - let unloadHandler = event => { - let doc = event.target; - let eventWin = doc.defaultView; - if (eventWin == win) { - win.removeEventListener("unload", unloadHandler); - this.onUnload(win); - } - }; - win.addEventListener("unload", unloadHandler, true); - } - - let {innerWindowID, currentURI} = browser; - - // Immediately cancel any earlier requests - this.removeRequestsForInner(innerWindowID); - - // First we do the actual setAndFetch call: - let loadType = PrivateBrowsingUtils.isWindowPrivate(win) - ? PlacesUtils.favicons.FAVICON_LOAD_PRIVATE - : PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE; - let callback = this._makeCompletionCallback(win, innerWindowID); - let request = PlacesUtils.favicons.setAndFetchFaviconForPage(currentURI, uri, false, - loadType, callback, principal); - - // Now register the result so we can cancel it if/when necessary. - if (!request) { - // The favicon service can return with success but no-op (and leave request - // as null) if the icon is the same as the page (e.g. for images) or if it is - // the favicon for an error page. In this case, we do not need to do anything else. - return; - } - callback.request = request; - let loadData = {innerWindowID, uri, callback}; - loadData.timerID = setTimeout(() => { - this._cancelRequest(loadData, "it timed out"); - this._removeLoadDataFromWindowMap(win, loadData); - }, FAVICON_REQUEST_TIMEOUT); - let loadDataForWindow = gFaviconLoadDataMap.get(win); - loadDataForWindow.push(loadData); - }, -}; - -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", - - /** - * Makes a URI from a spec, and do fixup - * @param aSpec - * The string spec of the URI - * @return A URI object for the spec. - */ - createFixedURI: function PUIU_createFixedURI(aSpec) { - return URIFixup.createFixupURI(aSpec, Ci.nsIURIFixup.FIXUP_FLAG_NONE); - }, - - getFormattedString: function PUIU_getFormattedString(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 PUIU_getPluralString(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 PUIU_getString(key) { - return bundle.GetStringFromName(key); - }, - - get _copyableAnnotations() { - return [ - this.DESCRIPTION_ANNO, - this.LOAD_IN_SIDEBAR_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 PUIU__getURIItemCopyTransaction(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 annos = []; - if (aData.annos) { - annos = aData.annos.filter(function(aAnno) { - return this._copyableAnnotations.includes(aAnno.name); - }, this); - } - - // There's no need to copy the keyword since it's bound to the bookmark url. - return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(aData.uri), - aContainer, aIndex, aData.title, - null, 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.includes(aAnno.name); - }, 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 PUIU__getLivemarkCopyTransaction(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.includes(aAnno.name) - }, 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 PUIU__isLivemark(aItemId) - { - // Since this check may be done on each dragover event, it's worth maintaining - // a cache. - let self = PUIU__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. - * @return An object implementing nsITransaction that can perform - * the move/insert. - */ - makeTransaction: - function PUIU_makeTransaction(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); - 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); - 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); - default: - if (type == PlacesUtils.TYPE_X_MOZ_URL || - type == PlacesUtils.TYPE_UNICODE || - type == TAB_DROP_TYPE) { - let title = type != PlacesUtils.TYPE_UNICODE ? data.title - : data.uri; - return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(data.uri), - container, index, title); - } - } - return null; - }, - - /** - * ********* PlacesTransactions version of the function defined above ******** - * - * Constructs a Places Transaction for the drop or paste of a blob of data - * into a container. - * - * @param aData - * The unwrapped data blob of dropped or pasted data. - * @param aType - * The content type of the data. - * @param aNewParentGuid - * GUID of the container the data was dropped or pasted into. - * @param aIndex - * The index within the container the item was dropped or pasted at. - * @param aCopy - * The drag action was copy, so don't move folders or links. - * - * @return a Places Transaction that can be transacted for performing the - * move/insert command. - */ - getTransactionForData(aData, aType, aNewParentGuid, aIndex, aCopy) { - if (!this.SUPPORTED_FLAVORS.includes(aData.type)) - throw new Error(`Unsupported '${aData.type}' data type`); - - if ("itemGuid" in aData) { - if (!this.PLACES_FLAVORS.includes(aData.type)) - throw new Error(`itemGuid unexpectedly set on ${aData.type} data`); - - let info = { guid: aData.itemGuid - , newParentGuid: aNewParentGuid - , newIndex: aIndex }; - if (aCopy) { - info.excludingAnnotation = "Places/SmartBookmark"; - return PlacesTransactions.Copy(info); - } - return PlacesTransactions.Move(info); - } - - // Since it's cheap and harmless, we allow the paste of separators and - // bookmarks from builds that use legacy transactions (i.e. when itemGuid - // was not set on PLACES_FLAVORS data). Containers are a different story, - // and thus disallowed. - if (aData.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) - throw new Error("Can't copy a container from a legacy-transactions build"); - - if (aData.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) { - return PlacesTransactions.NewSeparator({ parentGuid: aNewParentGuid - , index: aIndex }); - } - - let title = aData.type != PlacesUtils.TYPE_UNICODE ? aData.title - : aData.uri; - return PlacesTransactions.NewBookmark({ uri: NetUtil.newURI(aData.uri) - , title - , parentGuid: aNewParentGuid - , index: aIndex }); - }, - - /** - * 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 PUIU_showBookmarkDialog(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.includes("folderPicker"); - // 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 PUIU__getTopBrowserWin() { - return RecentWindow.getMostRecentBrowserWindow(); - }, - - /** - * set and fetch a favicon. Can only be used from the parent process. - * @param browser {Browser} The XUL browser element for which we're fetching a favicon. - * @param principal {Principal} The loading principal to use for the fetch. - * @param uri {URI} The URI to fetch. - */ - loadFavicon(browser, principal, uri) { - if (gInContentProcess) { - throw new Error("Can't track loads from within the child process!"); - } - InternalFaviconLoader.loadFavicon(browser, principal, uri); - }, - - /** - * 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 PUIU_getViewForNode(aNode) { - let node = aNode; - - // The view for a 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 PUIU_markPageAsTyped(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 PUIU_markPageAsFollowedBookmark(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 PUIU_markPageAsFollowedLink(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 PUIU_checkURLSecurity(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 - * element. - * @param doc - * A DOM Document to get a description for - * @return A description string if a META element was discovered with a - * "description" or "httpequiv" attribute, empty string otherwise. - */ - getDescriptionFromDocument: function PUIU_getDescriptionFromDocument(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 - * @return the description of the given item, or an empty string if it is - * not set. - */ - getItemDescription: function PUIU_getItemDescription(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(aNode) { - let parentNode = aNode.parent; - if (!parentNode) { - // canUserRemove doesn't accept root nodes. - return false; - } - - // 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(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(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 PUIU__openTabset(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/array;1"]. - createInstance(Ci.nsIMutableArray); - args.appendElement(uriList, /* weak =*/ false); - 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 PUIU_openLiveMarkNodesInTabs(aNode, aEvent, aView) { - let window = aView.ownerWindow; - - PlacesUtils.livemarks.getLivemark({id: aNode.itemId}) - .then(aLivemark => { - let 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 PUIU_openContainerInTabs(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 PUIU_openURINodesInTabs(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])}); - } - 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 PUIU_openNodeWithEvent(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 PUIU_openNodeIn(aNode, aWhere, aView, aPrivate) { - let window = aView.ownerWindow; - this._openNodeIn(aNode, aWhere, window, aPrivate); - }, - - _openNodeIn: function PUIU_openNodeIn(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, { - allowPopups: aNode.uri.startsWith("javascript:"), - 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 PUIU_guessUrlSchemeForUI(aUrlString) { - return aUrlString.substr(0, aUrlString.indexOf(":")); - }, - - getBestTitle: function PUIU_getBestTitle(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("OtherBookmarksFolderTitle"), - 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 queryItems = as.getItemsWithAnnotation(this.ORGANIZER_QUERY_ANNO); - // While looping through queries we will also check for their validity. - let queriesCount = 0; - let corrupt = false; - for (let i = 0; i < queryItems.length; i++) { - let queryName = as.getItemAnnotation(queryItems[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 = queryItems[i]; - - if (!itemExists(query.itemId)) { - // Orphan annotation, bail out and create a new left pane root. - corrupt = true; - break; - } - - // Check that all queries have valid parents. - let parentId = bs.getFolderIdForItem(query.itemId); - if (!queryItems.includes(parentId) && parentId != leftPaneRoot) { - // The parent is not part of the left pane, bail out and create a new - // left pane root. - corrupt = true; - 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++; - } - - // Note: it's not enough to just check for queriesCount, since we may - // find an invalid query just after accounting for a sufficient number of - // valid ones. As well as we can't just rely on corrupt since we may find - // less valid queries than expected. - if (corrupt || 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. - queryItems.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 CB_create_query(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 CB_create_folder(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 CB_runBatched(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); - - 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 - * @return the name of the query, or empty string if not a left-pane query - */ - getLeftPaneQueryNameFromId: function PUIU_getLeftPaneQueryNameFromId(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] of Object.entries(this.leftPaneQueries)) { - if (aItemId == id) - queryName = name; - } - } - return queryName; - }, - - shouldShowTabsFromOtherComputersMenuitem() { - let weaveOK = Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED && - Weave.Svc.Prefs.get("firstSync", "") != "notReady"; - return weaveOK; - }, - - /** - * WARNING TO ADDON AUTHORS: DO NOT USE THIS METHOD. IT'S LIKELY TO BE REMOVED IN A - * FUTURE RELEASE. - * - * Checks if a place: href represents a folder shortcut. - * - * @param queryString - * the query string to check (a place: href) - * @return whether or not queryString represents a folder shortcut. - * @throws if queryString is malformed. - */ - isFolderShortcutQueryString(queryString) { - // Based on GetSimpleBookmarksQueryFolder in nsNavHistory.cpp. - - let queriesParam = { }, optionsParam = { }; - PlacesUtils.history.queryStringToQueries(queryString, - queriesParam, - { }, - optionsParam); - let queries = queries.value; - if (queries.length == 0) - throw new Error(`Invalid place: uri: ${queryString}`); - return queries.length == 1 && - queries[0].folderCount == 1 && - !queries[0].hasBeginTime && - !queries[0].hasEndTime && - !queries[0].hasDomain && - !queries[0].hasURI && - !queries[0].hasSearchTerms && - !queries[0].tags.length == 0 && - optionsParam.value.maxResults == 0; - }, - - /** - * WARNING TO ADDON AUTHORS: DO NOT USE THIS METHOD. IT"S LIKELY TO BE REMOVED IN A - * FUTURE RELEASE. - * - * Helpers for consumers of editBookmarkOverlay which don't have a node as their input. - * Given a partial node-like object, having at least the itemId property set, this - * method completes the rest of the properties necessary for initialising the edit - * overlay with it. - * - * @param aNodeLike - * an object having at least the itemId nsINavHistoryResultNode property set, - * along with any other properties available. - */ - completeNodeLikeObjectForItemId(aNodeLike) { - if (this.useAsyncTransactions) { - // When async-transactions are enabled, node-likes must have - // bookmarkGuid set, and we cannot set it synchronously. - throw new Error("completeNodeLikeObjectForItemId cannot be used when " + - "async transactions are enabled"); - } - if (!("itemId" in aNodeLike)) - throw new Error("itemId missing in aNodeLike"); - - let itemId = aNodeLike.itemId; - let defGetter = XPCOMUtils.defineLazyGetter.bind(XPCOMUtils, aNodeLike); - - if (!("title" in aNodeLike)) - defGetter("title", () => PlacesUtils.bookmarks.getItemTitle(itemId)); - - if (!("uri" in aNodeLike)) { - defGetter("uri", () => { - let uri = null; - try { - uri = PlacesUtils.bookmarks.getBookmarkURI(itemId); - } catch (ex) { } - return uri ? uri.spec : ""; - }); - } - - if (!("type" in aNodeLike)) { - defGetter("type", () => { - if (aNodeLike.uri.length > 0) { - if (/^place:/.test(aNodeLike.uri)) { - if (this.isFolderShortcutQueryString(aNodeLike.uri)) - return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT; - - return Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY; - } - - return Ci.nsINavHistoryResultNode.RESULT_TYPE_URI; - } - - let itemType = PlacesUtils.bookmarks.getItemType(itemId); - if (itemType == PlacesUtils.bookmarks.TYPE_FOLDER) - return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER; - - throw new Error("Unexpected item type"); - }); - } - }, - - /** - * Helpers for consumers of editBookmarkOverlay which don't have a node as their input. - * - * Given a bookmark object for either a url bookmark or a folder, returned by - * Bookmarks.fetch (see Bookmark.jsm), this creates a node-like object suitable for - * initialising the edit overlay with it. - * - * @param aFetchInfo - * a bookmark object returned by Bookmarks.fetch. - * @return a node-like object suitable for initialising editBookmarkOverlay. - * @throws if aFetchInfo is representing a separator. - */ - promiseNodeLikeFromFetchInfo: Task.async(function* (aFetchInfo) { - if (aFetchInfo.itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR) - throw new Error("promiseNodeLike doesn't support separators"); - - return Object.freeze({ - itemId: yield PlacesUtils.promiseItemId(aFetchInfo.guid), - bookmarkGuid: aFetchInfo.guid, - title: aFetchInfo.title, - uri: aFetchInfo.url !== undefined ? aFetchInfo.url.href : "", - - get type() { - if (aFetchInfo.itemType == PlacesUtils.bookmarks.TYPE_FOLDER) - return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER; - - if (this.uri.length == 0) - throw new Error("Unexpected item type"); - - if (/^place:/.test(this.uri)) { - if (this.isFolderShortcutQueryString(this.uri)) - return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT; - - return Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY; - } - - return Ci.nsINavHistoryResultNode.RESULT_TYPE_URI; - } - }); - }), - - /** - * Shortcut for calling promiseNodeLikeFromFetchInfo on the result of - * Bookmarks.fetch for the given guid/info object. - * - * @see promiseNodeLikeFromFetchInfo above and Bookmarks.fetch in Bookmarks.jsm. - */ - fetchNodeLike: Task.async(function* (aGuidOrInfo) { - let info = yield PlacesUtils.bookmarks.fetch(aGuidOrInfo); - if (!info) - return null; - return (yield this.promiseNodeLikeFromFetchInfo(info)); - }) -}; - - -PlacesUIUtils.PLACES_FLAVORS = [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER, - PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR, - PlacesUtils.TYPE_X_MOZ_PLACE]; - -PlacesUIUtils.URI_FLAVORS = [PlacesUtils.TYPE_X_MOZ_URL, - TAB_DROP_TYPE, - PlacesUtils.TYPE_UNICODE], - -PlacesUIUtils.SUPPORTED_FLAVORS = [...PlacesUIUtils.PLACES_FLAVORS, - ...PlacesUIUtils.URI_FLAVORS]; - -XPCOMUtils.defineLazyServiceGetter(PlacesUIUtils, "RDF", - "@mozilla.org/rdf/rdf-service;1", - "nsIRDFService"); - -XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ellipsis", function() { - return Services.prefs.getComplexValue("intl.ellipsis", - Ci.nsIPrefLocalizedString).data; -}); - -XPCOMUtils.defineLazyGetter(PlacesUIUtils, "useAsyncTransactions", function() { - try { - return Services.prefs.getBoolPref("browser.places.useAsyncTransactions"); - } catch (ex) { } - return false; -}); - -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); -}); - -/** - * 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: (aName, aTransactions) => - new PlacesAggregatedTransaction(aName, aTransactions), - - createFolder: (aName, aContainer, aIndex, aAnnotations, - aChildItemsTransactions) => - new PlacesCreateFolderTransaction(aName, aContainer, aIndex, aAnnotations, - aChildItemsTransactions), - - createItem: (aURI, aContainer, aIndex, aTitle, aKeyword, - aAnnotations, aChildTransactions) => - new PlacesCreateBookmarkTransaction(aURI, aContainer, aIndex, aTitle, - aKeyword, aAnnotations, - aChildTransactions), - - createSeparator: (aContainer, aIndex) => - new PlacesCreateSeparatorTransaction(aContainer, aIndex), - - createLivemark: (aFeedURI, aSiteURI, aName, aContainer, aIndex, - aAnnotations) => - new PlacesCreateLivemarkTransaction(aFeedURI, aSiteURI, aName, aContainer, - aIndex, aAnnotations), - - moveItem: (aItemId, aNewContainer, aNewIndex) => - new PlacesMoveItemTransaction(aItemId, aNewContainer, aNewIndex), - - removeItem: (aItemId) => - new PlacesRemoveItemTransaction(aItemId), - - editItemTitle: (aItemId, aNewTitle) => - new PlacesEditItemTitleTransaction(aItemId, aNewTitle), - - editBookmarkURI: (aItemId, aNewURI) => - new PlacesEditBookmarkURITransaction(aItemId, aNewURI), - - setItemAnnotation: (aItemId, aAnnotationObject) => - new PlacesSetItemAnnotationTransaction(aItemId, aAnnotationObject), - - setPageAnnotation: (aURI, aAnnotationObject) => - new PlacesSetPageAnnotationTransaction(aURI, aAnnotationObject), - - editBookmarkKeyword: (aItemId, aNewKeyword) => - new PlacesEditBookmarkKeywordTransaction(aItemId, aNewKeyword), - - editLivemarkSiteURI: (aLivemarkId, aSiteURI) => - new PlacesEditLivemarkSiteURITransaction(aLivemarkId, aSiteURI), - - editLivemarkFeedURI: (aLivemarkId, aFeedURI) => - new PlacesEditLivemarkFeedURITransaction(aLivemarkId, aFeedURI), - - editItemDateAdded: (aItemId, aNewDateAdded) => - new PlacesEditItemDateAddedTransaction(aItemId, aNewDateAdded), - - editItemLastModified: (aItemId, aNewLastModified) => - new PlacesEditItemLastModifiedTransaction(aItemId, aNewLastModified), - - sortFolderByName: (aFolderId) => - new PlacesSortFolderByNameTransaction(aFolderId), - - tagURI: (aURI, aTags) => - new PlacesTagURITransaction(aURI, aTags), - - untagURI: (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. - * @return nsITransaction object. - */ - setLoadInSidebar(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. - * @return nsITransaction object. - */ - editItemDescription(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: () => - PlacesUtils.transactionManager.beginBatch(null), - - endBatch: () => - PlacesUtils.transactionManager.endBatch(false), - - doTransaction: (txn) => - PlacesUtils.transactionManager.doTransaction(txn), - - undoTransaction: () => - PlacesUtils.transactionManager.undoTransaction(), - - redoTransaction: () => - PlacesUtils.transactionManager.redoTransaction(), - - get numberOfUndoItems() { - return PlacesUtils.transactionManager.numberOfUndoItems; - }, - get numberOfRedoItems() { - return PlacesUtils.transactionManager.numberOfRedoItems; - }, - get maxTransactionCount() { - return PlacesUtils.transactionManager.maxTransactionCount; - }, - set maxTransactionCount(val) { - PlacesUtils.transactionManager.maxTransactionCount = val; - }, - - clear: () => - PlacesUtils.transactionManager.clear(), - - peekUndoStack: () => - PlacesUtils.transactionManager.peekUndoStack(), - - peekRedoStack: () => - PlacesUtils.transactionManager.peekRedoStack(), - - getUndoStack: () => - PlacesUtils.transactionManager.getUndoStack(), - - getRedoStack: () => - PlacesUtils.transactionManager.getRedoStack(), - - AddListener: (aListener) => - PlacesUtils.transactionManager.AddListener(aListener), - - RemoveListener: (aListener) => - PlacesUtils.transactionManager.RemoveListener(aListener) - } -}); diff --git a/application/basilisk/components/places/content/bookmarkProperties.js b/application/basilisk/components/places/content/bookmarkProperties.js deleted file mode 100644 index fd37c04a4..000000000 --- a/application/basilisk/components/places/content/bookmarkProperties.js +++ /dev/null @@ -1,683 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ - -/** - * The panel is initialized based on data given in the js object passed - * as window.arguments[0]. The object must have the following fields set: - * @ action (String). Possible values: - * - "add" - for adding a new item. - * @ type (String). Possible values: - * - "bookmark" - * @ loadBookmarkInSidebar - optional, the default state for the - * "Load this bookmark in the sidebar" field. - * - "folder" - * @ URIList (Array of nsIURI objects) - optional, list of uris to - * be bookmarked under the new folder. - * - "livemark" - * @ uri (nsIURI object) - optional, the default uri for the new item. - * The property is not used for the "folder with items" type. - * @ title (String) - optional, the default title for the new item. - * @ description (String) - optional, the default description for the new - * item. - * @ defaultInsertionPoint (InsertionPoint JS object) - optional, the - * default insertion point for the new item. - * @ keyword (String) - optional, the default keyword for the new item. - * @ postData (String) - optional, POST data to accompany the keyword. - * @ charSet (String) - optional, character-set to accompany the keyword. - * Notes: - * 1) If |uri| is set for a bookmark/livemark item and |title| isn't, - * the dialog will query the history tables for the title associated - * with the given uri. If the dialog is set to adding a folder with - * bookmark items under it (see URIList), a default static title is - * used ("[Folder Name]"). - * 2) The index field of the default insertion point is ignored if - * the folder picker is shown. - * - "edit" - for editing a bookmark item or a folder. - * @ type (String). Possible values: - * - "bookmark" - * @ node (an nsINavHistoryResultNode object) - a node representing - * the bookmark. - * - "folder" (also applies to livemarks) - * @ node (an nsINavHistoryResultNode object) - a node representing - * the folder. - * @ hiddenRows (Strings array) - optional, list of rows to be hidden - * regardless of the item edited or added by the dialog. - * Possible values: - * - "title" - * - "location" - * - "description" - * - "keyword" - * - "tags" - * - "loadInSidebar" - * - "folderPicker" - hides both the tree and the menu. - * - * window.arguments[0].performed is set to true if any transaction has - * been performed by the dialog. - */ - -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", - "resource://gre/modules/PrivateBrowsingUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils", - "resource://gre/modules/PromiseUtils.jsm"); - -const BOOKMARK_ITEM = 0; -const BOOKMARK_FOLDER = 1; -const LIVEMARK_CONTAINER = 2; - -const ACTION_EDIT = 0; -const ACTION_ADD = 1; - -var elementsHeight = new Map(); - -var BookmarkPropertiesPanel = { - - /** UI Text Strings */ - __strings: null, - get _strings() { - if (!this.__strings) { - this.__strings = document.getElementById("stringBundle"); - } - return this.__strings; - }, - - _action: null, - _itemType: null, - _itemId: -1, - _uri: null, - _loadInSidebar: false, - _title: "", - _description: "", - _URIs: [], - _keyword: "", - _postData: null, - _charSet: "", - _feedURI: null, - _siteURI: null, - - _defaultInsertionPoint: null, - _hiddenRows: [], - _batching: false, - - /** - * This method returns the correct label for the dialog's "accept" - * button based on the variant of the dialog. - */ - _getAcceptLabel: function BPP__getAcceptLabel() { - if (this._action == ACTION_ADD) { - if (this._URIs.length) - return this._strings.getString("dialogAcceptLabelAddMulti"); - - if (this._itemType == LIVEMARK_CONTAINER) - return this._strings.getString("dialogAcceptLabelAddLivemark"); - - if (this._dummyItem || this._loadInSidebar) - return this._strings.getString("dialogAcceptLabelAddItem"); - - return this._strings.getString("dialogAcceptLabelSaveItem"); - } - return this._strings.getString("dialogAcceptLabelEdit"); - }, - - /** - * This method returns the correct title for the current variant - * of this dialog. - */ - _getDialogTitle: function BPP__getDialogTitle() { - if (this._action == ACTION_ADD) { - if (this._itemType == BOOKMARK_ITEM) - return this._strings.getString("dialogTitleAddBookmark"); - if (this._itemType == LIVEMARK_CONTAINER) - return this._strings.getString("dialogTitleAddLivemark"); - - // add folder - NS_ASSERT(this._itemType == BOOKMARK_FOLDER, "Unknown item type"); - if (this._URIs.length) - return this._strings.getString("dialogTitleAddMulti"); - - return this._strings.getString("dialogTitleAddFolder"); - } - if (this._action == ACTION_EDIT) { - return this._strings.getFormattedString("dialogTitleEdit", [this._title]); - } - return ""; - }, - - /** - * Determines the initial data for the item edited or added by this dialog - */ - _determineItemInfo() { - let dialogInfo = window.arguments[0]; - this._action = dialogInfo.action == "add" ? ACTION_ADD : ACTION_EDIT; - this._hiddenRows = dialogInfo.hiddenRows ? dialogInfo.hiddenRows : []; - if (this._action == ACTION_ADD) { - NS_ASSERT("type" in dialogInfo, "missing type property for add action"); - - if ("title" in dialogInfo) - this._title = dialogInfo.title; - - if ("defaultInsertionPoint" in dialogInfo) { - this._defaultInsertionPoint = dialogInfo.defaultInsertionPoint; - } else { - this._defaultInsertionPoint = - new InsertionPoint(PlacesUtils.bookmarksMenuFolderId, - PlacesUtils.bookmarks.DEFAULT_INDEX, - Ci.nsITreeView.DROP_ON); - } - - switch (dialogInfo.type) { - case "bookmark": - this._itemType = BOOKMARK_ITEM; - if ("uri" in dialogInfo) { - NS_ASSERT(dialogInfo.uri instanceof Ci.nsIURI, - "uri property should be a uri object"); - this._uri = dialogInfo.uri; - if (typeof(this._title) != "string") { - this._title = this._getURITitleFromHistory(this._uri) || - this._uri.spec; - } - } else { - this._uri = PlacesUtils._uri("about:blank"); - this._title = this._strings.getString("newBookmarkDefault"); - this._dummyItem = true; - } - - if ("loadBookmarkInSidebar" in dialogInfo) - this._loadInSidebar = dialogInfo.loadBookmarkInSidebar; - - if ("keyword" in dialogInfo) { - this._keyword = dialogInfo.keyword; - this._isAddKeywordDialog = true; - if ("postData" in dialogInfo) - this._postData = dialogInfo.postData; - if ("charSet" in dialogInfo) - this._charSet = dialogInfo.charSet; - } - break; - - case "folder": - this._itemType = BOOKMARK_FOLDER; - if (!this._title) { - if ("URIList" in dialogInfo) { - this._title = this._strings.getString("bookmarkAllTabsDefault"); - this._URIs = dialogInfo.URIList; - } else - this._title = this._strings.getString("newFolderDefault"); - this._dummyItem = true; - } - break; - - case "livemark": - this._itemType = LIVEMARK_CONTAINER; - if ("feedURI" in dialogInfo) - this._feedURI = dialogInfo.feedURI; - if ("siteURI" in dialogInfo) - this._siteURI = dialogInfo.siteURI; - - if (!this._title) { - if (this._feedURI) { - this._title = this._getURITitleFromHistory(this._feedURI) || - this._feedURI.spec; - } else - this._title = this._strings.getString("newLivemarkDefault"); - } - } - - if ("description" in dialogInfo) - this._description = dialogInfo.description; - } else { // edit - this._node = dialogInfo.node; - this._title = this._node.title; - if (PlacesUtils.nodeIsFolder(this._node)) - this._itemType = BOOKMARK_FOLDER; - else if (PlacesUtils.nodeIsURI(this._node)) - this._itemType = BOOKMARK_ITEM; - } - }, - - /** - * This method returns the title string corresponding to a given URI. - * If none is available from the bookmark service (probably because - * the given URI doesn't appear in bookmarks or history), we synthesize - * a title from the first 100 characters of the URI. - * - * @param aURI - * nsIURI object for which we want the title - * - * @returns a title string - */ - _getURITitleFromHistory: function BPP__getURITitleFromHistory(aURI) { - NS_ASSERT(aURI instanceof Ci.nsIURI); - - // get the title from History - return PlacesUtils.history.getPageTitle(aURI); - }, - - /** - * This method should be called by the onload of the Bookmark Properties - * dialog to initialize the state of the panel. - */ - onDialogLoad: Task.async(function* () { - this._determineItemInfo(); - - document.title = this._getDialogTitle(); - var acceptButton = document.documentElement.getButton("accept"); - acceptButton.label = this._getAcceptLabel(); - - // Do not use sizeToContent, otherwise, due to bug 90276, the dialog will - // grow at every opening. - // Since elements can be uncollapsed asynchronously, we must observe their - // mutations and resize the dialog using a cached element size. - this._height = window.outerHeight; - this._mutationObserver = new MutationObserver(mutations => { - for (let mutation of mutations) { - let target = mutation.target; - let id = target.id; - if (!/^editBMPanel_.*(Row|Checkbox)$/.test(id)) - continue; - - let collapsed = target.getAttribute("collapsed") === "true"; - let wasCollapsed = mutation.oldValue === "true"; - if (collapsed == wasCollapsed) - continue; - - if (collapsed) { - this._height -= elementsHeight.get(id); - elementsHeight.delete(id); - } else { - elementsHeight.set(id, target.boxObject.height); - this._height += elementsHeight.get(id); - } - window.resizeTo(window.outerWidth, this._height); - } - }); - - this._mutationObserver.observe(document, - { subtree: true, - attributeOldValue: true, - attributeFilter: ["collapsed"] }); - - // Some controls are flexible and we want to update their cached size when - // the dialog is resized. - window.addEventListener("resize", this); - - this._beginBatch(); - - switch (this._action) { - case ACTION_EDIT: - gEditItemOverlay.initPanel({ node: this._node - , hiddenRows: this._hiddenRows - , focusedElement: "first" }); - acceptButton.disabled = gEditItemOverlay.readOnly; - break; - case ACTION_ADD: - this._node = yield this._promiseNewItem(); - // Edit the new item - gEditItemOverlay.initPanel({ node: this._node - , hiddenRows: this._hiddenRows - , postData: this._postData - , focusedElement: "first" }); - - // Empty location field if the uri is about:blank, this way inserting a new - // url will be easier for the user, Accept button will be automatically - // disabled by the input listener until the user fills the field. - let locationField = this._element("locationField"); - if (locationField.value == "about:blank") - locationField.value = ""; - - // if this is an uri related dialog disable accept button until - // the user fills an uri value. - if (this._itemType == BOOKMARK_ITEM) - acceptButton.disabled = !this._inputIsValid(); - break; - } - - if (!gEditItemOverlay.readOnly) { - // Listen on uri fields to enable accept button if input is valid - if (this._itemType == BOOKMARK_ITEM) { - this._element("locationField") - .addEventListener("input", this); - if (this._isAddKeywordDialog) { - this._element("keywordField") - .addEventListener("input", this); - } - } - } - }), - - // nsIDOMEventListener - handleEvent: function BPP_handleEvent(aEvent) { - var target = aEvent.target; - switch (aEvent.type) { - case "input": - if (target.id == "editBMPanel_locationField" || - target.id == "editBMPanel_keywordField") { - // Check uri fields to enable accept button if input is valid - document.documentElement - .getButton("accept").disabled = !this._inputIsValid(); - } - break; - case "resize": - for (let [id, oldHeight] of elementsHeight) { - let newHeight = document.getElementById(id).boxObject.height; - this._height += -oldHeight + newHeight; - elementsHeight.set(id, newHeight); - } - break; - } - }, - - // Hack for implementing batched-Undo around the editBookmarkOverlay - // instant-apply code. For all the details see the comment above beginBatch - // in browser-places.js - _batchBlockingDeferred: null, - _beginBatch() { - if (this._batching) - return; - if (PlacesUIUtils.useAsyncTransactions) { - this._batchBlockingDeferred = PromiseUtils.defer(); - PlacesTransactions.batch(function* () { - yield this._batchBlockingDeferred.promise; - }.bind(this)); - } else { - PlacesUtils.transactionManager.beginBatch(null); - } - this._batching = true; - }, - - _endBatch() { - if (!this._batching) - return; - - if (PlacesUIUtils.useAsyncTransactions) { - this._batchBlockingDeferred.resolve(); - this._batchBlockingDeferred = null; - } else { - PlacesUtils.transactionManager.endBatch(false); - } - this._batching = false; - }, - - // nsISupports - QueryInterface: function BPP_QueryInterface(aIID) { - if (aIID.equals(Ci.nsIDOMEventListener) || - aIID.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_NOINTERFACE; - }, - - _element: function BPP__element(aID) { - return document.getElementById("editBMPanel_" + aID); - }, - - onDialogUnload() { - // gEditItemOverlay does not exist anymore here, so don't rely on it. - this._mutationObserver.disconnect(); - delete this._mutationObserver; - - window.removeEventListener("resize", this); - - // Calling removeEventListener with arguments which do not identify any - // currently registered EventListener on the EventTarget has no effect. - this._element("locationField") - .removeEventListener("input", this); - }, - - onDialogAccept() { - // We must blur current focused element to save its changes correctly - document.commandDispatcher.focusedElement.blur(); - // The order here is important! We have to uninit the panel first, otherwise - // late changes could force it to commit more transactions. - gEditItemOverlay.uninitPanel(true); - this._endBatch(); - window.arguments[0].performed = true; - }, - - onDialogCancel() { - // The order here is important! We have to uninit the panel first, otherwise - // changes done as part of Undo may change the panel contents and by - // that force it to commit more transactions. - gEditItemOverlay.uninitPanel(true); - this._endBatch(); - if (PlacesUIUtils.useAsyncTransactions) - PlacesTransactions.undo().catch(Components.utils.reportError); - else - PlacesUtils.transactionManager.undoTransaction(); - window.arguments[0].performed = false; - }, - - /** - * This method checks to see if the input fields are in a valid state. - * - * @returns true if the input is valid, false otherwise - */ - _inputIsValid: function BPP__inputIsValid() { - if (this._itemType == BOOKMARK_ITEM && - !this._containsValidURI("locationField")) - return false; - if (this._isAddKeywordDialog && !this._element("keywordField").value.length) - return false; - - return true; - }, - - /** - * Determines whether the XUL textbox with the given ID contains a - * string that can be converted into an nsIURI. - * - * @param aTextboxID - * the ID of the textbox element whose contents we'll test - * - * @returns true if the textbox contains a valid URI string, false otherwise - */ - _containsValidURI: function BPP__containsValidURI(aTextboxID) { - try { - var value = this._element(aTextboxID).value; - if (value) { - PlacesUIUtils.createFixedURI(value); - return true; - } - } catch (e) { } - return false; - }, - - /** - * [New Item Mode] Get the insertion point details for the new item, given - * dialog state and opening arguments. - * - * The container-identifier and insertion-index are returned separately in - * the form of [containerIdentifier, insertionIndex] - */ - _getInsertionPointDetails: function BPP__getInsertionPointDetails() { - var containerId = this._defaultInsertionPoint.itemId; - var indexInContainer = this._defaultInsertionPoint.index; - - return [containerId, indexInContainer]; - }, - - /** - * Returns a transaction for creating a new bookmark item representing the - * various fields and opening arguments of the dialog. - */ - _getCreateNewBookmarkTransaction: - function BPP__getCreateNewBookmarkTransaction(aContainer, aIndex) { - var annotations = []; - var childTransactions = []; - - if (this._description) { - let annoObj = { name : PlacesUIUtils.DESCRIPTION_ANNO, - type : Ci.nsIAnnotationService.TYPE_STRING, - flags : 0, - value : this._description, - expires: Ci.nsIAnnotationService.EXPIRE_NEVER }; - let editItemTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj); - childTransactions.push(editItemTxn); - } - - if (this._loadInSidebar) { - let annoObj = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO, - value : true }; - let setLoadTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj); - childTransactions.push(setLoadTxn); - } - - // XXX TODO: this should be in a transaction! - if (this._charSet && !PrivateBrowsingUtils.isWindowPrivate(window)) - PlacesUtils.setCharsetForURI(this._uri, this._charSet); - - let createTxn = new PlacesCreateBookmarkTransaction(this._uri, - aContainer, - aIndex, - this._title, - this._keyword, - annotations, - childTransactions, - this._postData); - - return new PlacesAggregatedTransaction(this._getDialogTitle(), - [createTxn]); - }, - - /** - * Returns a childItems-transactions array representing the URIList with - * which the dialog has been opened. - */ - _getTransactionsForURIList: function BPP__getTransactionsForURIList() { - var transactions = []; - for (let uri of this._URIs) { - // uri should be an object in the form { url, title }. Though add-ons - // could still use the legacy form, where it's an nsIURI. - let [_uri, _title] = uri instanceof Ci.nsIURI ? - [uri, this._getURITitleFromHistory(uri)] : [uri.uri, uri.title]; - - let createTxn = - new PlacesCreateBookmarkTransaction(_uri, -1, - PlacesUtils.bookmarks.DEFAULT_INDEX, - _title); - transactions.push(createTxn); - } - return transactions; - }, - - /** - * Returns a transaction for creating a new folder item representing the - * various fields and opening arguments of the dialog. - */ - _getCreateNewFolderTransaction: - function BPP__getCreateNewFolderTransaction(aContainer, aIndex) { - var annotations = []; - var childItemsTransactions; - if (this._URIs.length) - childItemsTransactions = this._getTransactionsForURIList(); - - if (this._description) - annotations.push(this._getDescriptionAnnotation(this._description)); - - return new PlacesCreateFolderTransaction(this._title, aContainer, - aIndex, annotations, - childItemsTransactions); - }, - - _createNewItem: Task.async(function* () { - let [container, index] = this._getInsertionPointDetails(); - let txn; - switch (this._itemType) { - case BOOKMARK_FOLDER: - txn = this._getCreateNewFolderTransaction(container, index); - break; - case LIVEMARK_CONTAINER: - txn = new PlacesCreateLivemarkTransaction(this._feedURI, this._siteURI, - this._title, container, index); - break; - default: // BOOKMARK_ITEM - txn = this._getCreateNewBookmarkTransaction(container, index); - } - - PlacesUtils.transactionManager.doTransaction(txn); - // This is a temporary hack until we use PlacesTransactions.jsm - if (txn._promise) { - yield txn._promise; - } - - let folderGuid = yield PlacesUtils.promiseItemGuid(container); - let bm = yield PlacesUtils.bookmarks.fetch({ - parentGuid: folderGuid, - index - }); - this._itemId = yield PlacesUtils.promiseItemId(bm.guid); - - return Object.freeze({ - itemId: this._itemId, - bookmarkGuid: bm.guid, - title: this._title, - uri: this._uri ? this._uri.spec : "", - type: this._itemType == BOOKMARK_ITEM ? - Ci.nsINavHistoryResultNode.RESULT_TYPE_URI : - Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER - }); - }), - - _promiseNewItem: Task.async(function* () { - if (!PlacesUIUtils.useAsyncTransactions) - return this._createNewItem(); - - let [containerId, index] = this._getInsertionPointDetails(); - let parentGuid = yield PlacesUtils.promiseItemGuid(containerId); - let annotations = []; - if (this._description) { - annotations.push({ name: PlacesUIUtils.DESCRIPTION_ANNO - , value: this._description }); - } - if (this._loadInSidebar) { - annotations.push({ name: PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO - , value: true }); - } - - let itemGuid; - let info = { parentGuid, index, title: this._title, annotations }; - if (this._itemType == BOOKMARK_ITEM) { - info.url = this._uri; - if (this._keyword) - info.keyword = this._keyword; - if (this._postData) - info.postData = this._postData; - - if (this._charSet && !PrivateBrowsingUtils.isWindowPrivate(window)) - PlacesUtils.setCharsetForURI(this._uri, this._charSet); - - itemGuid = yield PlacesTransactions.NewBookmark(info).transact(); - } else if (this._itemType == LIVEMARK_CONTAINER) { - info.feedUrl = this._feedURI; - if (this._siteURI) - info.siteUrl = this._siteURI; - - itemGuid = yield PlacesTransactions.NewLivemark(info).transact(); - } else if (this._itemType == BOOKMARK_FOLDER) { - itemGuid = yield PlacesTransactions.NewFolder(info).transact(); - for (let uri of this._URIs) { - let placeInfo = yield PlacesUtils.promisePlaceInfo(uri); - let title = placeInfo ? placeInfo.title : ""; - yield PlacesTransactions.transact({ parentGuid: itemGuid, uri, title }); - } - } else { - throw new Error(`unexpected value for _itemType: ${this._itemType}`); - } - - this._itemGuid = itemGuid; - this._itemId = yield PlacesUtils.promiseItemId(itemGuid); - return Object.freeze({ - itemId: this._itemId, - bookmarkGuid: this._itemGuid, - title: this._title, - uri: this._uri ? this._uri.spec : "", - type: this._itemType == BOOKMARK_ITEM ? - Ci.nsINavHistoryResultNode.RESULT_TYPE_URI : - Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER - }); - }) -}; diff --git a/application/basilisk/components/places/content/bookmarkProperties.xul b/application/basilisk/components/places/content/bookmarkProperties.xul deleted file mode 100644 index 2c04f8b05..000000000 --- a/application/basilisk/components/places/content/bookmarkProperties.xul +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - %editBookmarkOverlayDTD; -]> - - - - - - - - - - - - - - - - - - - - - - - diff --git a/application/basilisk/components/places/content/editBookmarkOverlay.js b/application/basilisk/components/places/content/editBookmarkOverlay.js deleted file mode 100644 index 4fc0b7893..000000000 --- a/application/basilisk/components/places/content/editBookmarkOverlay.js +++ /dev/null @@ -1,1148 +0,0 @@ -/* 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/. */ - -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); - -const LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed"; -const MAX_FOLDER_ITEM_IN_MENU_LIST = 5; - -var gEditItemOverlay = { - _observersAdded: false, - _staticFoldersListBuilt: false, - - _paneInfo: null, - _setPaneInfo(aInitInfo) { - if (!aInitInfo) - return this._paneInfo = null; - - if ("uris" in aInitInfo && "node" in aInitInfo) - throw new Error("ambiguous pane info"); - if (!("uris" in aInitInfo) && !("node" in aInitInfo)) - throw new Error("Neither node nor uris set for pane info"); - - let node = "node" in aInitInfo ? aInitInfo.node : null; - - // Since there's no true UI for folder shortcuts (they show up just as their target - // folders), when the pane shows for them it's opened in read-only mode, showing the - // properties of the target folder. - let itemId = node ? node.itemId : -1; - let itemGuid = PlacesUIUtils.useAsyncTransactions && node ? - PlacesUtils.getConcreteItemGuid(node) : null; - let isItem = itemId != -1; - let isFolderShortcut = isItem && - node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT; - let isTag = node && PlacesUtils.nodeIsTagQuery(node); - if (isTag) { - itemId = PlacesUtils.getConcreteItemId(node); - // For now we don't have access to the item guid synchronously for tags, - // so we'll need to fetch it later. - } - let isURI = node && PlacesUtils.nodeIsURI(node); - let uri = isURI ? NetUtil.newURI(node.uri) : null; - let title = node ? node.title : null; - let isBookmark = isItem && isURI; - let bulkTagging = !node; - let uris = bulkTagging ? aInitInfo.uris : null; - let visibleRows = new Set(); - let isParentReadOnly = false; - let postData = aInitInfo.postData; - if (node && "parent" in node) { - let parent = node.parent; - if (parent) { - isParentReadOnly = !PlacesUtils.nodeIsFolder(parent) || - PlacesUIUtils.isContentsReadOnly(parent); - } - } - let focusedElement = aInitInfo.focusedElement; - - return this._paneInfo = { itemId, itemGuid, isItem, - isURI, uri, title, - isBookmark, isFolderShortcut, isParentReadOnly, - bulkTagging, uris, - visibleRows, postData, isTag, focusedElement }; - }, - - get initialized() { - return this._paneInfo != null; - }, - - // Backwards-compatibility getters - get itemId() { - if (!this.initialized || this._paneInfo.bulkTagging) - return -1; - return this._paneInfo.itemId; - }, - - get uri() { - if (!this.initialized) - return null; - if (this._paneInfo.bulkTagging) - return this._paneInfo.uris[0]; - return this._paneInfo.uri; - }, - - get multiEdit() { - return this.initialized && this._paneInfo.bulkTagging; - }, - - // Check if the pane is initialized to show only read-only fields. - get readOnly() { - // TODO (Bug 1120314): Folder shortcuts are currently read-only due to some - // quirky implementation details (the most important being the "smart" - // semantics of node.title that makes hard to edit the right entry). - // This pane is read-only if: - // * the panel is not initialized - // * the node is a folder shortcut - // * the node is not bookmarked and not a tag container - // * the node is child of a read-only container and is not a bookmarked - // URI nor a tag container - return !this.initialized || - this._paneInfo.isFolderShortcut || - (!this._paneInfo.isItem && !this._paneInfo.isTag) || - (this._paneInfo.isParentReadOnly && !this._paneInfo.isBookmark && !this._paneInfo.isTag); - }, - - // the first field which was edited after this panel was initialized for - // a certain item - _firstEditedField: "", - - _initNamePicker() { - if (this._paneInfo.bulkTagging) - throw new Error("_initNamePicker called unexpectedly"); - - // title may by null, which, for us, is the same as an empty string. - this._initTextField(this._namePicker, this._paneInfo.title || ""); - }, - - _initLocationField() { - if (!this._paneInfo.isURI) - throw new Error("_initLocationField called unexpectedly"); - this._initTextField(this._locationField, this._paneInfo.uri.spec); - }, - - _initDescriptionField() { - if (!this._paneInfo.isItem) - throw new Error("_initDescriptionField called unexpectedly"); - - this._initTextField(this._descriptionField, - PlacesUIUtils.getItemDescription(this._paneInfo.itemId)); - }, - - _initKeywordField: Task.async(function* (newKeyword = "") { - if (!this._paneInfo.isBookmark) { - throw new Error("_initKeywordField called unexpectedly"); - } - - if (!newKeyword) { - let entries = []; - yield PlacesUtils.keywords.fetch({ url: this._paneInfo.uri.spec }, - e => entries.push(e)); - if (entries.length > 0) { - // We show an existing keyword if either POST data was not provided, or - // if the POST data is the same. - let existingKeyword = entries[0].keyword; - let postData = this._paneInfo.postData; - if (postData) { - let sameEntry = entries.find(e => e.postData === postData); - existingKeyword = sameEntry ? sameEntry.keyword : ""; - } - if (existingKeyword) { - this._keyword = newKeyword = existingKeyword; - } - } - } - this._initTextField(this._keywordField, newKeyword); - }), - - _initLoadInSidebar: Task.async(function* () { - if (!this._paneInfo.isBookmark) - throw new Error("_initLoadInSidebar called unexpectedly"); - - this._loadInSidebarCheckbox.checked = - PlacesUtils.annotations.itemHasAnnotation( - this._paneInfo.itemId, PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO); - }), - - /** - * Initialize the panel. - * - * @param aInfo - * An object having: - * 1. one of the following properties: - * - node: either a result node or a node-like object representing the - * item to be edited. A node-like object must have the following - * properties (with values that match exactly those a result node - * would have): itemId, bookmarkGuid, uri, title, type. - * - uris: an array of uris for bulk tagging. - * - * 2. any of the following optional properties: - * - hiddenRows (Strings array): list of rows to be hidden regardless - * of the item edited. Possible values: "title", "location", - * "description", "keyword", "loadInSidebar", "feedLocation", - * "siteLocation", folderPicker" - */ - initPanel(aInfo) { - if (typeof(aInfo) != "object" || aInfo === null) - throw new Error("aInfo must be an object."); - if ("node" in aInfo) { - try { - aInfo.node.type; - } catch (e) { - // If the lazy loader for |type| generates an exception, it means that - // this bookmark could not be loaded. This sometimes happens when tests - // create a bookmark by clicking the bookmark star, then try to cleanup - // before the bookmark panel has finished opening. Either way, if we - // cannot retrieve the bookmark information, we cannot open the panel. - return; - } - } - - // For sanity ensure that the implementer has uninited the panel before - // trying to init it again, or we could end up leaking due to observers. - if (this.initialized) - this.uninitPanel(false); - - let { itemId, isItem, isURI, - isBookmark, bulkTagging, uris, - visibleRows, focusedElement } = this._setPaneInfo(aInfo); - - let showOrCollapse = - (rowId, isAppropriateForInput, nameInHiddenRows = null) => { - let visible = isAppropriateForInput; - if (visible && "hiddenRows" in aInfo && nameInHiddenRows) - visible &= aInfo.hiddenRows.indexOf(nameInHiddenRows) == -1; - if (visible) - visibleRows.add(rowId); - return !(this._element(rowId).collapsed = !visible); - }; - - if (showOrCollapse("nameRow", !bulkTagging, "name")) { - this._initNamePicker(); - this._namePicker.readOnly = this.readOnly; - } - - // In some cases we want to hide the location field, since it's not - // human-readable, but we still want to initialize it. - showOrCollapse("locationRow", isURI, "location"); - if (isURI) { - this._initLocationField(); - this._locationField.readOnly = this.readOnly; - } - - // hide the description field for - if (showOrCollapse("descriptionRow", isItem && !this.readOnly, - "description")) { - this._initDescriptionField(); - this._descriptionField.readOnly = this.readOnly; - } - - if (showOrCollapse("keywordRow", isBookmark, "keyword")) { - this._initKeywordField().catch(Components.utils.reportError); - this._keywordField.readOnly = this.readOnly; - } - - // Collapse the tag selector if the item does not accept tags. - if (showOrCollapse("tagsRow", isURI || bulkTagging, "tags")) - this._initTagsField().catch(Components.utils.reportError); - else if (!this._element("tagsSelectorRow").collapsed) - this.toggleTagsSelector().catch(Components.utils.reportError); - - // Load in sidebar. - if (showOrCollapse("loadInSidebarCheckbox", isBookmark, "loadInSidebar")) { - this._initLoadInSidebar(); - } - - // Folder picker. - // Technically we should check that the item is not moveable, but that's - // not cheap (we don't always have the parent), and there's no use case for - // this (it's only the Star UI that shows the folderPicker) - if (showOrCollapse("folderRow", isItem, "folderPicker")) { - let containerId = PlacesUtils.bookmarks.getFolderIdForItem(itemId); - this._initFolderMenuList(containerId); - } - - // Selection count. - if (showOrCollapse("selectionCount", bulkTagging)) { - this._element("itemsCountText").value = - PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel", - uris.length, - [uris.length]); - } - - // Observe changes. - if (!this._observersAdded) { - PlacesUtils.bookmarks.addObserver(this, false); - window.addEventListener("unload", this); - this._observersAdded = true; - } - - // The focusedElement possible values are: - // * preferred: focus the field that the user touched first the last - // time the pane was shown (either namePicker or tagsField) - // * first: focus the first non collapsed textbox - // Note: since all controls are collapsed by default, we don't get the - // default XUL dialog behavior, that selects the first control, so we set - // the focus explicitly. - let elt; - if (focusedElement === "preferred") { - elt = this._element(gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField")); - } else if (focusedElement === "first") { - elt = document.querySelector("textbox:not([collapsed=true])"); - } - if (elt) { - elt.focus(); - elt.select(); - } - }, - - /** - * Finds tags that are in common among this._currentInfo.uris; - */ - _getCommonTags() { - if ("_cachedCommonTags" in this._paneInfo) - return this._paneInfo._cachedCommonTags; - - let uris = [...this._paneInfo.uris]; - let firstURI = uris.shift(); - let commonTags = new Set(PlacesUtils.tagging.getTagsForURI(firstURI)); - if (commonTags.size == 0) - return this._cachedCommonTags = []; - - for (let uri of uris) { - let curentURITags = PlacesUtils.tagging.getTagsForURI(uri); - for (let tag of commonTags) { - if (!curentURITags.includes(tag)) { - commonTags.delete(tag) - if (commonTags.size == 0) - return this._paneInfo.cachedCommonTags = []; - } - } - } - return this._paneInfo._cachedCommonTags = [...commonTags]; - }, - - _initTextField(aElement, aValue) { - if (aElement.value != aValue) { - aElement.value = aValue; - - // Clear the undo stack - let editor = aElement.editor; - if (editor) - editor.transactionManager.clear(); - } - }, - - /** - * Appends a menu-item representing a bookmarks folder to a menu-popup. - * @param aMenupopup - * The popup to which the menu-item should be added. - * @param aFolderId - * The identifier of the bookmarks folder. - * @return the new menu item. - */ - _appendFolderItemToMenupopup(aMenupopup, aFolderId) { - // First make sure the folders-separator is visible - this._element("foldersSeparator").hidden = false; - - var folderMenuItem = document.createElement("menuitem"); - var folderTitle = PlacesUtils.bookmarks.getItemTitle(aFolderId) - folderMenuItem.folderId = aFolderId; - folderMenuItem.setAttribute("label", folderTitle); - folderMenuItem.className = "menuitem-iconic folder-icon"; - aMenupopup.appendChild(folderMenuItem); - return folderMenuItem; - }, - - _initFolderMenuList: function EIO__initFolderMenuList(aSelectedFolder) { - // clean up first - var menupopup = this._folderMenuList.menupopup; - while (menupopup.childNodes.length > 6) - menupopup.removeChild(menupopup.lastChild); - - const bms = PlacesUtils.bookmarks; - const annos = PlacesUtils.annotations; - - // Build the static list - var unfiledItem = this._element("unfiledRootItem"); - if (!this._staticFoldersListBuilt) { - unfiledItem.label = bms.getItemTitle(PlacesUtils.unfiledBookmarksFolderId); - unfiledItem.folderId = PlacesUtils.unfiledBookmarksFolderId; - var bmMenuItem = this._element("bmRootItem"); - bmMenuItem.label = bms.getItemTitle(PlacesUtils.bookmarksMenuFolderId); - bmMenuItem.folderId = PlacesUtils.bookmarksMenuFolderId; - var toolbarItem = this._element("toolbarFolderItem"); - toolbarItem.label = bms.getItemTitle(PlacesUtils.toolbarFolderId); - toolbarItem.folderId = PlacesUtils.toolbarFolderId; - this._staticFoldersListBuilt = true; - } - - // List of recently used folders: - var folderIds = annos.getItemsWithAnnotation(LAST_USED_ANNO); - - /** - * The value of the LAST_USED_ANNO annotation is the time (in the form of - * Date.getTime) at which the folder has been last used. - * - * First we build the annotated folders array, each item has both the - * folder identifier and the time at which it was last-used by this dialog - * set. Then we sort it descendingly based on the time field. - */ - this._recentFolders = []; - for (let i = 0; i < folderIds.length; i++) { - var lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO); - this._recentFolders.push({ folderId: folderIds[i], lastUsed }); - } - this._recentFolders.sort(function(a, b) { - if (b.lastUsed < a.lastUsed) - return -1; - if (b.lastUsed > a.lastUsed) - return 1; - return 0; - }); - - var numberOfItems = Math.min(MAX_FOLDER_ITEM_IN_MENU_LIST, - this._recentFolders.length); - for (let i = 0; i < numberOfItems; i++) { - this._appendFolderItemToMenupopup(menupopup, - this._recentFolders[i].folderId); - } - - var defaultItem = this._getFolderMenuItem(aSelectedFolder); - this._folderMenuList.selectedItem = defaultItem; - - // Set a selectedIndex attribute to show special icons - this._folderMenuList.setAttribute("selectedIndex", - this._folderMenuList.selectedIndex); - - // Hide the folders-separator if no folder is annotated as recently-used - this._element("foldersSeparator").hidden = (menupopup.childNodes.length <= 6); - this._folderMenuList.disabled = this.readOnly; - }, - - QueryInterface: - XPCOMUtils.generateQI([Components.interfaces.nsIDOMEventListener, - Components.interfaces.nsINavBookmarkObserver]), - - _element(aID) { - return document.getElementById("editBMPanel_" + aID); - }, - - uninitPanel(aHideCollapsibleElements) { - if (aHideCollapsibleElements) { - // Hide the folder tree if it was previously visible. - var folderTreeRow = this._element("folderTreeRow"); - if (!folderTreeRow.collapsed) - this.toggleFolderTreeVisibility(); - - // Hide the tag selector if it was previously visible. - var tagsSelectorRow = this._element("tagsSelectorRow"); - if (!tagsSelectorRow.collapsed) - this.toggleTagsSelector(); - } - - if (this._observersAdded) { - PlacesUtils.bookmarks.removeObserver(this); - this._observersAdded = false; - } - - this._setPaneInfo(null); - this._firstEditedField = ""; - }, - - onTagsFieldChange() { - if (this._paneInfo.isURI || this._paneInfo.bulkTagging) { - this._updateTags().then( - anyChanges => { - if (anyChanges) - this._mayUpdateFirstEditField("tagsField"); - }, Components.utils.reportError); - } - }, - - /** - * For a given array of currently-set tags and the tags-input-field - * value, returns which tags should be removed and which should be added in - * the form of { removedTags: [...], newTags: [...] }. - */ - _getTagsChanges(aCurrentTags) { - let inputTags = this._getTagsArrayFromTagsInputField(); - - // Optimize the trivial cases (which are actually the most common). - if (inputTags.length == 0 && aCurrentTags.length == 0) - return { newTags: [], removedTags: [] }; - if (inputTags.length == 0) - return { newTags: [], removedTags: aCurrentTags }; - if (aCurrentTags.length == 0) - return { newTags: inputTags, removedTags: [] }; - - let removedTags = aCurrentTags.filter(t => !inputTags.includes(t)); - let newTags = inputTags.filter(t => !aCurrentTags.includes(t)); - return { removedTags, newTags }; - }, - - // Adds and removes tags for one or more uris. - _setTagsFromTagsInputField: Task.async(function* (aCurrentTags, aURIs) { - let { removedTags, newTags } = this._getTagsChanges(aCurrentTags); - if (removedTags.length + newTags.length == 0) - return false; - - if (!PlacesUIUtils.useAsyncTransactions) { - let txns = []; - for (let uri of aURIs) { - if (removedTags.length > 0) - txns.push(new PlacesUntagURITransaction(uri, removedTags)); - if (newTags.length > 0) - txns.push(new PlacesTagURITransaction(uri, newTags)); - } - - PlacesUtils.transactionManager.doTransaction( - new PlacesAggregatedTransaction("Update tags", txns)); - return true; - } - - let setTags = function* () { - if (newTags.length > 0) { - yield PlacesTransactions.Tag({ urls: aURIs, tags: newTags }) - .transact(); - } - if (removedTags.length > 0) { - yield PlacesTransactions.Untag({ urls: aURIs, tags: removedTags }) - .transact(); - } - }; - - // Only in the library info-pane it's safe (and necessary) to batch these. - // TODO bug 1093030: cleanup this mess when the bookmarksProperties dialog - // and star UI code don't "run a batch in the background". - if (window.document.documentElement.id == "places") - PlacesTransactions.batch(setTags).catch(Components.utils.reportError); - else - Task.spawn(setTags).catch(Components.utils.reportError); - return true; - }), - - _updateTags: Task.async(function*() { - let uris = this._paneInfo.bulkTagging ? - this._paneInfo.uris : [this._paneInfo.uri]; - let currentTags = this._paneInfo.bulkTagging ? - yield this._getCommonTags() : - PlacesUtils.tagging.getTagsForURI(uris[0]); - let anyChanges = yield this._setTagsFromTagsInputField(currentTags, uris); - if (!anyChanges) - return false; - - // The panel could have been closed in the meanwhile. - if (!this._paneInfo) - return false; - - // Ensure the tagsField is in sync, clean it up from empty tags - currentTags = this._paneInfo.bulkTagging ? - this._getCommonTags() : - PlacesUtils.tagging.getTagsForURI(this._paneInfo.uri); - this._initTextField(this._tagsField, currentTags.join(", "), false); - return true; - }), - - /** - * Stores the first-edit field for this dialog, if the passed-in field - * is indeed the first edited field - * @param aNewField - * the id of the field that may be set (without the "editBMPanel_" - * prefix) - */ - _mayUpdateFirstEditField(aNewField) { - // * The first-edit-field behavior is not applied in the multi-edit case - // * if this._firstEditedField is already set, this is not the first field, - // so there's nothing to do - if (this._paneInfo.bulkTagging || this._firstEditedField) - return; - - this._firstEditedField = aNewField; - - // set the pref - var prefs = Cc["@mozilla.org/preferences-service;1"]. - getService(Ci.nsIPrefBranch); - prefs.setCharPref("browser.bookmarks.editDialog.firstEditField", aNewField); - }, - - onNamePickerChange() { - if (this.readOnly || !(this._paneInfo.isItem || this._paneInfo.isTag)) - return; - - // Here we update either the item title or its cached static title - let newTitle = this._namePicker.value; - if (!newTitle && - PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) == PlacesUtils.tagsFolderId) { - // We don't allow setting an empty title for a tag, restore the old one. - this._initNamePicker(); - } else { - this._mayUpdateFirstEditField("namePicker"); - if (!PlacesUIUtils.useAsyncTransactions) { - let txn = new PlacesEditItemTitleTransaction(this._paneInfo.itemId, - newTitle); - PlacesUtils.transactionManager.doTransaction(txn); - return; - } - Task.spawn(function* () { - let guid = this._paneInfo.isTag - ? (yield PlacesUtils.promiseItemGuid(this._paneInfo.itemId)) - : this._paneInfo.itemGuid; - PlacesTransactions.EditTitle({ guid, title: newTitle }) - .transact().catch(Components.utils.reportError); - }).catch(Components.utils.reportError); - } - }, - - onDescriptionFieldChange() { - if (this.readOnly || !this._paneInfo.isItem) - return; - - let itemId = this._paneInfo.itemId; - let description = this._element("descriptionField").value; - if (description != PlacesUIUtils.getItemDescription(this._paneInfo.itemId)) { - let annotation = - { name: PlacesUIUtils.DESCRIPTION_ANNO, value: description }; - if (!PlacesUIUtils.useAsyncTransactions) { - let txn = new PlacesSetItemAnnotationTransaction(itemId, - annotation); - PlacesUtils.transactionManager.doTransaction(txn); - return; - } - let guid = this._paneInfo.itemGuid; - PlacesTransactions.Annotate({ guid, annotation }) - .transact().catch(Components.utils.reportError); - } - }, - - onLocationFieldChange() { - if (this.readOnly || !this._paneInfo.isBookmark) - return; - - let newURI; - try { - newURI = PlacesUIUtils.createFixedURI(this._locationField.value); - } catch (ex) { - // TODO: Bug 1089141 - Provide some feedback about the invalid url. - return; - } - - if (this._paneInfo.uri.equals(newURI)) - return; - - if (!PlacesUIUtils.useAsyncTransactions) { - let txn = new PlacesEditBookmarkURITransaction(this._paneInfo.itemId, newURI); - PlacesUtils.transactionManager.doTransaction(txn); - return; - } - let guid = this._paneInfo.itemGuid; - PlacesTransactions.EditUrl({ guid, url: newURI }) - .transact().catch(Components.utils.reportError); - }, - - onKeywordFieldChange() { - if (this.readOnly || !this._paneInfo.isBookmark) - return; - - let itemId = this._paneInfo.itemId; - let oldKeyword = this._keyword; - let keyword = this._keyword = this._keywordField.value; - let postData = this._paneInfo.postData; - if (!PlacesUIUtils.useAsyncTransactions) { - let txn = new PlacesEditBookmarkKeywordTransaction(itemId, - keyword, - postData, - oldKeyword); - PlacesUtils.transactionManager.doTransaction(txn); - return; - } - let guid = this._paneInfo.itemGuid; - PlacesTransactions.EditKeyword({ guid, keyword, postData, oldKeyword }) - .transact().catch(Components.utils.reportError); - }, - - onLoadInSidebarCheckboxCommand() { - if (!this.initialized || !this._paneInfo.isBookmark) - return; - - let annotation = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO }; - if (this._loadInSidebarCheckbox.checked) - annotation.value = true; - - if (!PlacesUIUtils.useAsyncTransactions) { - let itemId = this._paneInfo.itemId; - let txn = new PlacesSetItemAnnotationTransaction(itemId, - annotation); - PlacesUtils.transactionManager.doTransaction(txn); - return; - } - let guid = this._paneInfo.itemGuid; - PlacesTransactions.Annotate({ guid, annotation }) - .transact().catch(Components.utils.reportError); - }, - - toggleFolderTreeVisibility() { - var expander = this._element("foldersExpander"); - var folderTreeRow = this._element("folderTreeRow"); - if (!folderTreeRow.collapsed) { - expander.className = "expander-down"; - expander.setAttribute("tooltiptext", - expander.getAttribute("tooltiptextdown")); - folderTreeRow.collapsed = true; - this._element("chooseFolderSeparator").hidden = - this._element("chooseFolderMenuItem").hidden = false; - } else { - expander.className = "expander-up" - expander.setAttribute("tooltiptext", - expander.getAttribute("tooltiptextup")); - folderTreeRow.collapsed = false; - - // XXXmano: Ideally we would only do this once, but for some odd reason, - // the editable mode set on this tree, together with its collapsed state - // breaks the view. - const FOLDER_TREE_PLACE_URI = - "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" + - PlacesUIUtils.allBookmarksFolderId; - this._folderTree.place = FOLDER_TREE_PLACE_URI; - - this._element("chooseFolderSeparator").hidden = - this._element("chooseFolderMenuItem").hidden = true; - var currentFolder = this._getFolderIdFromMenuList(); - this._folderTree.selectItems([currentFolder]); - this._folderTree.focus(); - } - }, - - _getFolderIdFromMenuList() { - var selectedItem = this._folderMenuList.selectedItem; - NS_ASSERT("folderId" in selectedItem, - "Invalid menuitem in the folders-menulist"); - return selectedItem.folderId; - }, - - /** - * Get the corresponding menu-item in the folder-menu-list for a bookmarks - * folder if such an item exists. Otherwise, this creates a menu-item for the - * folder. If the items-count limit (see MAX_FOLDERS_IN_MENU_LIST) is reached, - * the new item replaces the last menu-item. - * @param aFolderId - * The identifier of the bookmarks folder. - */ - _getFolderMenuItem(aFolderId) { - let menupopup = this._folderMenuList.menupopup; - let menuItem = Array.prototype.find.call( - menupopup.childNodes, item => item.folderId === aFolderId); - if (menuItem !== undefined) - return menuItem; - - // 3 special folders + separator + folder-items-count limit - if (menupopup.childNodes.length == 4 + MAX_FOLDER_ITEM_IN_MENU_LIST) - menupopup.removeChild(menupopup.lastChild); - - return this._appendFolderItemToMenupopup(menupopup, aFolderId); - }, - - onFolderMenuListCommand(aEvent) { - // Set a selectedIndex attribute to show special icons - this._folderMenuList.setAttribute("selectedIndex", - this._folderMenuList.selectedIndex); - - if (aEvent.target.id == "editBMPanel_chooseFolderMenuItem") { - // reset the selection back to where it was and expand the tree - // (this menu-item is hidden when the tree is already visible - let containerId = PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId); - let item = this._getFolderMenuItem(containerId); - this._folderMenuList.selectedItem = item; - // XXXmano HACK: setTimeout 100, otherwise focus goes back to the - // menulist right away - setTimeout(() => this.toggleFolderTreeVisibility(), 100); - return; - } - - // Move the item - let containerId = this._getFolderIdFromMenuList(); - if (PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) != containerId && - this._paneInfo.itemId != containerId) { - if (PlacesUIUtils.useAsyncTransactions) { - Task.spawn(function* () { - let newParentGuid = yield PlacesUtils.promiseItemGuid(containerId); - let guid = this._paneInfo.itemGuid; - yield PlacesTransactions.Move({ guid, newParentGuid }).transact(); - }.bind(this)); - } else { - let txn = new PlacesMoveItemTransaction(this._paneInfo.itemId, - containerId, - PlacesUtils.bookmarks.DEFAULT_INDEX); - PlacesUtils.transactionManager.doTransaction(txn); - } - - // Mark the containing folder as recently-used if it isn't in the - // static list - if (containerId != PlacesUtils.unfiledBookmarksFolderId && - containerId != PlacesUtils.toolbarFolderId && - containerId != PlacesUtils.bookmarksMenuFolderId) { - this._markFolderAsRecentlyUsed(containerId) - .catch(Components.utils.reportError); - } - - // Auto-show the bookmarks toolbar when adding / moving an item there. - if (containerId == PlacesUtils.toolbarFolderId) { - Services.obs.notifyObservers(null, "autoshow-bookmarks-toolbar", null); - } - } - - // Update folder-tree selection - var folderTreeRow = this._element("folderTreeRow"); - if (!folderTreeRow.collapsed) { - var selectedNode = this._folderTree.selectedNode; - if (!selectedNode || - PlacesUtils.getConcreteItemId(selectedNode) != containerId) - this._folderTree.selectItems([containerId]); - } - }, - - onFolderTreeSelect() { - var selectedNode = this._folderTree.selectedNode; - - // Disable the "New Folder" button if we cannot create a new folder - this._element("newFolderButton") - .disabled = !this._folderTree.insertionPoint || !selectedNode; - - if (!selectedNode) - return; - - var folderId = PlacesUtils.getConcreteItemId(selectedNode); - if (this._getFolderIdFromMenuList() == folderId) - return; - - var folderItem = this._getFolderMenuItem(folderId); - this._folderMenuList.selectedItem = folderItem; - folderItem.doCommand(); - }, - - _markFolderAsRecentlyUsed: Task.async(function* (aFolderId) { - if (!PlacesUIUtils.useAsyncTransactions) { - let txns = []; - - // Expire old unused recent folders. - let annotation = this._getLastUsedAnnotationObject(false); - while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) { - let folderId = this._recentFolders.pop().folderId; - let annoTxn = new PlacesSetItemAnnotationTransaction(folderId, - annotation); - txns.push(annoTxn); - } - - // Mark folder as recently used - annotation = this._getLastUsedAnnotationObject(true); - let annoTxn = new PlacesSetItemAnnotationTransaction(aFolderId, - annotation); - txns.push(annoTxn); - - let aggregate = - new PlacesAggregatedTransaction("Update last used folders", txns); - PlacesUtils.transactionManager.doTransaction(aggregate); - return; - } - - // Expire old unused recent folders. - let guids = []; - while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) { - let folderId = this._recentFolders.pop().folderId; - let guid = yield PlacesUtils.promiseItemGuid(folderId); - guids.push(guid); - } - if (guids.length > 0) { - let annotation = this._getLastUsedAnnotationObject(false); - PlacesTransactions.Annotate({ guids, annotation }) - .transact().catch(Components.utils.reportError); - } - - // Mark folder as recently used - let annotation = this._getLastUsedAnnotationObject(true); - let guid = yield PlacesUtils.promiseItemGuid(aFolderId); - PlacesTransactions.Annotate({ guid, annotation }) - .transact().catch(Components.utils.reportError); - }), - - /** - * Returns an object which could then be used to set/unset the - * LAST_USED_ANNO annotation for a folder. - * - * @param aLastUsed - * Whether to set or unset the LAST_USED_ANNO annotation. - * @returns an object representing the annotation which could then be used - * with the transaction manager. - */ - _getLastUsedAnnotationObject(aLastUsed) { - return { name: LAST_USED_ANNO, - value: aLastUsed ? new Date().getTime() : null }; - }, - - _rebuildTagsSelectorList: Task.async(function* () { - let tagsSelector = this._element("tagsSelector"); - let tagsSelectorRow = this._element("tagsSelectorRow"); - if (tagsSelectorRow.collapsed) - return; - - // Save the current scroll position and restore it after the rebuild. - let firstIndex = tagsSelector.getIndexOfFirstVisibleRow(); - let selectedIndex = tagsSelector.selectedIndex; - let selectedTag = selectedIndex >= 0 ? tagsSelector.selectedItem.label - : null; - - while (tagsSelector.hasChildNodes()) { - tagsSelector.removeChild(tagsSelector.lastChild); - } - - let tagsInField = this._getTagsArrayFromTagsInputField(); - let allTags = PlacesUtils.tagging.allTags; - for (tag of allTags) { - let elt = document.createElement("listitem"); - elt.setAttribute("type", "checkbox"); - elt.setAttribute("label", tag); - if (tagsInField.includes(tag)) - elt.setAttribute("checked", "true"); - tagsSelector.appendChild(elt); - if (selectedTag === tag) - selectedIndex = tagsSelector.getIndexOfItem(elt); - } - - // Restore position. - // The listbox allows to scroll only if the required offset doesn't - // overflow its capacity, thus need to adjust the index for removals. - firstIndex = - Math.min(firstIndex, - tagsSelector.itemCount - tagsSelector.getNumberOfVisibleRows()); - tagsSelector.scrollToIndex(firstIndex); - if (selectedIndex >= 0 && tagsSelector.itemCount > 0) { - selectedIndex = Math.min(selectedIndex, tagsSelector.itemCount - 1); - tagsSelector.selectedIndex = selectedIndex; - tagsSelector.ensureIndexIsVisible(selectedIndex); - } - }), - - toggleTagsSelector: Task.async(function* () { - var tagsSelector = this._element("tagsSelector"); - var tagsSelectorRow = this._element("tagsSelectorRow"); - var expander = this._element("tagsSelectorExpander"); - if (tagsSelectorRow.collapsed) { - expander.className = "expander-up"; - expander.setAttribute("tooltiptext", - expander.getAttribute("tooltiptextup")); - tagsSelectorRow.collapsed = false; - yield this._rebuildTagsSelectorList(); - - // This is a no-op if we've added the listener. - tagsSelector.addEventListener("CheckboxStateChange", this); - } else { - expander.className = "expander-down"; - expander.setAttribute("tooltiptext", - expander.getAttribute("tooltiptextdown")); - tagsSelectorRow.collapsed = true; - } - }), - - /** - * Splits "tagsField" element value, returning an array of valid tag strings. - * - * @return Array of tag strings found in the field value. - */ - _getTagsArrayFromTagsInputField() { - let tags = this._element("tagsField").value; - return tags.trim() - .split(/\s*,\s*/) // Split on commas and remove spaces. - .filter(tag => tag.length > 0); // Kill empty tags. - }, - - newFolder: Task.async(function* () { - let ip = this._folderTree.insertionPoint; - - // default to the bookmarks menu folder - if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) { - ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId, - PlacesUtils.bookmarks.DEFAULT_INDEX, - Ci.nsITreeView.DROP_ON); - } - - // XXXmano: add a separate "New Folder" string at some point... - let title = this._element("newFolderButton").label; - if (PlacesUIUtils.useAsyncTransactions) { - let parentGuid = yield ip.promiseGuid(); - yield PlacesTransactions.NewFolder({ parentGuid, title, index: ip.index }) - .transact().catch(Components.utils.reportError); - } else { - let txn = new PlacesCreateFolderTransaction(title, ip.itemId, ip.index); - PlacesUtils.transactionManager.doTransaction(txn); - } - - this._folderTree.focus(); - this._folderTree.selectItems([ip.itemId]); - PlacesUtils.asContainer(this._folderTree.selectedNode).containerOpen = true; - this._folderTree.selectItems([this._lastNewItem]); - this._folderTree.startEditing(this._folderTree.view.selection.currentIndex, - this._folderTree.columns.getFirstColumn()); - }), - - // nsIDOMEventListener - handleEvent(aEvent) { - switch (aEvent.type) { - case "CheckboxStateChange": - // Update the tags field when items are checked/unchecked in the listbox - let tags = this._getTagsArrayFromTagsInputField(); - let tagCheckbox = aEvent.target; - - let curTagIndex = tags.indexOf(tagCheckbox.label); - let tagsSelector = this._element("tagsSelector"); - tagsSelector.selectedItem = tagCheckbox; - - if (tagCheckbox.checked) { - if (curTagIndex == -1) - tags.push(tagCheckbox.label); - } else if (curTagIndex != -1) { - tags.splice(curTagIndex, 1); - } - this._element("tagsField").value = tags.join(", "); - this._updateTags(); - break; - case "unload": - this.uninitPanel(false); - break; - } - }, - - _initTagsField: Task.async(function* () { - let tags; - if (this._paneInfo.isURI) - tags = PlacesUtils.tagging.getTagsForURI(this._paneInfo.uri); - else if (this._paneInfo.bulkTagging) - tags = this._getCommonTags(); - else - throw new Error("_promiseTagsStr called unexpectedly"); - - this._initTextField(this._tagsField, tags.join(", ")); - }), - - _onTagsChange(aItemId) { - let paneInfo = this._paneInfo; - let updateTagsField = false; - if (paneInfo.isURI) { - if (paneInfo.isBookmark && aItemId == paneInfo.itemId) { - updateTagsField = true; - } else if (!paneInfo.isBookmark) { - let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId); - updateTagsField = changedURI.equals(paneInfo.uri); - } - } else if (paneInfo.bulkTagging) { - let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId); - if (paneInfo.uris.some(uri => uri.equals(changedURI))) { - updateTagsField = true; - delete this._paneInfo._cachedCommonTags; - } - } else { - throw new Error("_onTagsChange called unexpectedly"); - } - - if (updateTagsField) - this._initTagsField().catch(Components.utils.reportError); - - // Any tags change should be reflected in the tags selector. - if (this._element("tagsSelector")) - this._rebuildTagsSelectorList().catch(Components.utils.reportError); - }, - - _onItemTitleChange(aItemId, aNewTitle) { - if (!this._paneInfo.isBookmark) - return; - if (aItemId == this._paneInfo.itemId) { - this._paneInfo.title = aNewTitle; - this._initTextField(this._namePicker, aNewTitle); - } else if (this._paneInfo.visibleRows.has("folderRow")) { - // If the title of a folder which is listed within the folders - // menulist has been changed, we need to update the label of its - // representing element. - let menupopup = this._folderMenuList.menupopup; - for (menuitem of menupopup.childNodes) { - if ("folderId" in menuitem && menuitem.folderId == aItemId) { - menuitem.label = aNewTitle; - break; - } - } - } - }, - - // nsINavBookmarkObserver - onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aValue, - aLastModified, aItemType) { - if (aProperty == "tags" && this._paneInfo.visibleRows.has("tagsRow")) { - this._onTagsChange(aItemId); - } else if (aProperty == "title" && this._paneInfo.isItem) { - // This also updates titles of folders in the folder menu list. - this._onItemTitleChange(aItemId, aValue); - } else if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId) { - return; - } - - switch (aProperty) { - case "uri": - let newURI = NetUtil.newURI(aValue); - if (!newURI.equals(this._paneInfo.uri)) { - this._paneInfo.uri = newURI; - if (this._paneInfo.visibleRows.has("locationRow")) - this._initLocationField(); - - if (this._paneInfo.visibleRows.has("tagsRow")) { - delete this._paneInfo._cachedCommonTags; - this._onTagsChange(aItemId); - } - } - break; - case "keyword": - if (this._paneInfo.visibleRows.has("keywordRow")) - this._initKeywordField(aValue).catch(Components.utils.reportError); - break; - case PlacesUIUtils.DESCRIPTION_ANNO: - if (this._paneInfo.visibleRows.has("descriptionRow")) - this._initDescriptionField(); - break; - case PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO: - if (this._paneInfo.visibleRows.has("loadInSidebarCheckbox")) - this._initLoadInSidebar(); - break; - } - }, - - onItemMoved(aItemId, aOldParent, aOldIndex, - aNewParent, aNewIndex, aItemType) { - if (!this._paneInfo.isItem || - !this._paneInfo.visibleRows.has("folderPicker") || - this._paneInfo.itemId != aItemOd || - aNewParent == this._getFolderIdFromMenuList()) { - return; - } - - // Just setting selectItem _does not_ trigger oncommand, so we don't - // recurse. - this._folderMenuList.selectedItem = this._getFolderMenuItem(aNewParent); - }, - - onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI) { - this._lastNewItem = aItemId; - }, - - onItemRemoved() { }, - onBeginUpdateBatch() { }, - onEndUpdateBatch() { }, - onItemVisited() { }, -}; - - -for (let elt of ["folderMenuList", "folderTree", "namePicker", - "locationField", "descriptionField", "keywordField", - "tagsField", "loadInSidebarCheckbox"]) { - let eltScoped = elt; - XPCOMUtils.defineLazyGetter(gEditItemOverlay, `_${eltScoped}`, - () => gEditItemOverlay._element(eltScoped)); -} diff --git a/application/basilisk/components/places/content/editBookmarkOverlay.xul b/application/basilisk/components/places/content/editBookmarkOverlay.xul deleted file mode 100644 index 140e752c0..000000000 --- a/application/basilisk/components/places/content/editBookmarkOverlay.xul +++ /dev/null @@ -1,188 +0,0 @@ - - - -%editBookmarkOverlayDTD; -]> - - - - - - - - - - - - - - - - - - - - - - - -