| // Copyright 2011 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "chrome/browser/ui/cocoa/applescript/bookmark_folder_applescript.h" |
| |
| #include "base/strings/sys_string_conversions.h" |
| #import "chrome/browser/ui/cocoa/applescript/bookmark_item_applescript.h" |
| #import "chrome/browser/ui/cocoa/applescript/constants_applescript.h" |
| #include "chrome/browser/ui/cocoa/applescript/error_applescript.h" |
| #include "components/bookmarks/browser/bookmark_model.h" |
| #include "components/bookmarks/common/bookmark_metrics.h" |
| #include "url/gurl.h" |
| |
| using bookmarks::BookmarkModel; |
| using bookmarks::BookmarkNode; |
| |
| // /!\ Warning |
| // |
| // The design of the AppleScript dictionary with regards to bookmarks is |
| // deficient. The ideal design would mirror the design of the actual bookmark |
| // system, where a bookmark element could be either a folder or an item, and |
| // bookmark folders could hold any number of them, in any order. |
| // |
| // However, that's not the design that is implemented. The current design is |
| // that bookmark folders and bookmark items are separate and unrelated things, |
| // and that bookmark folders have a list of bookmark folders they contain, as |
| // well as a list of bookmark items they contain. |
| // |
| // That is, there are _two separate lists_. |
| // |
| // Translating from the real bookmarks system, where the children of a folder |
| // are a list of intermingled folders and items, is easy: walk the list of |
| // children, and filter out the wrong kind. (See `-bookmarkFolders` and |
| // `-bookmarkItems`.) |
| // |
| // Translating _to_ the real bookmarks system cannot be done in the general |
| // case. There is no control over the mingling of folders and items, and there |
| // is only vague control over the ordering of items that share a type. |
| // |
| // All this is to explain the difference in terminology between "index" and |
| // "bookmark manager position". An "index" value is relative to the two separate |
| // child lists that exist from the AppleScript perspective. The "bookmark |
| // manager position" is relative to the true list of (both types of) children of |
| // a folder in the bookmarks system. |
| // |
| // Because AppleScript maintains no state, no translation ever needs to be done |
| // from positions to indexes. AppleScript keeps object identifiers, and if it |
| // ever needs to update the index, it will re-request the list and find the item |
| // again by matching its ID. |
| // |
| // However, when a script requests a folder or bookmark to be moved around or |
| // inserted, AppleScript will request a change in index. That index will need to |
| // be converted into a position in the real list of children of a folder, and |
| // that's what the `-bookmarkManagerPositionOf[Folder|Item]At:` methods are for. |
| |
| @interface BookmarkFolderAppleScript () |
| |
| // Does the actual insertion of a bookmark folder at an absolute position. |
| - (void)insertFolder:(BookmarkFolderAppleScript*)bookmarkFolder |
| atBookmarkManagerPosition:(size_t)position; |
| |
| // Does the actual insertion of a bookmark item at an absolute position. |
| - (void)insertItem:(BookmarkItemAppleScript*)bookmarkItem |
| atBookmarkManagerPosition:(size_t)position; |
| |
| // Returns the position of a bookmark folder within the current bookmark folder |
| // which consists of bookmark folders as well as bookmark items. |
| - (size_t)bookmarkManagerPositionOfFolderAt:(size_t)index; |
| |
| // Returns the position of a bookmark item within the current bookmark folder |
| // which consists of bookmark folders as well as bookmark items. |
| - (size_t)bookmarkManagerPositionOfItemAt:(size_t)index; |
| |
| @end |
| |
| @implementation BookmarkFolderAppleScript |
| |
| - (NSArray<BookmarkFolderAppleScript*>*)bookmarkFolders { |
| NSMutableArray<BookmarkFolderAppleScript*>* bookmarkFolders = |
| [NSMutableArray arrayWithCapacity:self.bookmarkNode->children().size()]; |
| |
| for (const auto& node : self.bookmarkNode->children()) { |
| if (!node->is_folder()) { |
| continue; |
| } |
| |
| BookmarkFolderAppleScript* bookmarkFolder = |
| [[BookmarkFolderAppleScript alloc] initWithBookmarkNode:node.get()]; |
| [bookmarkFolder setContainer:self |
| property:AppleScript::kBookmarkFoldersProperty]; |
| [bookmarkFolders addObject:bookmarkFolder]; |
| } |
| |
| return bookmarkFolders; |
| } |
| |
| - (NSArray<BookmarkItemAppleScript*>*)bookmarkItems { |
| NSMutableArray<BookmarkItemAppleScript*>* bookmarkItems = |
| [NSMutableArray arrayWithCapacity:self.bookmarkNode->children().size()]; |
| |
| for (const auto& node : self.bookmarkNode->children()) { |
| if (!node->is_url()) { |
| continue; |
| } |
| |
| BookmarkItemAppleScript* bookmarkItem = |
| [[BookmarkItemAppleScript alloc] initWithBookmarkNode:node.get()]; |
| [bookmarkItem setContainer:self |
| property:AppleScript::kBookmarkItemsProperty]; |
| [bookmarkItems addObject:bookmarkItem]; |
| } |
| |
| return bookmarkItems; |
| } |
| |
| - (void)insertInBookmarkFolders:(BookmarkFolderAppleScript*)bookmarkFolder { |
| [self insertFolder:bookmarkFolder |
| atBookmarkManagerPosition:self.bookmarkNode->children().size()]; |
| } |
| |
| - (void)insertInBookmarkFolders:(BookmarkFolderAppleScript*)bookmarkFolder |
| atIndex:(size_t)index { |
| [self insertFolder:bookmarkFolder |
| atBookmarkManagerPosition:[self bookmarkManagerPositionOfFolderAt:index]]; |
| } |
| |
| - (void)insertFolder:(BookmarkFolderAppleScript*)bookmarkFolder |
| atBookmarkManagerPosition:(size_t)position { |
| [bookmarkFolder setContainer:self |
| property:AppleScript::kBookmarkFoldersProperty]; |
| |
| BookmarkModel* model = self.bookmarkModel; |
| if (!model) { |
| return; |
| } |
| |
| const BookmarkNode* node = model->AddFolder( |
| self.bookmarkNode, position, |
| /*title=*/std::u16string(), /*meta_info=*/nullptr, |
| /*creation_time=*/std::nullopt, bookmarkFolder.bookmarkGUID); |
| if (!node) { |
| AppleScript::SetError(AppleScript::Error::kCreateBookmarkFolder); |
| return; |
| } |
| |
| [bookmarkFolder didCreateBookmarkNode:node]; |
| } |
| |
| - (void)removeFromBookmarkFoldersAtIndex:(size_t)index { |
| size_t position = [self bookmarkManagerPositionOfFolderAt:index]; |
| |
| BookmarkModel* model = self.bookmarkModel; |
| if (!model) { |
| return; |
| } |
| |
| model->Remove(self.bookmarkNode->children()[position].get(), |
| bookmarks::metrics::BookmarkEditSource::kUser, FROM_HERE); |
| } |
| |
| - (void)insertInBookmarkItems:(BookmarkItemAppleScript*)bookmarkItem { |
| [self insertItem:bookmarkItem |
| atBookmarkManagerPosition:self.bookmarkNode->children().size()]; |
| } |
| |
| - (void)insertInBookmarkItems:(BookmarkItemAppleScript*)bookmarkItem |
| atIndex:(size_t)index { |
| [self insertItem:bookmarkItem |
| atBookmarkManagerPosition:[self bookmarkManagerPositionOfItemAt:index]]; |
| } |
| |
| - (void)insertItem:(BookmarkItemAppleScript*)bookmarkItem |
| atBookmarkManagerPosition:(size_t)position { |
| [bookmarkItem setContainer:self property:AppleScript::kBookmarkItemsProperty]; |
| |
| BookmarkModel* model = self.bookmarkModel; |
| if (!model) { |
| return; |
| } |
| |
| GURL url(base::SysNSStringToUTF8(bookmarkItem.URL)); |
| if (!url.is_valid()) { |
| AppleScript::SetError(AppleScript::Error::kInvalidURL); |
| return; |
| } |
| |
| const BookmarkNode* node = model->AddURL( |
| self.bookmarkNode, position, /*title=*/std::u16string(), url, |
| /*meta_info=*/nullptr, /*creation_time=*/std::nullopt, |
| bookmarkItem.bookmarkGUID, /*added_by_user=*/true); |
| if (!node) { |
| AppleScript::SetError(AppleScript::Error::kCreateBookmarkItem); |
| return; |
| } |
| |
| [bookmarkItem didCreateBookmarkNode:node]; |
| } |
| |
| - (void)removeFromBookmarkItemsAtIndex:(size_t)index { |
| size_t position = [self bookmarkManagerPositionOfItemAt:index]; |
| |
| BookmarkModel* model = self.bookmarkModel; |
| if (!model) { |
| return; |
| } |
| |
| model->Remove(self.bookmarkNode->children()[position].get(), |
| bookmarks::metrics::BookmarkEditSource::kUser, FROM_HERE); |
| } |
| |
| - (size_t)bookmarkManagerPositionOfFolderAt:(size_t)index { |
| // Traverse through all the child nodes until the required node is found and |
| // return its position. |
| // AppleScript is 1-based therefore index is incremented by 1. |
| ++index; |
| size_t count = 0; |
| while (index) { |
| if (self.bookmarkNode->children()[count++]->is_folder()) { |
| --index; |
| } |
| } |
| return count - 1; |
| } |
| |
| - (size_t)bookmarkManagerPositionOfItemAt:(size_t)index { |
| // Traverse through all the child nodes until the required node is found and |
| // return its position. |
| // AppleScript is 1-based therefore index is incremented by 1. |
| ++index; |
| size_t count = 0; |
| while (index) { |
| if (self.bookmarkNode->children()[count++]->is_url()) { |
| --index; |
| } |
| } |
| return count - 1; |
| } |
| |
| @end |