Skip to content

Commit

Permalink
NTP: Prevent data loss in topSites after update
Browse files Browse the repository at this point in the history
Fix brave/brave-browser#8781
Top sites (now called grid sites) now have their
own storage. This change allows the migration
process from a shared to a dedicated storage.
  • Loading branch information
cezaraugusto committed Mar 27, 2020
1 parent 5b2313f commit ab3f78f
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 18 deletions.
44 changes: 37 additions & 7 deletions components/brave_new_tab_ui/helpers/newTabUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ export const getCharForSite = (
return name.charAt(0).toUpperCase()
}

export const generateGridSiteId = (currentIndex: number): string => {
return `topsite-${currentIndex}-${Date.now()}`
export const generateGridSiteId = (): string => {
const randomNumber = Math.floor(Math.random() * 10000)
return `topsite-${randomNumber}-${Date.now()}`
}

export const generateGridSiteFavicon = (url: string): string => {
Expand Down Expand Up @@ -53,21 +54,25 @@ export const isExistingGridSite = (

export const generateGridSiteProperties = (
index: number,
topSite: chrome.topSites.MostVisitedURL
topSite: chrome.topSites.MostVisitedURL,
fromLegacyData?: boolean
): NewTab.Site => {
return {
...topSite,
id: generateGridSiteId(index),
title: topSite.title,
url: topSite.url,
id: generateGridSiteId(),
letter: getCharForSite(topSite),
favicon: generateGridSiteFavicon(topSite.url),
pinnedIndex: undefined,
// In the legacy version of topSites the pinnedIndex
// was the site index itself.
pinnedIndex: fromLegacyData ? index : undefined,
bookmarkInfo: undefined
}
}

export const getGridSitesWhitelist = (
topSites: chrome.topSites.MostVisitedURL[]
): chrome.topSites.MostVisitedURL[] => {
): chrome.topSites.MostVisitedURL[] => {
const defaultChromeWebStoreUrl: string = 'https://chrome.google.com/webstore'
const filteredGridSites: chrome.topSites.MostVisitedURL[] = topSites
.filter(site => {
Expand All @@ -76,3 +81,28 @@ export const getGridSitesWhitelist = (
})
return filteredGridSites
}

export const generateGridSitesFromLegacyEntries = (legacyTopSites: NewTab.LegacySite[]) => {
const newGridSites: NewTab.Site[] = []
if (legacyTopSites == null) {
return []
}
for (const topSite of legacyTopSites) {
newGridSites
.push(generateGridSiteProperties(topSite.index, topSite, true))
}

return newGridSites
}

export function filterFromExcludedSites (
sitesData: NewTab.Site[],
removedSitesData: NewTab.Site[]
): NewTab.Site[] {
return sitesData
.filter((site: NewTab.Site) => {
// In updatedGridSites we only want sites not removed by the user
return removedSitesData
.every((removedSite: NewTab.Site) => removedSite.url !== site.url)
})
}
15 changes: 15 additions & 0 deletions components/brave_new_tab_ui/reducers/grid_sites_reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ export const gridSitesReducer: Reducer<NewTab.GridSitesState | undefined> = (

switch (action.type) {
case types.GRID_SITES_SET_FIRST_RENDER_DATA: {

// If there are legacy values from a previous
// storage, update first render data with it
state = gridSitesState
.gridSitesReducerSetFirstRenderDataFromLegacy(
state,
startingState.legacy
)
// Now that we stored the legacy reference, delete it
// so it won't override gridSites in further updates
if (startingState.legacy) {
delete startingState.legacy
}

// New profiles just store what comes from Chromium
state = gridSitesState
.gridSitesReducerSetFirstRenderData(state, payload.topSites)
break
Expand Down
36 changes: 28 additions & 8 deletions components/brave_new_tab_ui/state/gridSitesState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,28 @@ import {
isExistingGridSite,
getGridSitesWhitelist,
isGridSitePinned,
isGridSiteBookmarked
isGridSiteBookmarked,
filterFromExcludedSites
} from '../helpers/newTabUtils'

export function gridSitesReducerSetFirstRenderDataFromLegacy (
state: NewTab.GridSitesState,
legacyState: NewTab.LegacyState
) {
const { ignoredTopSites, pinnedTopSites } = legacyState

if (ignoredTopSites.length > 0) {
for (const ignoredTopSite of ignoredTopSites) {
state = gridSitesReducerRemoveSite(state, ignoredTopSite)
}
}

if (pinnedTopSites) {
state = gridSitesReducerAddSiteOrSites(state, pinnedTopSites)
}
return state
}

export function gridSitesReducerSetFirstRenderData (
state: NewTab.GridSitesState,
topSites: chrome.topSites.MostVisitedURL[]
Expand All @@ -25,7 +44,12 @@ export function gridSitesReducerSetFirstRenderData (
}
newGridSites.push(generateGridSiteProperties(index, topSite))
}
state = gridSitesReducerAddSiteOrSites(state, newGridSites)

// If there are removed sites coming from a legacy storage,
// ensure they get filtered.
const sitesToAdd = filterFromExcludedSites(newGridSites, state.removedSites)
state = gridSitesReducerAddSiteOrSites(state, sitesToAdd)

return state
}

Expand Down Expand Up @@ -87,12 +111,8 @@ export function gridSitesReducerRemoveSite (
removedSites: [ ...state.removedSites, removedSite ]
}

const filterRemovedFromGridSites = state.gridSites
.filter((site: NewTab.Site) => {
// In updatedGridSites we only want sites not removed by the user
return state.removedSites
.every((removedSite: NewTab.Site) => removedSite.url !== site.url)
})
const filterRemovedFromGridSites =
filterFromExcludedSites(state.gridSites, state.removedSites)
state = gridSitesReducerDataUpdated(state, filterRemovedFromGridSites)
return state
}
Expand Down
20 changes: 18 additions & 2 deletions components/brave_new_tab_ui/storage/grid_sites_storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,29 @@

// Utils
import { debounce } from '../../common/debounce'
import { keyName as newTabKeyName } from './new_tab_storage'
import { generateGridSitesFromLegacyEntries } from '../helpers/newTabUtils'
const keyName = 'grid-sites-data-v1'

const keyName = 'grid-sites-data'
const newTabData: any = window.localStorage.getItem(newTabKeyName)
const parsedNewTabData = JSON.parse(newTabData)

export const initialGridSitesState: NewTab.GridSitesState = {
gridSites: [],
removedSites: [],
shouldShowSiteRemovedNotification: false
shouldShowSiteRemovedNotification: false,
legacy: {
// Store legacy pinnedTopSites so users
// migrating to this new storage won't lose
// data. Once this change hits the release channel
// we are safe to remove this bridge
pinnedTopSites: generateGridSitesFromLegacyEntries(
parsedNewTabData.pinnedTopSites
),
ignoredTopSites: generateGridSitesFromLegacyEntries(
parsedNewTabData.ignoredTopSites
)
}
}

export const load = (): NewTab.GridSitesState => {
Expand Down
2 changes: 1 addition & 1 deletion components/brave_new_tab_ui/storage/new_tab_storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Utils
import { debounce } from '../../common/debounce'

const keyName = 'new-tab-data'
export const keyName = 'new-tab-data'

export const defaultState: NewTab.State = {
initialDataLoaded: false,
Expand Down
21 changes: 21 additions & 0 deletions components/definitions/newTab.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ declare namespace NewTab {
bookmarkInfo: chrome.bookmarks.BookmarkTreeNode | undefined
}

// This is preserved for migration reasons.
// Do not tyoe new code using this interface.
export interface LegacySite {
index: number
url: string
title: string
favicon: string
letter: string
thumb: string
themeColor: string
computedThemeColor: string
pinned: boolean
bookmarked?: Bookmark
}

export interface Stats {
adsBlockedStat: number
javascriptBlockedStat: number
Expand All @@ -58,10 +73,16 @@ declare namespace NewTab {

export type StackWidget = 'rewards' | 'binance'

export interface LegacyState {
pinnedTopSites: Site[]
ignoredTopSites: Site[]
}

export interface GridSitesState {
removedSites: Site[]
gridSites: Site[]
shouldShowSiteRemovedNotification: boolean
legacy: LegacyState
}

export interface PageState {
Expand Down

0 comments on commit ab3f78f

Please sign in to comment.