tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

0004-top-sites-feature.md (4805B)



layout: page title: Introduce a Top Sites Feature permalink: /rfc/0004-top-sites-feature


Summary

Create a TopSitesFeature that abstracts out the co-ordination between the multiple data storages.

Motivation

A large part of the top sites implementation is spread across A-C and Fenix. There is a lot of co-ordination between the History API and the current Top Sites API to manage the final state of what sites should be displayed that can exist as a single implementation.

Guide-level explanation

To begin, we should rename the TopSitesStorage to PinnedSitesStorage and create a TopSitesFeature that would orchestrate the different storages and present them to the app.

We can then follow the architecture of existing features by using the presenter/interactor/view model, and introduce a TopSitesStorage which abstract out the complexity of the multiple data sources from PinnedSitesStorage and PlacesHistoryStorage.

To allow the ability for adding a top site from different parts of the app (e.g. context menus), we introduce the PinnedSitesUseCases that acts on the storage directly. If the TopSitesFeature is started, it will then be notified by the Observer to update the UI.


/**
 * Implemented by the application for displaying onto the UI.
 */
interface TopSitesView {
  /**
   * Update the UI.
   */
  fun displaySites(sites: List<TopSite>)

  interface Listener {
    /**
     * Invoked by the UI for us to add to our storage.
     */
    fun onAddTopSite(topSite: TopSite)

    /**
     * Invoked by the UI for us to remove from our storage.
     */
    fun onRemoveTopSite(topSite: TopSite)
  }
}

/**
 * Abstraction layer above the multiple storages.
 */
interface TopSitesStorage {

  /**
   * Return a unified list of top sites based on the given number of sites desired.
   * If `includeFrecent` is true, fill in any missing top sites with frecent top site results.
   */
  suspend fun getTopSites(totalNumberOfSites: Int, includeFrecent: Boolean): List<TopSite>

  interface Observer {
    /**
     * Invoked when changes are made to the storage.
     */
    fun onStorageUpdated()
  }
}

// Already exists in browser-storage-sync.
interface PlacesHistoryStorage {
  fun getTopFrecentSites(num: Int)
}

class DefaultTopSitesStorage(
  val pinnedSitesStorage: PinnedSitesStorage,
  val historyStorage: PlacesHistoryStorage
) : TopSitesStorage {

  /**
   * Merge data sources here, return a single list of top sites.
   */
  override suspend fun getTopSites(totalNumberOfSites: Int, includeFrecent: Boolean): List<TopSite>
}

/**
 * Use cases can be used for adding a pinned site from different places like a context menu.
 */
class PinnedSitesUseCases(pinnedSitesStorage: PinnedSitesStorage) {
  val addPinnedSites: AddPinnedSiteUseCase
  val removePinnedSites: RemovePinnedSiteUseCase
}

/**
 * View-bound feature that updates the UI when changes are made.
 */
class TopSitesFeature(
  val storage: TopSitesStorage,
  val presenter: TopSitesPresenter,
  val view: TopSitesView,
  val defaultSites: () -> List<TopSites>
) : LifecycleAwareFeature {
  override fun start()
  override fun stop()
}

Drawbacks

Rationale and alternatives

Prior art

Unresolved questions