Closed Bug 1341541 Opened 7 years ago Closed 7 years ago

WebExtensions bookmarks.getRecent() results include raw tag objects

Categories

(WebExtensions :: Untriaged, defect, P2)

54 Branch
defect

Tracking

(firefox55 fixed)

RESOLVED FIXED
mozilla55
Tracking Status
firefox55 --- fixed

People

(Reporter: jerry, Assigned: bsilverberg)

Details

(Whiteboard: [bookmarks]triaged)

Attachments

(1 file)

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.23 (KHTML, like Gecko) Version/10.1 Safari/603.1.23

Steps to reproduce:

• Craft a WebExtension which, upon loading, invokes browser.bookmarks.getRecent() with a parameter of "3", meaning that it should return the 3 most recently-bookmarked bookmarks.  The API https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/bookmarks/getRecent is clear that this function "retrieves a specified number of the most recently added **bookmarks**" [emphasis added].
• Run Firefox 51, or Firefox 54 Nightly 2017-02-10.
• Add three new bookmarks.
• Add a tag to the last new bookmark that you just added.
• Load the extension.
• Look at the console and see what was retrieved by getRecent().


Actual results:

An array of 3 objects is returned as expected, but only two are the newest bookmarks that you added.  The third is an object with no URL whose title is the name of the tag you added – it's the tag!


Expected results:

It should have got all *three* new bookmarks with no mention of any tags.

* * *

Apparently, this issue was realized during the development of Bug 1251269, because in toolkit/components/places/Bookmarks.jsm, in function fetchRecentBookmarks(numberOfItems), line 1198, the query contains this clause:

WHERE p.parent <> :tags_folder

which is obviously intended to exclude tags from the result.  But it's not working for some reason.
Component: Untriaged → WebExtensions: Untriaged
Product: Firefox → Toolkit
Assignee: nobody → bob.silverberg
Flags: needinfo?(bob.silverberg)
Whiteboard: [bookmarks], investigate
I have been able to reproduce this, and I've looked at the code and I'm not sure why it's not working as expected. Marco, could you please take a look at the code for `fetchRecentBookmarks` in Bookmarks.jsm [1] to see if you can tell why it is including tags, when I have attempted to code it to exclude them, using similar logic to `fetchBookmarksByURL` [2], for example.

[1] http://searchfox.org/mozilla-central/source/toolkit/components/places/Bookmarks.jsm#1186
[2] http://searchfox.org/mozilla-central/source/toolkit/components/places/Bookmarks.jsm#1162
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true
Flags: needinfo?(bob.silverberg) → needinfo?(mak77)
Priority: -- → P2
Whiteboard: [bookmarks], investigate → [bookmarks]triaged
off-hand looks like the query is also returning folders and separators. I don't know if that's intended.
the tags that are being returned seems to be the tag folders?

I don't know if the API is intended to return folders, if not it should AND type = BOOKMARK, otherwise it should _also_ AND b.parent <> :tags_folder
Flags: needinfo?(mak77)
Hello, Marco.

Regarding folders and separators: The API documentation https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/bookmarks/getRecent specifies that this function shall return "most recently added bookmarks".  It would be nice if that documentation were more explicit, but I'd say that, in this context, that means it should *not* include folders, nor separators.

Regarding "tag folders":  Yes, tags are in fact represented in moz_bookmark as folders, that is, rows whose `type` column is TYPE_FOLDER=2.  And this is indeed what I see being returned.

Regarding the WHERE clause in the query:  I tried adding the type = BOOKMARK clause you suggested.  That is, I changed line 1198 to

       WHERE p.parent <> :tags_folder AND b.type = :type_bookmark

and defined type_bookmark by changing line 1201 to

      `, { tags_folder: PlacesUtils.tagsFolderId, type_bookmark: Bookmarks.TYPE_BOOKMARK, numberOfItems });

With these changes, the result no longer contains those "tag folders".  Good!

However, the getRecent() result still contains the hidden smart folders which descend from that "All Bookmarks" folder.  As I recall from previous work, a query which will exclude all of this garbage requires also checking parent id against some of these.
Thanks for the bug report, Jerry, and your additional observations. I have finished a patch which I believe fixes the issue entirely. It is now attached to this bug. If you want to give it a try and see if it fixes all of the issues you are seeing that would be great.
Comment on attachment 8842986 [details]
Bug 1341541 - WebExtensions bookmarks.getRecent() results include raw tag objects

https://reviewboard.mozilla.org/r/116716/#review118470

Test changes look reasonable, but I am not very familiar with the places db stuff so expect other review for that.
Attachment #8842986 - Flags: review?(mixedpuppy) → review+
Thank you, Bob.

I have tested your patch and indeed you have found and fixed this bug – no more tags.  However the six garbage hidden smart folders, or whatever they are, still appear in getRecent() results.  These six items apparently serve some internal purpose in Firefox, do not appear in the user interface, and therefore should not appear in getRecent() results.

I tested using a small set of bookmarks – the default bookmarks in a new Firefox profile, plus one bookmark that I added with two tags.  My places.sqlite dumps something like this:

? id           guid   title                     type    fk parent position
- --   ------------   ------------------------- ----  ---- ------ --------
z  1   root________                                2   NULL     0        0
z  2   menu________   Bookmarks Menu               2   NULL     1        0
z  3   toolbar_____   Bookmarks Toolbar            2   NULL     1        1
z  4   tags________   Tags                         2   NULL     1        2
z  5   unfiled_____   Other Bookmarks              2   NULL     1        3
z  6   mobile______   mobile                       2   NULL     1        4
+  7   Hj322gIlkrsS   Get Involved                 1      1     3        1
z  8   QwNSEkp1Cm2o   Firefox Nightly Resources    2   NULL     2        2
+  9   3GkHrh41MXtd   Firefox Nightly blog         1      2     8        0
+ 10   MHz792dMnaZf   Mozilla Bug Tracker          1      3     8        1
+ 14   4NIi92Z5p5KW   Discuss Nightly on IRC       1      7     8        2
+ 16   qKZbOcbVSaox   Most Visited                 1      9     3        0
+ 17   59-dsKmX0Oom   Recent Tags                  1     10     2        0
z 18   5M86We-KwYo9   NULL                         3   NULL     2        1
z 19   o3tTQpjze_7L                                2   NULL     1        5
- 20   eLqTj_9cgyNd   History                      1     11    19        0
- 21   LGeGmVaqsQOy   Downloads                    1     12    19        1
- 22   czH7f87fqVlU   Tags                         1     13    19        2
z 23   VKM92jydd9Fr   All Bookmarks                2   NULL    19        3
- 24   6-D-HYpyJGBx   NULL                         1     14    23        0
- 25   Gc-5kOU4jyPs   NULL                         1     15    23        1
- 26   JniVnF2ObIAy   NULL                         1     16    23        2
+ 27   XN_fxhuJXu4_   My Bookmark                  1     18     5        0
z 40   wM9iVfdd9ALL   MyTag1                       2   NULL     4        0
z 41   sFNiptGY0IFf   NULL                         1     18    40        0
z 42   Fu73YHel9Esk   MyTag2                       2   NULL     4        1
z 43   QAxAieGQM-eJ   NULL                         1     18    42        0

where I added the first "?" column manually.  Here is what that column means:

z = should not and does not appear in getRecent() results.  Good.
+ = should and does appear in getRecent()results.  Good.
- = should not but does appear in getRecent() results.  Bad.

I was able to get rid of the six undesired "-" items by adding two more lines to Bookmarks.jsm:

1200    AND b.parent <> :all_bookmarks_folder
1201    AND b.parent <> :shadow_root

However I don't know the constants (if there even are any) for those "All Bookmarks" and (what I call the) "shadow root" items.  You can see them  at rows 23 and 19 in my places.sqlite dump.  To do a proof of concept, I cheated and hard-coded their id, so my line 1205 is now this:

`, { tags_folder: PlacesUtils.tagsFolderId, all_bookmarks_folder:23, shadow_root:19, type: Bookmarks.TYPE_BOOKMARK, numberOfItems });

and voila now getRecent() works all as expected.

Do you want to deal with that in this bug, or should I copy and paste the above information into a new bug?
Flags: needinfo?(bob.silverberg)
Thanks for the info, Jerry. I think we can keep this in this bug, and just treat this bug as trying to get getRecent() working properly.

Marco, can you please take a look at Jerry's comments in comment #7 and let me know what you think? I'm still not that familiar with the places database, so I'm not sure what these extraneous items are, or whether Jerry's approach to eliminating them from the results makes sense.
Flags: needinfo?(bob.silverberg) → needinfo?(mak77)
Those are "smart" queries, things like "place:folder=XYZ" or such.
I agree we may not want these.

To exclude those you should add to the query:
AND url_hash NOT BETWEEN hash("place", "prefix_lo")
                     AND hash("place", "prefix_hi")

Let me clarify what this does. It would potentially be enough to check that url doesn't begin with "place:", but for space reasons we dropped the url index and instead added an url hash. The problem is that an index on the url hash doesn't allow to quickly match schemas, and for some queries we needed that. So the hash has been built as [ 16bit schema hash + 32bit url hash). The hash("place", "prefix_lo") builds a [ 16bit "place" hash + 0x00] while hash("place", "prefix_hi") builds [ 16bit "place" hash + 0xFF]. The between thus allows to select or exclude hashes for urls like place:XXX.
Flags: needinfo?(mak77)
Please, update the javadoc to clarify the API excludes folders, separators and queries, and add a test for those to toolkit/components/places/tests/bookmarks (where there's already the existing getRecent test)
Thanks Marco. This is ready for another review.
Comment on attachment 8842986 [details]
Bug 1341541 - WebExtensions bookmarks.getRecent() results include raw tag objects

https://reviewboard.mozilla.org/r/116716/#review119240

::: toolkit/components/places/Bookmarks.jsm:1200
(Diff revision 2)
>                NULL AS _syncStatus
>         FROM moz_bookmarks b
>         LEFT JOIN moz_bookmarks p ON p.id = b.parent
>         LEFT JOIN moz_places h ON h.id = b.fk
>         WHERE p.parent <> :tags_folder
> +       AND b.parent <> :tags_folder

Checking b.parent is no more needed if you already check type = BOOKMARK, since :tags_folder can only contain folders.

::: toolkit/components/places/Bookmarks.jsm:1203
(Diff revision 2)
>         LEFT JOIN moz_places h ON h.id = b.fk
>         WHERE p.parent <> :tags_folder
> +       AND b.parent <> :tags_folder
> +       AND b.type = :type
> +       AND url_hash NOT BETWEEN hash("place", "prefix_lo")
> +                    AND hash("place", "prefix_hi")

nit: align the 2 "hash" calls, adding more indentation before AND

::: toolkit/components/places/Bookmarks.jsm:1206
(Diff revision 2)
> +       AND b.type = :type
> +       AND url_hash NOT BETWEEN hash("place", "prefix_lo")
> +                    AND hash("place", "prefix_hi")
>         ORDER BY b.dateAdded DESC, b.ROWID DESC
>         LIMIT :numberOfItems
> -      `, { tags_folder: PlacesUtils.tagsFolderId, numberOfItems });
> +      `, { tags_folder: PlacesUtils.tagsFolderId, type: Bookmarks.TYPE_BOOKMARK, numberOfItems });

nit: this should go multi-line for the 80-chars soft limit of the coding style.

::: toolkit/components/places/tests/bookmarks/test_bookmarks_getRecent.js:42
(Diff revision 2)
> +  // Add a query bookmark.
> +  let queryURL = `place:folder=${PlacesUtils.bookmarksMenuFolderId}&queryType=1`;
> +  let bm5 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
> +                                                 url: queryURL,
> +                                                 title: "a test query" });
> +  checkBookmarkObject(bm5);

I'd also add a separator to the test, just in case.
Attachment #8842986 - Flags: review?(mak77) → review+
Pushed by bsilverberg@mozilla.com:
https://hg.mozilla.org/integration/autoland/rev/353df4a8b88c
WebExtensions bookmarks.getRecent() results include raw tag objects, r=mak,mixedpuppy
https://hg.mozilla.org/mozilla-central/rev/353df4a8b88c
Status: ASSIGNED → RESOLVED
Closed: 7 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla55
Product: Toolkit → WebExtensions
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: