From 472463a1fd9db9280d9669565be1e7e41b73cfee Mon Sep 17 00:00:00 2001 From: Aubrey Keus Date: Sun, 30 Oct 2016 16:39:04 -0400 Subject: [PATCH] Insert virtual history items for sites like www.google.ca When a site is visted for a deep linked page or with parameters the history item is stored with the complete parameters and links. Often after a user visits the site a number of times, the expectation shifts such that the simple domain should be the first suggestion in the url bar suggestion list. The canonical example is when a user searches Google a number of times via the url bar. After three searches www.google.com will be added as a virtual history item in the search results. Auditors: @bbondy, @bradrichter Test Plan: 1. Clear history 2. End three searchs from the url bar without accessing www.google.com 3. Open a new tab and type goog - ensure a history item for www.google.com is displayed Fixes: #5067 --- app/renderer/lib/suggestion.js | 63 ++++++++++++++++++++++++++++++ js/components/urlBarSuggestions.js | 29 ++++++++------ test/unit/lib/urlSuggestionTest.js | 35 +++++++++++++++++ 3 files changed, 116 insertions(+), 11 deletions(-) diff --git a/app/renderer/lib/suggestion.js b/app/renderer/lib/suggestion.js index 9be2dc7bc97..7d65971dc64 100644 --- a/app/renderer/lib/suggestion.js +++ b/app/renderer/lib/suggestion.js @@ -4,6 +4,8 @@ const urlParser = require('url') const appConfig = require('../../../js/constants/appConfig') +const _ = require('underscore') +const Immutable = require('immutable') const sigmoid = (t) => { return 1 / (1 + Math.pow(Math.E, -t)) @@ -112,3 +114,64 @@ module.exports.normalizeLocation = (location) => { location = location.replace(/^https:\/\//, '') return location } + +/* + * return a site representing the simple location for a + * set of related sites without a history item for the + * simple location. + * + * This is used to show a history suggestion for something + * like www.google.com if it has not been visited but + * there are two or more locations with that prefix containing + * path info or parameters + * + * @param {Array[Object]} sites - array of similar sites + */ +var virtualSite = (sites) => { + // array of sites without paths or query params + var simple = sites.filter((parsed) => { + return (parsed.hash === null && parsed.search === null && parsed.query === null && parsed.pathname === '/') + }) + // if there are no simple locations then we will build and return one + if (simple.length === 0) { + // we need to create a virtual history item + return Immutable.Map({ + location: sites[0].protocol + '//' + sites[0].host, + count: 0, + title: sites[0].host, + lastAccessedTime: (new Date()).getTime() + }) + } else { + return + } +} + +/* + * Create an array of simple locations from history + * The simple locations will be the root domain for a location + * without parameters or path + * + * @param {ImmutableList[ImmutableMap]} - history + */ +module.exports.createVirtualHistoryItems = (historySites) => { + // parse each history item + var parsedHistorySites = historySites.map((site) => { + return urlParser.parse(site.get('location')) + }).toArray() + // group them by host + var grouped = _.groupBy(parsedHistorySites, (parsedSite) => { + return parsedSite.host || 'unknown' + }) + // find groups with more than 2 of the same host + var multiGroupKeys = _.filter(_.keys(grouped), (k) => { + return grouped[k].length > 2 + }) + // potentially create virtual history items + var virtualHistorySites = _.map(multiGroupKeys, (location) => { + return virtualSite(grouped[location]) + }) + virtualHistorySites = _.filter(virtualHistorySites, (site) => { + return !!site + }) + return virtualHistorySites +} diff --git a/js/components/urlBarSuggestions.js b/js/components/urlBarSuggestions.js index 0b794eb6b65..f67061146c5 100644 --- a/js/components/urlBarSuggestions.js +++ b/js/components/urlBarSuggestions.js @@ -281,10 +281,26 @@ class UrlBarSuggestions extends ImmutableComponent { } } + const historyFilter = (site) => { + const title = site.get('title') || '' + const location = site.get('location') || '' + // Note: Bookmark sites are now included in history. This will allow + // sites to appear in the auto-complete regardless of their bookmark + // status. If history is turned off, bookmarked sites will appear + // in the bookmark section. + return (title.toLowerCase().includes(urlLocationLower) || + location.toLowerCase().includes(urlLocationLower)) + } + var historySites = props.sites.filter(historyFilter) + + // potentially append virtual history items (such as www.google.com when + // searches have been made but the root site has not been visited) + historySites = historySites.concat(suggestion.createVirtualHistoryItems(historySites)) + // history if (getSetting(settings.HISTORY_SUGGESTIONS)) { suggestions = suggestions.concat(mapListToElements({ - data: props.sites, + data: historySites, maxResults: config.urlBarSuggestions.maxHistorySites, type: suggestionTypes.HISTORY, clickHandler: navigateClickHandler((site) => { @@ -293,16 +309,7 @@ class UrlBarSuggestions extends ImmutableComponent { sortHandler: sortBasedOnLocationPos, formatTitle: (site) => site.get('title'), formatUrl: (site) => site.get('location'), - filterValue: (site) => { - const title = site.get('title') || '' - const location = site.get('location') || '' - return (title.toLowerCase().includes(urlLocationLower) || - location.toLowerCase().includes(urlLocationLower)) - // Note: Bookmkark sites are now included in history. This will allow - // sites to appear in the auto-complete regardless of their bookmark - // status. If history is turned off, bookmarked sites will appear - // in the bookmark section. - } + filterValue: historyFilter })) } diff --git a/test/unit/lib/urlSuggestionTest.js b/test/unit/lib/urlSuggestionTest.js index 9bc5e62adaf..f6bb9055138 100644 --- a/test/unit/lib/urlSuggestionTest.js +++ b/test/unit/lib/urlSuggestionTest.js @@ -26,3 +26,38 @@ describe('suggestion', function () { assert.ok(suggestion.simpleDomainNameValue(siteComplex) === 0, 'complex site returns 0') }) }) + +const site1 = Immutable.Map({ + location: 'http://www.foo.com/1', + count: 0, + lastAccessedTime: 0, + title: 'www.foo/com/1' +}) + +const site2 = Immutable.Map({ + location: 'http://www.foo.com/2', + count: 0, + lastAccessedTime: 0, + title: 'www.foo/com/2' +}) + +const site3 = Immutable.Map({ + location: 'http://www.foo.com/3', + count: 0, + lastAccessedTime: 0, + title: 'www.foo/com/3' +}) + +describe('suggestion', function () { + it('shows virtual history item', function () { + var history = Immutable.List([site1, site2, site3]) + var virtual = suggestion.createVirtualHistoryItems(history) + assert.ok(virtual.length > 0, 'virtual location created') + assert.ok(virtual[0].get('location') === 'http://www.foo.com') + assert.ok(virtual[0].get('title') === 'www.foo.com') + assert.ok(virtual[0].get('lastAccessedTime') > 0) + history = Immutable.List([site1, site2]) + virtual = suggestion.createVirtualHistoryItems(history) + assert.ok(virtual.length === 0, 'virtual location not created') + }) +})