diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /services/sync/tps/extensions/tps/resource/modules/bookmarks.sys.mjs | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'services/sync/tps/extensions/tps/resource/modules/bookmarks.sys.mjs')
-rw-r--r-- | services/sync/tps/extensions/tps/resource/modules/bookmarks.sys.mjs | 833 |
1 files changed, 833 insertions, 0 deletions
diff --git a/services/sync/tps/extensions/tps/resource/modules/bookmarks.sys.mjs b/services/sync/tps/extensions/tps/resource/modules/bookmarks.sys.mjs new file mode 100644 index 0000000000..e4aac948b5 --- /dev/null +++ b/services/sync/tps/extensions/tps/resource/modules/bookmarks.sys.mjs @@ -0,0 +1,833 @@ +/* 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 is a JavaScript module (JSM) to be imported via + * ChromeUtils.import() and acts as a singleton. Only the following + * listed symbols will exposed on import, and only when and where imported. + */ + +import { PlacesBackups } from "resource://gre/modules/PlacesBackups.sys.mjs"; + +import { PlacesSyncUtils } from "resource://gre/modules/PlacesSyncUtils.sys.mjs"; +import { PlacesUtils } from "resource://gre/modules/PlacesUtils.sys.mjs"; + +import { Logger } from "resource://tps/logger.sys.mjs"; + +export async function DumpBookmarks() { + let [bookmarks] = await PlacesBackups.getBookmarksTree(); + Logger.logInfo( + "Dumping Bookmarks...\n" + JSON.stringify(bookmarks, undefined, 2) + "\n\n" + ); +} + +/** + * extend, causes a child object to inherit from a parent + */ +function extend(child, supertype) { + Object.setPrototypeOf(child.prototype, supertype.prototype); +} +/** + * PlacesItemProps object, holds properties for places items + */ +function PlacesItemProps(props) { + this.location = null; + this.uri = null; + this.keyword = null; + this.title = null; + this.after = null; + this.before = null; + this.folder = null; + this.position = null; + this.delete = false; + this.tags = null; + this.last_item_pos = null; + this.type = null; + + for (var prop in props) { + if (prop in this) { + this[prop] = props[prop]; + } + } +} + +/** + * PlacesItem object. Base class for places items. + */ +export function PlacesItem(props) { + this.props = new PlacesItemProps(props); + if (this.props.location == null) { + this.props.location = "menu"; + } + if ("changes" in props) { + this.updateProps = new PlacesItemProps(props.changes); + } else { + this.updateProps = null; + } +} + +/** + * Instance methods for generic places items. + */ +PlacesItem.prototype = { + // an array of possible root folders for places items + _bookmarkFolders: { + places: PlacesUtils.bookmarks.rootGuid, + menu: PlacesUtils.bookmarks.menuGuid, + tags: PlacesUtils.bookmarks.tagsGuid, + unfiled: PlacesUtils.bookmarks.unfiledGuid, + toolbar: PlacesUtils.bookmarks.toolbarGuid, + mobile: PlacesUtils.bookmarks.mobileGuid, + }, + + _typeMap: new Map([ + [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER, PlacesUtils.bookmarks.TYPE_FOLDER], + [ + PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR, + PlacesUtils.bookmarks.TYPE_SEPARATOR, + ], + [PlacesUtils.TYPE_X_MOZ_PLACE, PlacesUtils.bookmarks.TYPE_BOOKMARK], + ]), + + toString() { + var that = this; + var props = ["uri", "title", "location", "folder"]; + var string = + (this.props.type ? this.props.type + " " : "") + + "(" + + (function () { + var ret = []; + for (var i in props) { + if (that.props[props[i]]) { + ret.push(props[i] + ": " + that.props[props[i]]); + } + } + return ret; + })().join(", ") + + ")"; + return string; + }, + + /** + * GetPlacesChildGuid + * + * Finds the guid of the an item with the specified properties in the places + * database under the specified parent. + * + * @param folder The guid of the folder to search + * @param type The type of the item to find, or null to match any item; + * this is one of the PlacesUtils.bookmarks.TYPE_* values + * @param title The title of the item to find, or null to match any title + * @param uri The uri of the item to find, or null to match any uri + * + * @return the node id if the item was found, otherwise null + */ + async GetPlacesChildGuid(folder, type, title, uri) { + let children = (await PlacesUtils.promiseBookmarksTree(folder)).children; + if (!children) { + return null; + } + let guid = null; + for (let node of children) { + if (node.title == title) { + let nodeType = this._typeMap.get(node.type); + if (type == null || type == undefined || nodeType == type) { + if (uri == undefined || uri == null || node.uri.spec == uri.spec) { + // Note that this is suspect as we return the *last* matching + // child, which some tests rely on (ie, an early-return here causes + // at least 1 test to fail). But that's a yak for another day. + guid = node.guid; + } + } + } + } + return guid; + }, + + /** + * IsAdjacentTo + * + * Determines if this object is immediately adjacent to another. + * + * @param itemName The name of the other object; this may be any kind of + * places item + * @param relativePos The relative position of the other object. If -1, + * it means the other object should precede this one, if +1, + * the other object should come after this one + * @return true if this object is immediately adjacent to the other object, + * otherwise false + */ + async IsAdjacentTo(itemName, relativePos) { + Logger.AssertTrue( + this.props.folder_id != -1 && this.props.guid != null, + "Either folder_id or guid was invalid" + ); + let otherGuid = await this.GetPlacesChildGuid( + this.props.parentGuid, + null, + itemName + ); + Logger.AssertTrue(otherGuid, "item " + itemName + " not found"); + let other_pos = (await PlacesUtils.bookmarks.fetch(otherGuid)).index; + let this_pos = (await PlacesUtils.bookmarks.fetch(this.props.guid)).index; + if (other_pos + relativePos != this_pos) { + Logger.logPotentialError( + "Invalid position - " + + (this.props.title ? this.props.title : this.props.folder) + + " not " + + (relativePos == 1 ? "after " : "before ") + + itemName + + " for " + + this.toString() + ); + return false; + } + return true; + }, + + /** + * GetItemIndex + * + * Gets the item index for this places item. + * + * @return the item index, or -1 if there's an error + */ + async GetItemIndex() { + if (this.props.guid == null) { + return -1; + } + return (await PlacesUtils.bookmarks.fetch(this.props.guid)).index; + }, + + /** + * GetFolder + * + * Gets the folder guid for the specified bookmark folder + * + * @param location The full path of the folder, which must begin + * with one of the bookmark root folders + * @return the folder guid if the folder is found, otherwise null + */ + async GetFolder(location) { + let folder_parts = location.split("/"); + if (!(folder_parts[0] in this._bookmarkFolders)) { + return null; + } + let folderGuid = this._bookmarkFolders[folder_parts[0]]; + for (let i = 1; i < folder_parts.length; i++) { + let guid = await this.GetPlacesChildGuid( + folderGuid, + PlacesUtils.bookmarks.TYPE_FOLDER, + folder_parts[i] + ); + if (guid == null) { + return null; + } + folderGuid = guid; + } + return folderGuid; + }, + + /** + * CreateFolder + * + * Creates a bookmark folder. + * + * @param location The full path of the folder, which must begin + * with one of the bookmark root folders + * @return the folder id if the folder was created, otherwise -1 + */ + async CreateFolder(location) { + let folder_parts = location.split("/"); + if (!(folder_parts[0] in this._bookmarkFolders)) { + return -1; + } + let folderGuid = this._bookmarkFolders[folder_parts[0]]; + for (let i = 1; i < folder_parts.length; i++) { + let subfolderGuid = await this.GetPlacesChildGuid( + folderGuid, + PlacesUtils.bookmarks.TYPE_FOLDER, + folder_parts[i] + ); + if (subfolderGuid == null) { + let { guid } = await PlacesUtils.bookmarks.insert({ + parentGuid: folderGuid, + name: folder_parts[i], + type: PlacesUtils.bookmarks.TYPE_FOLDER, + }); + folderGuid = guid; + } else { + folderGuid = subfolderGuid; + } + } + return folderGuid; + }, + + /** + * GetOrCreateFolder + * + * Locates the specified folder; if not found it is created. + * + * @param location The full path of the folder, which must begin + * with one of the bookmark root folders + * @return the folder id if the folder was found or created, otherwise -1 + */ + async GetOrCreateFolder(location) { + let parentGuid = await this.GetFolder(location); + if (parentGuid == null) { + parentGuid = await this.CreateFolder(location); + } + return parentGuid; + }, + + /** + * CheckPosition + * + * Verifies the position of this places item. + * + * @param before The name of the places item that this item should be + before, or null if this check should be skipped + * @param after The name of the places item that this item should be + after, or null if this check should be skipped + * @param last_item_pos The index of the places item above this one, + * or null if this check should be skipped + * @return true if this item is in the correct position, otherwise false + */ + async CheckPosition(before, after, last_item_pos) { + if (after) { + if (!(await this.IsAdjacentTo(after, 1))) { + return false; + } + } + if (before) { + if (!(await this.IsAdjacentTo(before, -1))) { + return false; + } + } + if (last_item_pos != null && last_item_pos > -1) { + let index = await this.GetItemIndex(); + if (index != last_item_pos + 1) { + Logger.logPotentialError( + "Item not found at the expected index, got " + + index + + ", expected " + + (last_item_pos + 1) + + " for " + + this.toString() + ); + return false; + } + } + return true; + }, + + /** + * SetLocation + * + * Moves this places item to a different folder. + * + * @param location The full path of the folder to which to move this + * places item, which must begin with one of the bookmark root + * folders; if null, no changes are made + * @return nothing if successful, otherwise an exception is thrown + */ + async SetLocation(location) { + if (location != null) { + let newfolderGuid = await this.GetOrCreateFolder(location); + Logger.AssertTrue( + newfolderGuid, + "Location " + location + " doesn't exist; can't change item's location" + ); + await PlacesUtils.bookmarks.update({ + guid: this.props.guid, + parentGuid: newfolderGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + }); + this.props.parentGuid = newfolderGuid; + } + }, + + /** + * SetPosition + * + * Updates the position of this places item within this item's current + * folder. Use SetLocation to change folders. + * + * @param position The new index this item should be moved to; if null, + * no changes are made; if -1, this item is moved to the bottom of + * the current folder. Otherwise, must be a string which is the + * title of an existing item in the folder, who's current position + * is used as the index. + * @return nothing if successful, otherwise an exception is thrown + */ + async SetPosition(position) { + if (position == null) { + return; + } + let index = -1; + if (position != -1) { + let existingGuid = await this.GetPlacesChildGuid( + this.props.parentGuid, + null, + position + ); + if (existingGuid) { + index = (await PlacesUtils.bookmarks.fetch(existingGuid)).index; + } + Logger.AssertTrue( + index != -1, + "position " + position + " is invalid; unable to change position" + ); + } + await PlacesUtils.bookmarks.update({ guid: this.props.guid, index }); + }, + + /** + * Update the title of this places item + * + * @param title The new title to set for this item; if null, no changes + * are made + * @return nothing + */ + async SetTitle(title) { + if (title != null) { + await PlacesUtils.bookmarks.update({ guid: this.props.guid, title }); + } + }, +}; + +/** + * Bookmark class constructor. Initializes instance properties. + */ +export function Bookmark(props) { + PlacesItem.call(this, props); + if (this.props.title == null) { + this.props.title = this.props.uri; + } + this.props.type = "bookmark"; +} + +/** + * Bookmark instance methods. + */ +Bookmark.prototype = { + /** + * SetKeyword + * + * Update this bookmark's keyword. + * + * @param keyword The keyword to set for this bookmark; if null, no + * changes are made + * @return nothing + */ + async SetKeyword(keyword) { + if (keyword != null) { + // Mirror logic from PlacesSyncUtils's updateBookmarkMetadata + let entry = await PlacesUtils.keywords.fetch({ url: this.props.uri }); + if (entry) { + await PlacesUtils.keywords.remove(entry); + } + await PlacesUtils.keywords.insert({ keyword, url: this.props.uri }); + } + }, + + /** + * SetUri + * + * Updates this bookmark's URI. + * + * @param uri The new URI to set for this boomark; if null, no changes + * are made + * @return nothing + */ + async SetUri(uri) { + if (uri) { + let url = Services.io.newURI(uri); + await PlacesUtils.bookmarks.update({ guid: this.props.guid, url }); + } + }, + + /** + * SetTags + * + * Updates this bookmark's tags. + * + * @param tags An array of tags which should be associated with this + * bookmark; any previous tags are removed; if this param is null, + * no changes are made. If this param is an empty array, all + * tags are removed from this bookmark. + * @return nothing + */ + SetTags(tags) { + if (tags != null) { + let URI = Services.io.newURI(this.props.uri); + PlacesUtils.tagging.untagURI(URI, null); + if (tags.length) { + PlacesUtils.tagging.tagURI(URI, tags); + } + } + }, + + /** + * Create + * + * Creates the bookmark described by this object's properties. + * + * @return the id of the created bookmark + */ + async Create() { + this.props.parentGuid = await this.GetOrCreateFolder(this.props.location); + Logger.AssertTrue( + this.props.parentGuid, + "Unable to create " + + "bookmark, error creating folder " + + this.props.location + ); + let bookmarkURI = Services.io.newURI(this.props.uri); + let { guid } = await PlacesUtils.bookmarks.insert({ + parentGuid: this.props.parentGuid, + url: bookmarkURI, + title: this.props.title, + }); + this.props.guid = guid; + await this.SetKeyword(this.props.keyword); + await this.SetTags(this.props.tags); + return this.props.guid; + }, + + /** + * Update + * + * Updates this bookmark's properties according the properties on this + * object's 'updateProps' property. + * + * @return nothing + */ + async Update() { + Logger.AssertTrue(this.props.guid, "Invalid guid during Update"); + await this.SetTitle(this.updateProps.title); + await this.SetUri(this.updateProps.uri); + await this.SetKeyword(this.updateProps.keyword); + await this.SetTags(this.updateProps.tags); + await this.SetLocation(this.updateProps.location); + await this.SetPosition(this.updateProps.position); + }, + + /** + * Find + * + * Locates the bookmark which corresponds to this object's properties. + * + * @return the bookmark guid if the bookmark was found, otherwise null + */ + async Find() { + this.props.parentGuid = await this.GetFolder(this.props.location); + + if (this.props.parentGuid == null) { + Logger.logError("Unable to find folder " + this.props.location); + return null; + } + let bookmarkTitle = this.props.title; + this.props.guid = await this.GetPlacesChildGuid( + this.props.parentGuid, + null, + bookmarkTitle, + this.props.uri + ); + + if (!this.props.guid) { + Logger.logPotentialError(this.toString() + " not found"); + return null; + } + if (this.props.keyword != null) { + let { keyword } = await PlacesSyncUtils.bookmarks.fetch(this.props.guid); + if (keyword != this.props.keyword) { + Logger.logPotentialError( + "Incorrect keyword - expected: " + + this.props.keyword + + ", actual: " + + keyword + + " for " + + this.toString() + ); + return null; + } + } + if (this.props.tags != null) { + try { + let URI = Services.io.newURI(this.props.uri); + let tags = PlacesUtils.tagging.getTagsForURI(URI); + tags.sort(); + this.props.tags.sort(); + if (JSON.stringify(tags) != JSON.stringify(this.props.tags)) { + Logger.logPotentialError( + "Wrong tags - expected: " + + JSON.stringify(this.props.tags) + + ", actual: " + + JSON.stringify(tags) + + " for " + + this.toString() + ); + return null; + } + } catch (e) { + Logger.logPotentialError("error processing tags " + e); + return null; + } + } + if ( + !(await this.CheckPosition( + this.props.before, + this.props.after, + this.props.last_item_pos + )) + ) { + return null; + } + return this.props.guid; + }, + + /** + * Remove + * + * Removes this bookmark. The bookmark should have been located previously + * by a call to Find. + * + * @return nothing + */ + async Remove() { + Logger.AssertTrue(this.props.guid, "Invalid guid during Remove"); + await PlacesUtils.bookmarks.remove(this.props.guid); + }, +}; + +extend(Bookmark, PlacesItem); + +/** + * BookmarkFolder class constructor. Initializes instance properties. + */ +export function BookmarkFolder(props) { + PlacesItem.call(this, props); + this.props.type = "folder"; +} + +/** + * BookmarkFolder instance methods + */ +BookmarkFolder.prototype = { + /** + * Create + * + * Creates the bookmark folder described by this object's properties. + * + * @return the id of the created bookmark folder + */ + async Create() { + this.props.parentGuid = await this.GetOrCreateFolder(this.props.location); + Logger.AssertTrue( + this.props.parentGuid, + "Unable to create " + + "folder, error creating parent folder " + + this.props.location + ); + let { guid } = await PlacesUtils.bookmarks.insert({ + parentGuid: this.props.parentGuid, + title: this.props.folder, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_FOLDER, + }); + this.props.guid = guid; + return this.props.parentGuid; + }, + + /** + * Find + * + * Locates the bookmark folder which corresponds to this object's + * properties. + * + * @return the folder guid if the folder was found, otherwise null + */ + async Find() { + this.props.parentGuid = await this.GetFolder(this.props.location); + if (this.props.parentGuid == null) { + Logger.logError("Unable to find folder " + this.props.location); + return null; + } + this.props.guid = await this.GetPlacesChildGuid( + this.props.parentGuid, + PlacesUtils.bookmarks.TYPE_FOLDER, + this.props.folder + ); + if (this.props.guid == null) { + return null; + } + if ( + !(await this.CheckPosition( + this.props.before, + this.props.after, + this.props.last_item_pos + )) + ) { + return null; + } + return this.props.guid; + }, + + /** + * Remove + * + * Removes this folder. The folder should have been located previously + * by a call to Find. + * + * @return nothing + */ + async Remove() { + Logger.AssertTrue(this.props.guid, "Invalid guid during Remove"); + await PlacesUtils.bookmarks.remove(this.props.guid); + }, + + /** + * Update + * + * Updates this bookmark's properties according the properties on this + * object's 'updateProps' property. + * + * @return nothing + */ + async Update() { + Logger.AssertTrue(this.props.guid, "Invalid guid during Update"); + await this.SetLocation(this.updateProps.location); + await this.SetPosition(this.updateProps.position); + await this.SetTitle(this.updateProps.folder); + }, +}; + +extend(BookmarkFolder, PlacesItem); + +/** + * Separator class constructor. Initializes instance properties. + */ +export function Separator(props) { + PlacesItem.call(this, props); + this.props.type = "separator"; +} + +/** + * Separator instance methods. + */ +Separator.prototype = { + /** + * Create + * + * Creates the bookmark separator described by this object's properties. + * + * @return the id of the created separator + */ + async Create() { + this.props.parentGuid = await this.GetOrCreateFolder(this.props.location); + Logger.AssertTrue( + this.props.parentGuid, + "Unable to create " + + "folder, error creating parent folder " + + this.props.location + ); + let { guid } = await PlacesUtils.bookmarks.insert({ + parentGuid: this.props.parentGuid, + type: PlacesUtils.bookmarks.TYPE_SEPARATOR, + }); + this.props.guid = guid; + return guid; + }, + + /** + * Find + * + * Locates the bookmark separator which corresponds to this object's + * properties. + * + * @return the item guid if the separator was found, otherwise null + */ + async Find() { + this.props.parentGuid = await this.GetFolder(this.props.location); + if (this.props.parentGuid == null) { + Logger.logError("Unable to find folder " + this.props.location); + return null; + } + if (this.props.before == null && this.props.last_item_pos == null) { + Logger.logPotentialError( + "Separator requires 'before' attribute if it's the" + + "first item in the list" + ); + return null; + } + let expected_pos = -1; + if (this.props.before) { + let otherGuid = this.GetPlacesChildGuid( + this.props.parentGuid, + null, + this.props.before + ); + if (otherGuid == null) { + Logger.logPotentialError( + "Can't find places item " + + this.props.before + + " for locating separator" + ); + return null; + } + expected_pos = (await PlacesUtils.bookmarks.fetch(otherGuid)).index - 1; + } else { + expected_pos = this.props.last_item_pos + 1; + } + // Note these are IDs instead of GUIDs. + let children = await PlacesSyncUtils.bookmarks.fetchChildRecordIds( + this.props.parentGuid + ); + this.props.guid = children[expected_pos]; + if (this.props.guid == null) { + Logger.logPotentialError( + "No separator found at position " + expected_pos + ); + return null; + } + let info = await PlacesUtils.bookmarks.fetch(this.props.guid); + if (info.type != PlacesUtils.bookmarks.TYPE_SEPARATOR) { + Logger.logPotentialError( + "Places item at position " + expected_pos + " is not a separator" + ); + return null; + } + return this.props.guid; + }, + + /** + * Update + * + * Updates this separator's properties according the properties on this + * object's 'updateProps' property. + * + * @return nothing + */ + async Update() { + Logger.AssertTrue(this.props.guid, "Invalid guid during Update"); + await this.SetLocation(this.updateProps.location); + await this.SetPosition(this.updateProps.position); + return true; + }, + + /** + * Remove + * + * Removes this separator. The separator should have been located + * previously by a call to Find. + * + * @return nothing + */ + async Remove() { + Logger.AssertTrue(this.props.guid, "Invalid guid during Update"); + await PlacesUtils.bookmarks.remove(this.props.guid); + }, +}; + +extend(Separator, PlacesItem); |