Merge lp://qastaging/~uriboni/webbrowser-app/newtabview-listviews into lp://qastaging/webbrowser-app

Proposed by Ugo Riboni
Status: Needs review
Proposed branch: lp://qastaging/~uriboni/webbrowser-app/newtabview-listviews
Merge into: lp://qastaging/webbrowser-app
Prerequisite: lp://qastaging/~artmello/webbrowser-app/webbrowser-app-bookmarks_view
Diff against target: 2461 lines (+1569/-407)
25 files modified
src/app/webbrowser/BookmarksFoldersView.qml (+151/-140)
src/app/webbrowser/BookmarksFoldersViewWide.qml (+1/-2)
src/app/webbrowser/BookmarksHeader.qml (+89/-0)
src/app/webbrowser/BookmarksSection.qml (+74/-0)
src/app/webbrowser/CMakeLists.txt (+2/-0)
src/app/webbrowser/NewTabView.qml (+50/-120)
src/app/webbrowser/UrlsList.qml (+40/-22)
src/app/webbrowser/bookmarks-folderlist-model.cpp (+5/-0)
src/app/webbrowser/bookmarks-folderlist-model.h (+1/-0)
src/app/webbrowser/bookmarks-model.cpp (+12/-0)
src/app/webbrowser/bookmarks-model.h (+5/-1)
src/app/webbrowser/list-aggregator-model.cpp (+240/-0)
src/app/webbrowser/list-aggregator-model.h (+82/-0)
src/app/webbrowser/roles-adapter-model.cpp (+111/-0)
src/app/webbrowser/roles-adapter-model.h (+50/-0)
src/app/webbrowser/webbrowser-app.cpp (+4/-0)
tests/autopilot/webbrowser_app/emulators/browser.py (+48/-17)
tests/autopilot/webbrowser_app/tests/test_bookmark_options.py (+15/-15)
tests/autopilot/webbrowser_app/tests/test_new_tab_view.py (+97/-89)
tests/unittests/CMakeLists.txt (+2/-0)
tests/unittests/bookmarks-model/tst_BookmarksModelTests.cpp (+6/-1)
tests/unittests/list-aggregator-model/CMakeLists.txt (+13/-0)
tests/unittests/list-aggregator-model/tst_ListAggregatorModelTests.cpp (+361/-0)
tests/unittests/roles-adapter-model/CMakeLists.txt (+13/-0)
tests/unittests/roles-adapter-model/tst_RolesAdapterModelTests.cpp (+97/-0)
To merge this branch: bzr merge lp://qastaging/~uriboni/webbrowser-app/newtabview-listviews
Reviewer Review Type Date Requested Status
system-apps-ci-bot continuous-integration Needs Fixing
PS Jenkins bot continuous-integration Needs Fixing
Olivier Tilloy Needs Fixing
Review via email: mp+274986@code.qastaging.launchpad.net

Commit message

Refactor BookmarksFoldersView to use a ListView, so performance is not dependent on number of bookmarks. Use this view properly within NewTabView.

Description of the change

Refactor BookmarksFoldersView to use a ListView, so performance is not dependent on number of bookmarks. Use this view properly within NewTabView.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1250. By Ugo Riboni

Fix AP tests

1251. By Ugo Riboni

Merge changes from trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1252. By Ugo Riboni

Fix more AP tests

1253. By Ugo Riboni

Merge changes from trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
1254. By Ugo Riboni

Merge changes from trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
1255. By Ugo Riboni

Merge changes from trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Olivier Tilloy (osomon) wrote :

This works, but is a rather complex implementation.

I’ve taken a stab at something much simpler that doesn’t involve aggregator models: the idea is to use a SortFilterModel indeed to sort bookmarks by folder, but instead of filtering out bookmarks for which the folder is collapsed (which as you pointed out wouldn’t work because the corresponding section wouldn’t be displayed by the list view), I’m keeping track of expanded/collapsed state of each section in a separate dictionary, and delegates in the list view are visible or not depending on that state.

This results in much simpler code, with no need for custom C++ models, all is done in QML.

The only thing that this doesn’t achieve, afaict, is prepending the homepage to the model as a hardcoded bookmark. I’ve been thinking about that, and in fact I think modifying the model is the wrong approach. We can leverage the 'header' property of ListView to display a first hardcoded item that’s not in the model.

See a fully functional standalone example at http://pastebin.ubuntu.com/13009316/.
Comments welcome.

Revision history for this message
Olivier Tilloy (osomon) wrote :

I did some profiling on my laptop, comparing the performance of this branch and trunk with a large bookmarks database.

The first set of tests was with a database of 10000 bookmarks (100 folders with 100 URLs in each). In that case, this branch performs slightly better than trunk. The binding on internal.seeMoreBookmarksView for the 'active' property of bookmarksFolderListViewLoader takes 260ms to execute with this branch, vs 330ms with trunk. From a user perspective, there’s no perceived difference, the view takes about half a second to load and be responsive with both branches.

With a database of 100000 bookmarks (100 folders with 1000 URLs in each), trunk performs significantly better than this branch. The binding on internal.seeMoreBookmarksView for the 'active' property of bookmarksFolderListViewLoader takes 2.2s to execute with this branch, vs 1.9s with trunk. But more importantly, it takes ~7s for the view to load and be responsive with this branch, compared to ~3s with trunk.

If we want to optimize for large bookmark databases, this branch doesn’t appear to be improving things, quite the contrary. All of this is anecdotal when we look at the time it takes for the application itself to launch and become responsive though. With 100000 bookmarks, the app takes ~20s to launch. So if we really want to optimize for this use case, we need to remove the loading of the model from the critical startup path, before doing any optimization work on the bookmarks view.

The databases I used for profiling are available there: http://people.canonical.com/~osomon/lotsofbookmarks/.

review: Needs Fixing
Revision history for this message
Olivier Tilloy (osomon) wrote :

Small correction: the application takes ~20s to launch only if the current tab is a new tab view (e.g. if I pressed Ctrl+Tab in the last browsing session, then exited the browser, and launched it again). If it’s a normal tab (with a page loaded), there’s no noticeable slowdown (which is good).

Revision history for this message
Olivier Tilloy (osomon) wrote :

I have profiled the top-level bookmarks view (was profiling the one embedded in the new tab view before), and I’m seeing similar results.

Revision history for this message
Olivier Tilloy (osomon) wrote :

I have profiled the solution I was suggesting in my first comment. With a database of 100000 bookmarks, instantiating the entire view takes 600ms, and it is instantly responsive.

It works well when all sections are expanded, but it slows down considerably when all sections are collapsed (presumably because then there are 1000 invisible delegates instantiated for each section, so if there are 10 collapsed sections on screen that’s 10000 delegates instantiated at any given time).

On top of that the layout of the view is incorrect after expanding/collapsing a section, because the implementation of ListView computes an estimate of its contentHeight when using sections, instead of using the real content height.

Nested list views may not be such a bad idea after all…

1256. By Ugo Riboni

Remove another startup bottleneck by turning the UrlsList into a ListView fed by a LimitProxyModel with the home page bookmarks aggregated at the start. This keeps NewTabView lean at startup.

1257. By Ugo Riboni

Make sure that data change signals from the aggregated models are propagated correctly

1258. By Ugo Riboni

Fix some AP tests after changes in UrlsList

1259. By Ugo Riboni

Fix one more stray reference to a non-singleton BookmarksModel

Revision history for this message
Ugo Riboni (uriboni) wrote :

> I have profiled the solution I was suggesting in my first comment. With a
> database of 100000 bookmarks, instantiating the entire view takes 600ms, and
> it is instantly responsive.
>
> It works well when all sections are expanded, but it slows down considerably
> when all sections are collapsed (presumably because then there are 1000
> invisible delegates instantiated for each section, so if there are 10
> collapsed sections on screen that’s 10000 delegates instantiated at any given
> time).

All the solutions you proposed are not removing the main problem, which is that the performance is still linearly proportional to the number of bookmarks. By making the delegates leaner you are making the degrade in performance slower as the number of bookmarks grow, but the point is that we can't control the number of bookmarks, so the problem is never really solved.

Moreover you are testing with 1K bookmarks per folder, while you will see the app being completely unusable if you put 10K bookmarks in a folder and try to open it (or put them in the root folder, which is open when you open the view).

> On top of that the layout of the view is incorrect after expanding/collapsing
> a section, because the implementation of ListView computes an estimate of its
> contentHeight when using sections, instead of using the real content height.

Hiding delegates by making their height zero is essentially an hack, in my opinion: it works in some cases but it is not robust and/or scalable, and denies what is arguably the main benefit of using ListView in the first place: delegates are created and destroyed on-demand.

> Nested list views may not be such a bad idea after all…

How would you go about implementing nested ListViews ? A ListView to work needs to know the height of its delegates, and if each delegate is a ListView then they need to have height equal to contentHeight, which instantiates all the sub-list delegates. You can limit the height of each sub-ListView to a certain fixed amount, but then you have scrolling problems because either you scroll the main ListView or the sub-ListView.

Revision history for this message
Ugo Riboni (uriboni) wrote :

> If we want to optimize for large bookmark databases, this branch doesn’t
> appear to be improving things, quite the contrary. All of this is anecdotal
> when we look at the time it takes for the application itself to launch and
> become responsive though. With 100000 bookmarks, the app takes ~20s to launch [when the new tab view is visible]

The problem was that the collapsed bookmarks view was still a Column+Repeater+Loader and that was causing the long startup. This has been solved by use of a ListView backed by a LimitProxyModel.

With this change, I can't find any major bottleneck that prevent the app to be usable in any condition.

Revision history for this message
Ugo Riboni (uriboni) wrote :

> The binding on internal.seeMoreBookmarksView for the 'active' property of
> bookmarksFolderListViewLoader takes 2.2s to execute with this branch, vs 1.9s
> with trunk. But more importantly, it takes ~7s for the view to load and be
> responsive with this branch, compared to ~3s with trunk.

This bottleneck is essentially the time that it takes to expand the bookmarks view.
I did some tests with your 100K model, which has 100 folders, and another model which has 100K bookmarks but no folders.
Yours took 220ms on my machine, the other 8ms.

What is causing the difference is having to allocate each single folder/section placeholder model.

So basically the problem is now shifted from making the performance proportional to the number of bookmarks, to having the performance proportional to the number of folders. It is less of a problem but of course still a problem since we can't control the number of folders that the user can create.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1260. By Ugo Riboni

Prevent the home bookmark from being deleted

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1261. By Ugo Riboni

Fix the utility method removing the first real bookmark in the list by taking into account the peculiarity of working with a LimitProxyModel

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
1262. By Ugo Riboni

Merge changes from trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Olivier Tilloy (osomon) wrote :

308 + removable: url != homeBookmarkUrl

This condition is not correct: what if the user has added the homepage as a bookmark, manually? They should be allowed to remove it. It would probably be more correct to make that depend on the index (i.e. only the first in the list cannot be removed).

Can we consider adding the homepage bookmark to the BookmarksModel at the C++ level, instead of jumping through hoops to prepend it in javascript? We could add a 'removable' role to the model.

Revision history for this message
Olivier Tilloy (osomon) wrote :

Reflecting further on the problem we’re trying to solve, I just had an idea (just thinking out loud here, let’s see if you think it can fly):

 How about writing a custom proxy model that uses the BookmarksModel as input, and outputs a list that contains folders (collapsed or expanded) and bookmarks, in the expected order? There would be an additional role that describes what an entry is (is it a bookmark, is it a collapsed folder, or an expanded folder?). The model would have to keep track of the expanded/status state of each folder, and there would be a method to toggle the expanded/collapsed state of a folder (which would result in row insertions/removals). With this, a simple list view (with no section headers) would suffice, the appearance of the delegates would depend on their nature.

If I understand correctly what you did, the idea is similar, but much less generic than your implementation, and all done in C++ as opposed to a mix of C++ and QML/JS. It also doesn’t use sections, which should make it simpler (and maybe more efficient).

This would also allow adding the hardcoded homepage bookmark without modifying the source BookmarksModel.

How does that sound? Have I overlooked important details?

Revision history for this message
Olivier Tilloy (osomon) wrote :

Here is the merge request that implements the idea I described above: https://code.launchpad.net/~osomon/webbrowser-app/bookmarks-proxy-model/+merge/279277.

With a single model, only the initial instantiation of the model is costly (260ms for 100000 entries, 40ms for 10000 entries), there’s no additional overhead.

The code ended up slightly more complex than I anticipated, because I made it possible to change at runtime whether to show empty folders, and whether to prepend the homepage bookmark. That’s more code and complexity, but it’s 100% unit tested, and shields us from future design changes.

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :
review: Needs Fixing (continuous-integration)

Unmerged revisions

1262. By Ugo Riboni

Merge changes from trunk

1261. By Ugo Riboni

Fix the utility method removing the first real bookmark in the list by taking into account the peculiarity of working with a LimitProxyModel

1260. By Ugo Riboni

Prevent the home bookmark from being deleted

1259. By Ugo Riboni

Fix one more stray reference to a non-singleton BookmarksModel

1258. By Ugo Riboni

Fix some AP tests after changes in UrlsList

1257. By Ugo Riboni

Make sure that data change signals from the aggregated models are propagated correctly

1256. By Ugo Riboni

Remove another startup bottleneck by turning the UrlsList into a ListView fed by a LimitProxyModel with the home page bookmarks aggregated at the start. This keeps NewTabView lean at startup.

1255. By Ugo Riboni

Merge changes from trunk

1254. By Ugo Riboni

Merge changes from trunk

1253. By Ugo Riboni

Merge changes from trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
The diff is not available at this time. You can reload the page or download it.

Subscribers

People subscribed via source and target branches

to status/vote changes: