--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -71,16 +71,20 @@ var whitelist = new Set([
{file: "resource://gre/greprefs.js"},
// browser/extensions/pdfjs/content/web/viewer.js
{file: "resource://pdf.js/build/pdf.worker.js"},
// Add-on API introduced in bug 1118285
{file: "resource://app/modules/NewTabURL.jsm"},
+ // browser/components/newtab bug 1355166
+ {file: "resource://app/modules/NewTabSearchProvider.jsm"},
+ {file: "resource://app/modules/NewTabWebChannel.jsm"},
+
// layout/mathml/nsMathMLChar.cpp
{file: "resource://gre/res/fonts/mathfontSTIXGeneral.properties"},
{file: "resource://gre/res/fonts/mathfontUnicode.properties"},
// toolkit/components/places/ColorAnalyzer_worker.js
{file: "resource://gre/modules/ClusterLib.js"},
{file: "resource://gre/modules/ColorConversion.js"},
deleted file mode 100644
--- a/browser/components/newtab/NewTabMessages.jsm
+++ /dev/null
@@ -1,242 +0,0 @@
-/* global
- NewTabWebChannel,
- NewTabPrefsProvider,
- PlacesProvider,
- PreviewProvider,
- NewTabSearchProvider,
- Preferences,
- XPCOMUtils,
- Task
-*/
-
-/* exported NewTabMessages */
-
-"use strict";
-
-const {utils: Cu} = Components;
-Cu.import("resource://gre/modules/Preferences.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesProvider",
- "resource:///modules/PlacesProvider.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PreviewProvider",
- "resource:///modules/PreviewProvider.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
- "resource:///modules/NewTabPrefsProvider.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NewTabSearchProvider",
- "resource:///modules/NewTabSearchProvider.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
- "resource:///modules/NewTabWebChannel.jsm");
-
-this.EXPORTED_SYMBOLS = ["NewTabMessages"];
-
-const PREF_ENABLED = "browser.newtabpage.remote";
-const CURRENT_ENGINE = "browser-search-engine-modified";
-
-// Action names are from the content's perspective. in from chrome == out from content
-// Maybe replace the ACTION objects by a bi-directional Map a bit later?
-const ACTIONS = {
- inboundActions: [
- "REQUEST_PREFS",
- "REQUEST_THUMB",
- "REQUEST_FRECENT",
- "REQUEST_UISTRINGS",
- "REQUEST_SEARCH_SUGGESTIONS",
- "REQUEST_MANAGE_ENGINES",
- "REQUEST_SEARCH_STATE",
- "REQUEST_REMOVE_FORM_HISTORY",
- "REQUEST_PERFORM_SEARCH",
- "REQUEST_CYCLE_ENGINE",
- ],
- prefs: {
- inPrefs: "REQUEST_PREFS",
- outPrefs: "RECEIVE_PREFS",
- },
- preview: {
- inThumb: "REQUEST_THUMB",
- outThumb: "RECEIVE_THUMB",
- },
- links: {
- inFrecent: "REQUEST_FRECENT",
- outFrecent: "RECEIVE_FRECENT",
- outPlacesChange: "RECEIVE_PLACES_CHANGE",
- },
- search: {
- inSearch: {
- UIStrings: "REQUEST_UISTRINGS",
- suggestions: "REQUEST_SEARCH_SUGGESTIONS",
- manageEngines: "REQUEST_MANAGE_ENGINES",
- state: "REQUEST_SEARCH_STATE",
- removeFormHistory: "REQUEST_REMOVE_FORM_HISTORY",
- performSearch: "REQUEST_PERFORM_SEARCH",
- cycleEngine: "REQUEST_CYCLE_ENGINE"
- },
- outSearch: {
- UIStrings: "RECEIVE_UISTRINGS",
- suggestions: "RECEIVE_SEARCH_SUGGESTIONS",
- state: "RECEIVE_SEARCH_STATE",
- currentEngine: "RECEIVE_CURRENT_ENGINE"
- },
- }
-};
-
-let NewTabMessages = {
-
- _prefs: {},
-
- /** NEWTAB EVENT HANDLERS **/
-
- handleContentRequest(actionName, {data, target}) {
- switch (actionName) {
- case ACTIONS.prefs.inPrefs:
- // Return to the originator all newtabpage prefs
- let results = NewTabPrefsProvider.prefs.newtabPagePrefs;
- NewTabWebChannel.send(ACTIONS.prefs.outPrefs, results, target);
- break;
- case ACTIONS.preview.inThumb:
- // Return to the originator a preview URL
- PreviewProvider.getThumbnail(data).then(imgData => {
- NewTabWebChannel.send(ACTIONS.preview.outThumb, {url: data, imgData}, target);
- });
- break;
- case ACTIONS.links.inFrecent:
- // Return to the originator the top frecent links
- PlacesProvider.links.getLinks().then(links => {
- NewTabWebChannel.send(ACTIONS.links.outFrecent, links, target);
- });
- break;
- case ACTIONS.search.inSearch.UIStrings:
- // Return to the originator all search strings to display
- let strings = NewTabSearchProvider.search.searchSuggestionUIStrings;
- NewTabWebChannel.send(ACTIONS.search.outSearch.UIStrings, strings, target);
- break;
- case ACTIONS.search.inSearch.suggestions:
- // Return to the originator all search suggestions
- Task.spawn(function*() {
- try {
- let {engineName, searchString} = data;
- let suggestions = yield NewTabSearchProvider.search.asyncGetSuggestions(engineName, searchString, target);
- NewTabWebChannel.send(ACTIONS.search.outSearch.suggestions, suggestions, target);
- } catch (e) {
- Cu.reportError(e);
- }
- });
- break;
- case ACTIONS.search.inSearch.manageEngines:
- // Open about:preferences to manage search state
- NewTabSearchProvider.search.manageEngines(target.browser);
- break;
- case ACTIONS.search.inSearch.state:
- // Return the state of the search component (i.e current engine and visible engine details)
- Task.spawn(function*() {
- try {
- let state = yield NewTabSearchProvider.search.asyncGetState();
- NewTabWebChannel.broadcast(ACTIONS.search.outSearch.state, state);
- } catch (e) {
- Cu.reportError(e);
- }
- });
- break;
- case ACTIONS.search.inSearch.removeFormHistory:
- // Remove a form history entry from the search component
- let suggestion = data;
- NewTabSearchProvider.search.removeFormHistory(target, suggestion);
- break;
- case ACTIONS.search.inSearch.performSearch:
- // Perform a search
- NewTabSearchProvider.search.asyncPerformSearch(target, data).catch(Cu.reportError);
- break;
- case ACTIONS.search.inSearch.cycleEngine:
- // Set the new current engine
- NewTabSearchProvider.search.asyncCycleEngine(data).catch(Cu.reportError);
- break;
- }
- },
-
- /*
- * Broadcast places change to all open newtab pages
- */
- handlePlacesChange(type, data) {
- NewTabWebChannel.broadcast(ACTIONS.links.outPlacesChange, {type, data});
- },
-
- /*
- * Broadcast current engine has changed to all open newtab pages
- */
- _handleCurrentEngineChange(name, value) { // jshint unused: false
- let engine = value;
- NewTabWebChannel.broadcast(ACTIONS.search.outSearch.currentEngine, engine);
- },
-
- /*
- * Broadcast preference changes to all open newtab pages
- */
- handlePrefChange(actionName, value) {
- let prefChange = {};
- prefChange[actionName] = value;
- NewTabWebChannel.broadcast(ACTIONS.prefs.outPrefs, prefChange);
- },
-
- _handleEnabledChange(prefName, value) {
- if (prefName === PREF_ENABLED) {
- if (this._prefs.enabled && !value) {
- this.uninit();
- } else if (!this._prefs.enabled && value) {
- this.init();
- }
- }
- },
-
- init() {
- this.handleContentRequest = this.handleContentRequest.bind(this);
- this._handleEnabledChange = this._handleEnabledChange.bind(this);
- this._handleCurrentEngineChange = this._handleCurrentEngineChange.bind(this);
-
- PlacesProvider.links.init();
- NewTabPrefsProvider.prefs.init();
- NewTabSearchProvider.search.init();
- NewTabWebChannel.init();
-
- this._prefs.enabled = Preferences.get(PREF_ENABLED, false);
-
- if (this._prefs.enabled) {
- for (let action of ACTIONS.inboundActions) {
- NewTabWebChannel.on(action, this.handleContentRequest);
- }
-
- NewTabPrefsProvider.prefs.on(PREF_ENABLED, this._handleEnabledChange);
- NewTabSearchProvider.search.on(CURRENT_ENGINE, this._handleCurrentEngineChange);
-
- for (let pref of NewTabPrefsProvider.newtabPagePrefSet) {
- NewTabPrefsProvider.prefs.on(pref, this.handlePrefChange);
- }
-
- PlacesProvider.links.on("deleteURI", this.handlePlacesChange);
- PlacesProvider.links.on("clearHistory", this.handlePlacesChange);
- PlacesProvider.links.on("linkChanged", this.handlePlacesChange);
- PlacesProvider.links.on("manyLinksChanged", this.handlePlacesChange);
- }
- },
-
- uninit() {
- this._prefs.enabled = Preferences.get(PREF_ENABLED, false);
-
- if (this._prefs.enabled) {
- NewTabPrefsProvider.prefs.off(PREF_ENABLED, this._handleEnabledChange);
- NewTabSearchProvider.search.off(CURRENT_ENGINE, this._handleCurrentEngineChange);
-
- for (let action of ACTIONS.inboundActions) {
- NewTabWebChannel.off(action, this.handleContentRequest);
- }
-
- for (let pref of NewTabPrefsProvider.newtabPagePrefSet) {
- NewTabPrefsProvider.prefs.off(pref, this.handlePrefChange);
- }
- }
-
- NewTabPrefsProvider.prefs.uninit();
- NewTabSearchProvider.search.uninit();
- NewTabWebChannel.uninit();
- }
-};
deleted file mode 100644
--- a/browser/components/newtab/PlacesProvider.jsm
+++ /dev/null
@@ -1,244 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/* global XPCOMUtils, Services, PlacesUtils, EventEmitter */
-/* global gLinks */
-/* exported PlacesProvider */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["PlacesProvider"];
-
-const {interfaces: Ci, utils: Cu} = Components;
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
- "resource://gre/modules/PlacesUtils.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
- const {EventEmitter} = Cu.import("resource://gre/modules/EventEmitter.jsm", {});
- return EventEmitter;
-});
-
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
- "resource://gre/modules/Task.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
- "resource://gre/modules/NewTabUtils.jsm");
-
-// The maximum number of results PlacesProvider retrieves from history.
-const HISTORY_RESULTS_LIMIT = 100;
-
-/* Queries history to retrieve the most visited sites. Emits events when the
- * history changes.
- * Implements the EventEmitter interface.
- */
-let Links = function Links() {
- EventEmitter.decorate(this);
-};
-
-Links.prototype = {
- /**
- * Set this to change the maximum number of links the provider will provide.
- */
- get maxNumLinks() {
- // getter, so it can't be replaced dynamically
- return HISTORY_RESULTS_LIMIT;
- },
-
- /**
- * A set of functions called by @mozilla.org/browser/nav-historyservice
- * All history events are emitted from this object.
- */
- historyObserver: {
- _batchProcessingDepth: 0,
- _batchCalledFrecencyChanged: false,
-
- /**
- * Called by the history service.
- */
- onBeginUpdateBatch() {
- this._batchProcessingDepth += 1;
- },
-
- onEndUpdateBatch() {
- this._batchProcessingDepth -= 1;
- if (this._batchProcessingDepth == 0 && this._batchCalledFrecencyChanged) {
- this.onManyFrecenciesChanged();
- this._batchCalledFrecencyChanged = false;
- }
- },
-
- onDeleteURI: function historyObserver_onDeleteURI(aURI) {
- // let observers remove sensetive data associated with deleted visit
- gLinks.emit("deleteURI", {
- url: aURI.spec,
- });
- },
-
- onClearHistory: function historyObserver_onClearHistory() {
- gLinks.emit("clearHistory");
- },
-
- onFrecencyChanged: function historyObserver_onFrecencyChanged(aURI,
- aNewFrecency, aGUID, aHidden, aLastVisitDate) { // jshint ignore:line
-
- // If something is doing a batch update of history entries we don't want
- // to do lots of work for each record. So we just track the fact we need
- // to call onManyFrecenciesChanged() once the batch is complete.
- if (this._batchProcessingDepth > 0) {
- this._batchCalledFrecencyChanged = true;
- return;
- }
-
- // The implementation of the query in getLinks excludes hidden and
- // unvisited pages, so it's important to exclude them here, too.
- if (!aHidden && aLastVisitDate &&
- NewTabUtils.linkChecker.checkLoadURI(aURI.spec)) {
- gLinks.emit("linkChanged", {
- url: aURI.spec,
- frecency: aNewFrecency,
- lastVisitDate: aLastVisitDate,
- type: "history",
- });
- }
- },
-
- onManyFrecenciesChanged: function historyObserver_onManyFrecenciesChanged() {
- // Called when frecencies are invalidated and also when clearHistory is called
- // See toolkit/components/places/tests/unit/test_frecency_observers.js
- gLinks.emit("manyLinksChanged");
- },
-
- onVisit(aURI, aVisitId, aTime, aSessionId, aReferrerVisitId, aTransitionType,
- aGuid, aHidden, aVisitCount, aTyped, aLastKnownTitle) {
- // For new visits, if we're not batch processing, notify for a title update
- if (!this._batchProcessingDepth && aVisitCount == 1 && aLastKnownTitle) {
- this.onTitleChanged(aURI, aLastKnownTitle, aGuid);
- }
- },
-
- onTitleChanged: function historyObserver_onTitleChanged(aURI, aNewTitle) {
- if (NewTabUtils.linkChecker.checkLoadURI(aURI.spec)) {
- gLinks.emit("linkChanged", {
- url: aURI.spec,
- title: aNewTitle
- });
- }
- },
-
- QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver,
- Ci.nsISupportsWeakReference])
- },
-
- /**
- * Must be called before the provider is used.
- * Makes it easy to disable under pref
- */
- init: function PlacesProvider_init() {
- try {
- PlacesUtils.history.addObserver(this.historyObserver, true);
- } catch (e) {
- Cu.reportError(e);
- }
- },
-
- /**
- * Gets the current set of links delivered by this provider.
- *
- * @returns {Promise} Returns a promise with the array of links as payload.
- */
- getLinks: Task.async(function*() {
- // Select a single page per host with highest frecency, highest recency.
- // Choose N top such pages. Note +rev_host, to turn off optimizer per :mak
- // suggestion.
- let sqlQuery = `SELECT url, title, frecency,
- last_visit_date as lastVisitDate,
- "history" as type
- FROM moz_places
- WHERE frecency in (
- SELECT MAX(frecency) as frecency
- FROM moz_places
- WHERE hidden = 0 AND last_visit_date NOTNULL
- GROUP BY +rev_host
- ORDER BY frecency DESC
- LIMIT :limit
- )
- GROUP BY rev_host HAVING MAX(lastVisitDate)
- ORDER BY frecency DESC, lastVisitDate DESC, url`;
-
- let links = yield this.executePlacesQuery(sqlQuery, {
- columns: ["url", "title", "lastVisitDate", "frecency", "type"],
- params: {limit: this.maxNumLinks}
- });
-
- return links.filter(link => NewTabUtils.linkChecker.checkLoadURI(link.url));
- }),
-
- /**
- * Executes arbitrary query against places database
- *
- * @param {String} aSql
- * SQL query to execute
- * @param {Object} [optional] aOptions
- * aOptions.columns - an array of column names. if supplied the returned
- * items will consist of objects keyed on column names. Otherwise
- * an array of raw values is returned in the select order
- * aOptions.param - an object of SQL binding parameters
- * aOptions.callback - a callback to handle query rows
- *
- * @returns {Promise} Returns a promise with the array of retrieved items
- */
- executePlacesQuery: Task.async(function*(aSql, aOptions = {}) {
- let {columns, params, callback} = aOptions;
- let items = [];
- let queryError = null;
- let conn = yield PlacesUtils.promiseDBConnection();
- yield conn.executeCached(aSql, params, aRow => {
- try {
- // check if caller wants to handle query raws
- if (callback) {
- callback(aRow);
- } else {
- // otherwise fill in the item and add items array
- let item = null;
- // if columns array is given construct an object
- if (columns && Array.isArray(columns)) {
- item = {};
- columns.forEach(column => {
- item[column] = aRow.getResultByName(column);
- });
- } else {
- // if no columns - make an array of raw values
- item = [];
- for (let i = 0; i < aRow.numEntries; i++) {
- item.push(aRow.getResultByIndex(i));
- }
- }
- items.push(item);
- }
- } catch (e) {
- queryError = e;
- throw StopIteration;
- }
- });
- if (queryError) {
- throw new Error(queryError);
- }
- return items;
- }),
-};
-
-/**
- * Singleton that serves as the default link provider for the grid.
- */
-const gLinks = new Links(); // jshint ignore:line
-
-let PlacesProvider = {
- links: gLinks,
-};
-
-// Kept only for backwards-compatibility
-XPCOMUtils.defineLazyGetter(PlacesProvider, "LinkChecker",
- () => NewTabUtils.linkChecker);
--- a/browser/components/newtab/moz.build
+++ b/browser/components/newtab/moz.build
@@ -9,23 +9,21 @@ with Files("**"):
BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
XPCSHELL_TESTS_MANIFESTS += [
'tests/xpcshell/xpcshell.ini',
]
EXTRA_JS_MODULES += [
- 'NewTabMessages.jsm',
'NewTabPrefsProvider.jsm',
'NewTabRemoteResources.jsm',
'NewTabSearchProvider.jsm',
'NewTabURL.jsm',
'NewTabWebChannel.jsm',
- 'PlacesProvider.jsm',
'PreviewProvider.jsm'
]
XPIDL_SOURCES += [
'nsIAboutNewTabService.idl',
]
XPIDL_MODULE = 'browser-newtab'
deleted file mode 100644
--- a/browser/components/newtab/tests/xpcshell/test_PlacesProvider.js
+++ /dev/null
@@ -1,357 +0,0 @@
-"use strict";
-
-/* global XPCOMUtils, PlacesUtils, PlacesTestUtils, PlacesProvider, NetUtil */
-/* global do_get_profile, run_next_test, add_task */
-/* global equal, ok */
-
-const {
- utils: Cu,
- interfaces: Ci,
-} = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesProvider",
- "resource:///modules/PlacesProvider.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
- "resource://gre/modules/PlacesUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
- "resource://testing-common/PlacesTestUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
- "resource://gre/modules/NetUtil.jsm");
-
-// ensure a profile exists
-do_get_profile();
-
-function run_test() {
- PlacesProvider.links.init();
- run_next_test();
-}
-
-// url prefix for test history population
-const TEST_URL = "https://mozilla.com/";
-// time when the test starts execution
-const TIME_NOW = new Date();
-
-// utility function to compute past timestap
-function timeDaysAgo(numDays) {
- return new Date(TIME_NOW - (numDays * 24 * 60 * 60 * 1000));
-}
-
-// utility function to make a visit for insetion into places db
-function makeVisit(index, daysAgo, isTyped, domain = TEST_URL) {
- let {
- TRANSITION_TYPED,
- TRANSITION_LINK
- } = PlacesUtils.history;
-
- return {
- uri: NetUtil.newURI(`${domain}${index}`),
- visitDate: timeDaysAgo(daysAgo),
- transition: (isTyped) ? TRANSITION_TYPED : TRANSITION_LINK,
- };
-}
-
-/** Test LinkChecker **/
-
-add_task(function test_LinkChecker_securityCheck() {
-
- let urls = [
- {url: "javascript:alert('hello')", expected: false}, // jshint ignore:line
- {url: "data:image/png;base64,XXX", expected: false},
- {url: "about:newtab", expected: true},
- {url: "https://example.com", expected: true},
- {url: "ftp://example.com", expected: true},
- {url: "file://home/file/image.png", expected: true},
- {url: "resource:///modules/PlacesProvider.jsm", expected: true},
- ];
- for (let {url, expected} of urls) {
- let observed = PlacesProvider.LinkChecker.checkLoadURI(url);
- equal(observed, expected, `can load "${url}"?`);
- }
-});
-
-/** Test Provider **/
-
-add_task(function* test_Links_getLinks() {
- yield PlacesTestUtils.clearHistory();
- let provider = PlacesProvider.links;
-
- let links = yield provider.getLinks();
- equal(links.length, 0, "empty history yields empty links");
-
- // add a visit
- let testURI = NetUtil.newURI("http://mozilla.com");
- yield PlacesTestUtils.addVisits(testURI);
-
- links = yield provider.getLinks();
- equal(links.length, 1, "adding a visit yields a link");
- equal(links[0].url, testURI.spec, "added visit corresponds to added url");
-});
-
-add_task(function* test_Links_getLinks_Order() {
- yield PlacesTestUtils.clearHistory();
- let provider = PlacesProvider.links;
-
- // all four visits must come from different domains to avoid deduplication
- let visits = [
- makeVisit(0, 0, true, "http://bar.com/"), // frecency 200, today
- makeVisit(1, 0, true, "http://foo.com/"), // frecency 200, today
- makeVisit(2, 2, true, "http://buz.com/"), // frecency 200, 2 days ago
- makeVisit(3, 2, false, "http://aaa.com/"), // frecency 10, 2 days ago, transition
- ];
-
- let links = yield provider.getLinks();
- equal(links.length, 0, "empty history yields empty links");
- yield PlacesTestUtils.addVisits(visits);
-
- links = yield provider.getLinks();
- equal(links.length, visits.length, "number of links added is the same as obtain by getLinks");
- for (let i = 0; i < links.length; i++) {
- equal(links[i].url, visits[i].uri.spec, "links are obtained in the expected order");
- }
-});
-
-add_task(function* test_Links_getLinks_Deduplication() {
- yield PlacesTestUtils.clearHistory();
- let provider = PlacesProvider.links;
-
- // all for visits must come from different domains to avoid deduplication
- let visits = [
- makeVisit(0, 2, true, "http://bar.com/"), // frecency 200, 2 days ago
- makeVisit(1, 0, true, "http://bar.com/"), // frecency 200, today
- makeVisit(2, 0, false, "http://foo.com/"), // frecency 10, today
- makeVisit(3, 0, true, "http://foo.com/"), // frecency 200, today
- ];
-
- let links = yield provider.getLinks();
- equal(links.length, 0, "empty history yields empty links");
- yield PlacesTestUtils.addVisits(visits);
-
- links = yield provider.getLinks();
- equal(links.length, 2, "only two links must be left after deduplication");
- equal(links[0].url, visits[1].uri.spec, "earliest link is present");
- equal(links[1].url, visits[3].uri.spec, "most fresent link is present");
-});
-
-add_task(function* test_Links_onLinkChanged() {
- let provider = PlacesProvider.links;
-
- let url = "https://example.com/onFrecencyChanged1";
- let linkChangedMsgCount = 0;
-
- let linkChangedPromise = new Promise(resolve => {
- let handler = (_, link) => { // jshint ignore:line
- /* There are 2 linkChanged events:
- * 1. visit insertion (-1 frecency by default)
- * 2. frecency score update (after transition type calculation etc)
- */
- if (link.url === url) {
- equal(link.url, url, `expected url on linkChanged event`);
- linkChangedMsgCount += 1;
- if (linkChangedMsgCount === 2) {
- ok(true, `all linkChanged events captured`);
- provider.off("linkChanged", this);
- resolve();
- }
- }
- };
- provider.on("linkChanged", handler);
- });
-
- // add a visit
- let testURI = NetUtil.newURI(url);
- yield PlacesUtils.history.insert({
- url: testURI,
- visits: [{ transition: PlacesUtils.history.TRANSITIONS.LINK }]
- });
- yield linkChangedPromise;
-
- yield PlacesTestUtils.clearHistory();
-});
-
-add_task(function* test_Links_onClearHistory() {
- let provider = PlacesProvider.links;
-
- let clearHistoryPromise = new Promise(resolve => {
- let handler = () => {
- ok(true, `clearHistory event captured`);
- provider.off("clearHistory", handler);
- resolve();
- };
- provider.on("clearHistory", handler);
- });
-
- // add visits
- for (let i = 0; i <= 10; i++) {
- let url = `https://example.com/onClearHistory${i}`;
- let testURI = NetUtil.newURI(url);
- yield PlacesTestUtils.addVisits(testURI);
- }
- yield PlacesTestUtils.clearHistory();
- yield clearHistoryPromise;
-});
-
-add_task(function* test_Links_onDeleteURI() {
- let provider = PlacesProvider.links;
-
- let testURL = "https://example.com/toDelete";
-
- let deleteURIPromise = new Promise(resolve => {
- let handler = (_, {url}) => { // jshint ignore:line
- equal(testURL, url, "deleted url and expected url are the same");
- provider.off("deleteURI", handler);
- resolve();
- };
-
- provider.on("deleteURI", handler);
- });
-
- let testURI = NetUtil.newURI(testURL);
- yield PlacesTestUtils.addVisits(testURI);
- yield PlacesUtils.history.remove(testURL);
- yield deleteURIPromise;
-});
-
-add_task(function* test_Links_onManyLinksChanged() {
- let provider = PlacesProvider.links;
-
- let promise = new Promise(resolve => {
- let handler = () => {
- ok(true);
- provider.off("manyLinksChanged", handler);
- resolve();
- };
-
- provider.on("manyLinksChanged", handler);
- });
-
- let testURL = "https://example.com/toDelete";
- let testURI = NetUtil.newURI(testURL);
- yield PlacesTestUtils.addVisits(testURI);
-
- // trigger DecayFrecency
- PlacesUtils.history.QueryInterface(Ci.nsIObserver).
- observe(null, "idle-daily", "");
-
- yield promise;
-});
-
-add_task(function* test_Links_execute_query() {
- yield PlacesTestUtils.clearHistory();
- let provider = PlacesProvider.links;
-
- let visits = [
- makeVisit(0, 0, true), // frecency 200, today
- makeVisit(1, 0, true), // frecency 200, today
- makeVisit(2, 2, true), // frecency 200, 2 days ago
- makeVisit(3, 2, false), // frecency 10, 2 days ago, transition
- ];
-
- yield PlacesTestUtils.addVisits(visits);
-
- function testItemValue(results, index, value) {
- equal(results[index][0], `${TEST_URL}${value}`, "raw url");
- equal(results[index][1], `test visit for ${TEST_URL}${value}`, "raw title");
- }
-
- function testItemObject(results, index, columnValues) {
- Object.keys(columnValues).forEach(name => {
- equal(results[index][name], columnValues[name], "object name " + name);
- });
- }
-
- // select all 4 records
- let results = yield provider.executePlacesQuery("select url, title from moz_places");
- equal(results.length, 4, "expect 4 items");
- // check for insert order sequence
- for (let i = 0; i < results.length; i++) {
- testItemValue(results, i, i);
- }
-
- // test parameter passing
- results = yield provider.executePlacesQuery(
- "select url, title from moz_places limit :limit",
- {params: {limit: 2}}
- );
- equal(results.length, 2, "expect 2 items");
- for (let i = 0; i < results.length; i++) {
- testItemValue(results, i, i);
- }
-
- // test extracting items by name
- results = yield provider.executePlacesQuery(
- "select url, title from moz_places limit :limit",
- {columns: ["url", "title"], params: {limit: 4}}
- );
- equal(results.length, 4, "expect 4 items");
- for (let i = 0; i < results.length; i++) {
- testItemObject(results, i, {
- "url": `${TEST_URL}${i}`,
- "title": `test visit for ${TEST_URL}${i}`,
- });
- }
-
- // test ordering
- results = yield provider.executePlacesQuery(
- "select url, title, last_visit_date, frecency from moz_places " +
- "order by frecency DESC, last_visit_date DESC, url DESC limit :limit",
- {columns: ["url", "title", "last_visit_date", "frecency"], params: {limit: 4}}
- );
- equal(results.length, 4, "expect 4 items");
- testItemObject(results, 0, {url: `${TEST_URL}1`});
- testItemObject(results, 1, {url: `${TEST_URL}0`});
- testItemObject(results, 2, {url: `${TEST_URL}2`});
- testItemObject(results, 3, {url: `${TEST_URL}3`});
-
- // test callback passing
- results = [];
- function handleRow(aRow) {
- results.push({
- url: aRow.getResultByName("url"),
- title: aRow.getResultByName("title"),
- last_visit_date: aRow.getResultByName("last_visit_date"),
- frecency: aRow.getResultByName("frecency")
- });
- }
- yield provider.executePlacesQuery(
- "select url, title, last_visit_date, frecency from moz_places " +
- "order by frecency DESC, last_visit_date DESC, url DESC",
- {callback: handleRow}
- );
- equal(results.length, 4, "expect 4 items");
- testItemObject(results, 0, {url: `${TEST_URL}1`});
- testItemObject(results, 1, {url: `${TEST_URL}0`});
- testItemObject(results, 2, {url: `${TEST_URL}2`});
- testItemObject(results, 3, {url: `${TEST_URL}3`});
-
- // negative test cases
- // bad sql
- try {
- yield provider.executePlacesQuery("select from moz");
- do_throw("bad sql should've thrown");
- } catch (e) {
- do_check_true("expected failure - bad sql");
- }
- // missing bindings
- try {
- yield provider.executePlacesQuery("select * from moz_places limit :limit");
- do_throw("bad sql should've thrown");
- } catch (e) {
- do_check_true("expected failure - missing bidning");
- }
- // non-existent column name
- try {
- yield provider.executePlacesQuery("select * from moz_places limit :limit",
- {columns: ["no-such-column"], params: {limit: 4}});
- do_throw("bad sql should've thrown");
- } catch (e) {
- do_check_true("expected failure - wrong column name");
- }
-
- // cleanup
- yield PlacesTestUtils.clearHistory();
-});
--- a/browser/components/newtab/tests/xpcshell/xpcshell.ini
+++ b/browser/components/newtab/tests/xpcshell/xpcshell.ini
@@ -2,9 +2,8 @@
head =
firefox-appdir = browser
skip-if = toolkit == 'android'
[test_AboutNewTabService.js]
[test_NewTabPrefsProvider.js]
[test_NewTabSearchProvider.js]
[test_NewTabURL.js]
-[test_PlacesProvider.js]
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -22,17 +22,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
/* global AboutHome:false, AboutNewTab:false, AddonManager:false,
AsyncShutdown:false, AutoCompletePopup:false, BookmarkHTMLUtils:false,
BookmarkJSONUtils:false, BrowserUITelemetry:false, BrowserUsageTelemetry:false,
ContentClick:false, ContentPrefServiceParent:false, ContentSearch:false,
DateTimePickerHelper:false, DirectoryLinksProvider:false,
ExtensionsUI:false, Feeds:false,
FileUtils:false, FormValidationHandler:false, Integration:false,
LightweightThemeManager:false, LoginHelper:false, LoginManagerParent:false,
- NetUtil:false, NewTabMessages:false, NewTabUtils:false, OS:false,
+ NetUtil:false, NewTabUtils:false, OS:false,
PageThumbs:false, PdfJs:false, PermissionUI:false, PlacesBackups:false,
PlacesUtils:false, PluralForm:false, PrivateBrowsingUtils:false,
ProcessHangMonitor:false, ReaderParent:false, RecentWindow:false,
RemotePrompt:false, SelfSupportBackend:false, SessionStore:false,
ShellService:false, SimpleServiceDiscovery:false, TabCrashHandler:false,
Task:false, UITour:false, WebChannel:false,
WindowsRegistry:false, webrtcUI:false, UserAgentOverrides: false */
@@ -60,17 +60,16 @@ XPCOMUtils.defineLazyServiceGetter(this,
["Feeds", "resource:///modules/Feeds.jsm"],
["FileUtils", "resource://gre/modules/FileUtils.jsm"],
["FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"],
["Integration", "resource://gre/modules/Integration.jsm"],
["LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm"],
["LoginHelper", "resource://gre/modules/LoginHelper.jsm"],
["LoginManagerParent", "resource://gre/modules/LoginManagerParent.jsm"],
["NetUtil", "resource://gre/modules/NetUtil.jsm"],
- ["NewTabMessages", "resource:///modules/NewTabMessages.jsm"],
["NewTabUtils", "resource://gre/modules/NewTabUtils.jsm"],
["OS", "resource://gre/modules/osfile.jsm"],
["PageThumbs", "resource://gre/modules/PageThumbs.jsm"],
["PdfJs", "resource://pdf.js/PdfJs.jsm"],
["PermissionUI", "resource:///modules/PermissionUI.jsm"],
["PlacesBackups", "resource://gre/modules/PlacesBackups.jsm"],
["PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"],
["PluralForm", "resource://gre/modules/PluralForm.jsm"],
@@ -504,18 +503,16 @@ BrowserGlue.prototype = {
webrtcUI.init();
AboutHome.init();
DirectoryLinksProvider.init();
NewTabUtils.init();
NewTabUtils.links.addProvider(DirectoryLinksProvider);
AboutNewTab.init();
- NewTabMessages.init();
-
SessionStore.init();
BrowserUsageTelemetry.init();
BrowserUITelemetry.init();
ContentSearch.init();
FormValidationHandler.init();
ContentClick.init();
RemotePrompt.init();
@@ -891,18 +888,18 @@ BrowserGlue.prototype = {
if (this._bookmarksBackupIdleTime) {
this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime);
delete this._bookmarksBackupIdleTime;
}
BrowserUsageTelemetry.uninit();
SelfSupportBackend.uninit();
PageThumbs.uninit();
- NewTabMessages.uninit();
AboutNewTab.uninit();
+ NewTabUtils.uninit();
webrtcUI.uninit();
FormValidationHandler.uninit();
AutoCompletePopup.uninit();
DateTimePickerHelper.uninit();
},
_initServiceDiscovery() {
if (!Services.prefs.getBoolPref("browser.casting.enabled")) {
--- a/browser/extensions/activity-stream/lib/TopSitesFeed.jsm
+++ b/browser/extensions/activity-stream/lib/TopSitesFeed.jsm
@@ -1,18 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- /* globals PlacesProvider, PreviewProvider */
+ /* globals NewTabUtils, PreviewProvider */
"use strict";
const {utils: Cu} = Components;
const {actionTypes: at, actionCreators: ac} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
-Cu.import("resource:///modules/PlacesProvider.jsm");
+Cu.import("resource://gre/modules/NewTabUtils.jsm");
Cu.import("resource:///modules/PreviewProvider.jsm");
const TOP_SITES_SHOWMORE_LENGTH = 12;
const UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
const DEFAULT_TOP_SITES = [
{"url": "https://www.facebook.com/"},
{"url": "https://www.youtube.com/"},
{"url": "http://www.amazon.com/"},
@@ -26,17 +26,17 @@ this.TopSitesFeed = class TopSitesFeed {
this.lastUpdated = 0;
}
async getScreenshot(url) {
let screenshot = await PreviewProvider.getThumbnail(url);
const action = {type: at.SCREENSHOT_UPDATED, data: {url, screenshot}};
this.store.dispatch(ac.BroadcastToContent(action));
}
async getLinksWithDefaults(action) {
- let links = await PlacesProvider.links.getLinks();
+ let links = await NewTabUtils.activityStreamLinks.getTopSites();
if (!links) {
links = [];
} else {
links = links.filter(link => link && link.type !== "affiliate").slice(0, 12);
}
if (links.length < TOP_SITES_SHOWMORE_LENGTH) {
--- a/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js
@@ -12,50 +12,50 @@ describe("Top Sites Feed", () => {
let sandbox;
let links;
let clock;
before(() => {
globals = new GlobalOverrider();
sandbox = globals.sandbox;
});
beforeEach(() => {
- globals.set("PlacesProvider", {links: {getLinks: sandbox.spy(() => Promise.resolve(links))}});
+ globals.set("NewTabUtils", {activityStreamLinks: {getTopSites: sandbox.spy(() => Promise.resolve(links))}});
globals.set("PreviewProvider", {getThumbnail: sandbox.spy(() => Promise.resolve(FAKE_SCREENSHOT))});
feed = new TopSitesFeed();
feed.store = {dispatch: sinon.spy(), getState() { return {TopSites: {rows: Array(12).fill("site")}}; }};
links = FAKE_LINKS;
clock = sinon.useFakeTimers();
});
afterEach(() => {
globals.restore();
clock.restore();
});
it("should have default sites with .isDefault = true", () => {
DEFAULT_TOP_SITES.forEach(link => assert.propertyVal(link, "isDefault", true));
});
describe("#getLinksWithDefaults", () => {
- it("should get the links from Places Provider", async () => {
+ it("should get the links from NewTabUtils", async () => {
const result = await feed.getLinksWithDefaults();
assert.deepEqual(result, links);
- assert.calledOnce(global.PlacesProvider.links.getLinks);
+ assert.calledOnce(global.NewTabUtils.activityStreamLinks.getTopSites);
});
it("should add defaults if there are are not enough links", async () => {
links = [{url: "foo.com"}];
const result = await feed.getLinksWithDefaults();
assert.deepEqual(result, [{url: "foo.com"}, ...DEFAULT_TOP_SITES]);
});
it("should only add defaults up to TOP_SITES_SHOWMORE_LENGTH", async () => {
links = new Array(TOP_SITES_SHOWMORE_LENGTH - 1).fill({url: "foo.com"});
const result = await feed.getLinksWithDefaults();
assert.lengthOf(result, TOP_SITES_SHOWMORE_LENGTH);
assert.deepEqual(result, [...links, DEFAULT_TOP_SITES[0]]);
});
- it("should not throw if PlacesProvider returns null", () => {
+ it("should not throw if NewTabUtils returns null", () => {
links = null;
assert.doesNotThrow(() => {
feed.getLinksWithDefaults(action);
});
});
});
describe("#refresh", () => {
it("should dispatch an action with the links returned", async () => {
--- a/toolkit/components/places/tests/PlacesTestUtils.jsm
+++ b/toolkit/components/places/tests/PlacesTestUtils.jsm
@@ -82,16 +82,53 @@ this.PlacesTestUtils = Object.freeze({
date: visitDate,
referrer: place.referrer
}];
infos.push(info);
}
return PlacesUtils.history.insertMany(infos);
}),
+ /*
+ * Add Favicons
+ *
+ * @param {Map} faviconURLs keys are page URLs, values are their
+ * associated favicon URLs.
+ */
+
+ addFavicons: Task.async(function*(faviconURLs) {
+ let faviconPromises = [];
+
+ // If no favicons were provided, we do not want to continue on
+ if (!faviconURLs) {
+ throw new Error("No favicon URLs were provided");
+ }
+ for (let [key, val] of faviconURLs) {
+ if (!val) {
+ throw new Error("URL does not exist");
+ }
+ faviconPromises.push(new Promise((resolve, reject) => {
+ let uri = NetUtil.newURI(key);
+ let faviconURI = NetUtil.newURI(val);
+ try {
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ uri,
+ faviconURI,
+ false,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ resolve,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ } catch (ex) {
+ reject(ex);
+ }
+ }));
+ }
+ yield Promise.all(faviconPromises);
+ }),
+
/**
* Clear all history.
*
* @return {Promise}
* @resolves When history was cleared successfully.
* @rejects JavaScript exception.
*/
clearHistory() {
--- a/toolkit/modules/NewTabUtils.jsm
+++ b/toolkit/modules/NewTabUtils.jsm
@@ -8,16 +8,17 @@ this.EXPORTED_SYMBOLS = ["NewTabUtils"];
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");
+Cu.importGlobalProperties(["btoa"]);
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
"resource://gre/modules/PageThumbs.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BinarySearch",
@@ -48,16 +49,22 @@ const PREF_NEWTAB_COLUMNS = "browser.new
const HISTORY_RESULTS_LIMIT = 100;
// The maximum number of links Links.getLinks will return.
const LINKS_GET_LINKS_LIMIT = 100;
// The gather telemetry topic.
const TOPIC_GATHER_TELEMETRY = "gather-telemetry";
+// The number of top sites to display on Activity Stream page
+const TOP_SITES_LENGTH = 6;
+
+// Use double the number to allow for immediate display when blocking sites
+const TOP_SITES_LIMIT = TOP_SITES_LENGTH * 2;
+
/**
* Calculate the MD5 hash for a string.
* @param aValue
* The string to convert.
* @return The base64 representation of the MD5 hash.
*/
function toHash(aValue) {
let value = gUnicodeConverter.convertToByteArray(aValue);
@@ -343,16 +350,24 @@ var GridPrefs = {
/**
* Initializes object. Adds a preference observer
*/
init: function GridPrefs_init() {
Services.prefs.addObserver(PREF_NEWTAB_ROWS, this);
Services.prefs.addObserver(PREF_NEWTAB_COLUMNS, this);
},
+ /**
+ * Uninitializes object. Removes the preference observers
+ */
+ uninit: function GridPrefs_uninit() {
+ Services.prefs.removeObserver(PREF_NEWTAB_ROWS, this);
+ Services.prefs.removeObserver(PREF_NEWTAB_COLUMNS, this);
+ },
+
/**
* Implements the nsIObserver interface to get notified when the preference
* value changes.
*/
observe: function GridPrefs_observe(aSubject, aTopic, aData) {
if (aData == PREF_NEWTAB_ROWS) {
this._gridRows = null;
} else {
@@ -507,16 +522,23 @@ var BlockedLinks = {
/**
* Registers an object that will be notified when the blocked links change.
*/
addObserver(aObserver) {
this._observers.push(aObserver);
},
/**
+ * Remove the observers.
+ */
+ removeObservers() {
+ this._observers = [];
+ },
+
+ /**
* The list of blocked links.
*/
get links() {
if (!this._links)
this._links = Storage.get("blockedLinks", {});
return this._links;
},
@@ -796,16 +818,419 @@ var PlacesProvider = {
}
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver,
Ci.nsISupportsWeakReference]),
};
/**
+ * Queries history to retrieve the most frecent sites. Emits events when the
+ * history changes.
+ */
+var ActivityStreamProvider = {
+
+ /**
+ * Process links after getting them from the database.
+ *
+ * @param {Array} aLinks
+ * an array containing link objects
+ *
+ * @returns {Array} an array of checked links with favicons and eTLDs added
+ */
+ _processLinks(aLinks) {
+ let links_ = aLinks.filter(link => LinkChecker.checkLoadURI(link.url));
+ links_ = this._faviconBytesToDataURI(links_);
+ return this._addETLD(links_);
+ },
+
+ /**
+ * From an Array of links, if favicons are present, convert to data URIs
+ *
+ * @param {Array} aLinks
+ * an array containing objects with favicon data and mimeTypes
+ *
+ * @returns {Array} an array of links with favicons as data uri
+ */
+ _faviconBytesToDataURI(aLinks) {
+ return aLinks.map(link => {
+ if (link.favicon) {
+ let encodedData = btoa(String.fromCharCode.apply(null, link.favicon));
+ link.favicon = `data:${link.mimeType};base64,${encodedData}`;
+ }
+ delete link.mimeType;
+ return link;
+ });
+ },
+
+ /**
+ * Computes favicon data for each url in a set of links
+ *
+ * @param {Array} links
+ * an array containing objects without favicon data or mimeTypes yet
+ *
+ * @returns {Promise} Returns a promise with the array of links with favicon data,
+ * mimeType, and byte array length
+ */
+ _addFavicons: Task.async(function*(aLinks) {
+ if (aLinks.length) {
+ // Each link in the array needs a favicon for it's page - so we fire off
+ // a promise for each link to compute the favicon data and attach it back
+ // to the original link object. We must wait until all favicons for
+ // the array of links are computed before returning
+ yield Promise.all(aLinks.map(link => new Promise(resolve => {
+ return PlacesUtils.favicons.getFaviconDataForPage(
+ Services.io.newURI(link.url),
+ (iconuri, len, data, mime) => {
+ // Due to the asynchronous behaviour of inserting a favicon into
+ // moz_favicons, the data may not be available to us just yet,
+ // since we listen on a history entry being inserted. As a result,
+ // we don't want to throw if the icon uri is not here yet, we
+ // just want to resolve on an empty favicon. Activity Stream
+ // knows how to handle null favicons
+ if (!iconuri) {
+ link.favicon = null;
+ link.mimeType = null;
+ } else {
+ link.favicon = data;
+ link.mimeType = mime;
+ link.faviconLength = len;
+ }
+ return resolve(link);
+ });
+ }).catch(() => {
+ // If something goes wrong - that's ok - just return a null favicon
+ // without rejecting the entire Promise.all
+ link.favicon = null;
+ link.mimeType = null;
+ return link;
+ })
+ ));
+ }
+ return aLinks;
+ }),
+
+ /**
+ * Add the eTLD to each link in the array of links.
+ *
+ * @param {Array} aLinks
+ * an array containing objects with urls
+ *
+ * @returns {Array} an array of links with eTLDs added
+ */
+ _addETLD(aLinks) {
+ return aLinks.map(link => {
+ try {
+ link.eTLD = Services.eTLD.getPublicSuffix(Services.io.newURI(link.url));
+ } catch (e) {
+ link.eTLD = "";
+ }
+ return link;
+ });
+ },
+
+ /*
+ * Initializes Activity Stream provider - adds a history observer and a
+ * bookmarks observer.
+ */
+ init() {
+ PlacesUtils.history.addObserver(this.historyObserver, true);
+ PlacesUtils.bookmarks.addObserver(this.bookmarksObsever, true);
+ },
+
+ /**
+ * A set of functions called by @mozilla.org/browser/nav-historyservice
+ * All history events are emitted from this object.
+ */
+ historyObserver: {
+ onDeleteURI(uri) {
+ Services.obs.notifyObservers(null, "newtab-deleteURI", {url: uri.spec});
+ },
+
+ onClearHistory() {
+ Services.obs.notifyObservers(null, "newtab-clearHistory");
+ },
+
+ onFrecencyChanged(uri, newFrecency, guid, hidden, lastVisitDate) {
+ if (!hidden && lastVisitDate) {
+ Services.obs.notifyObservers(null, "newtab-linkChanged", {
+ url: uri.spec,
+ frecency: newFrecency,
+ lastVisitDate,
+ type: "history"
+ });
+ }
+ },
+
+ onManyFrecenciesChanged() {
+ Services.obs.notifyObservers(null, "newtab-manyLinksChanged");
+ },
+
+ onTitleChanged(uri, newTitle) {
+ Services.obs.notifyObservers(null, "newtab-linkChanged", {url: uri.spec, title: newTitle});
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver,
+ Ci.nsISupportsWeakReference])
+ },
+
+ /**
+ * A set of functions called by @mozilla.org/browser/nav-bookmarks-service
+ * All bookmark events are emitted from this object.
+ */
+ bookmarksObsever: {
+ onItemAdded(id, folderId, index, type, uri, title, dateAdded, guid) {
+ if (type === PlacesUtils.bookmarks.TYPE_BOOKMARK) {
+ ActivityStreamProvider.getBookmark(guid).then(bookmark => {
+ Services.obs.notifyObservers(null, "newtab-bookmarkAdded", bookmark);
+ }).catch(Cu.reportError);
+ }
+ },
+
+ onItemRemoved(id, folderId, index, type, uri) {
+ if (type === PlacesUtils.bookmarks.TYPE_BOOKMARK) {
+ Services.obs.notifyObservers(null, "newtab-bookmarkRemoved", {bookmarkId: id, url: uri.spec});
+ }
+ },
+
+ onItemChanged(id, property, isAnnotation, value, lastModified, type, parent, guid) {
+ if (type === PlacesUtils.bookmarks.TYPE_BOOKMARK) {
+ ActivityStreamProvider.getBookmark(guid).then(bookmark => {
+ Services.obs.notifyObservers(null, "newtab-bookmarkChanged", bookmark);
+ }).catch(Cu.reportError);
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver,
+ Ci.nsISupportsWeakReference])
+ },
+
+ /*
+ * Gets the top frecent sites for Activity Stream.
+ *
+ * @param {Object} aOptions
+ * options.ignoreBlocked: Do not filter out blocked links .
+ *
+ * @returns {Promise} Returns a promise with the array of links as payload.
+ */
+ getTopFrecentSites: Task.async(function*(aOptions = {}) {
+ let {ignoreBlocked} = aOptions;
+
+ // GROUP first by rev_host to get the most-frecent page of an exact host
+ // then GROUP by rev_nowww to dedupe between top two pages of nowww host.
+ // Note that unlike mysql, sqlite picks the last raw from groupby bucket.
+ // Which is why subselect orders frecency and last_visit_date backwards.
+ // In general the groupby behavior in the absence of aggregates is not
+ // defined in SQL, hence we are relying on sqlite implementation that may
+ // change in the future.
+
+ const limit = Object.keys(BlockedLinks.links).length + TOP_SITES_LIMIT;
+ let sqlQuery = `/* do not warn (bug N/A): do not need index */
+ SELECT url, title, SUM(frecency) frecency, guid, bookmarkGuid,
+ last_visit_date / 1000 as lastVisitDate, "history" as type
+ FROM (SELECT * FROM (
+ SELECT
+ rev_host,
+ fixup_url(get_unreversed_host(rev_host)) AS rev_nowww,
+ moz_places.url,
+ moz_places.title,
+ frecency,
+ last_visit_date,
+ moz_places.guid AS guid,
+ moz_bookmarks.guid AS bookmarkGuid
+ FROM moz_places
+ LEFT JOIN moz_bookmarks
+ on moz_places.id = moz_bookmarks.fk
+ WHERE hidden = 0 AND last_visit_date NOTNULL
+ ORDER BY frecency, last_visit_date, moz_places.url DESC
+ ) GROUP BY rev_host)
+ GROUP BY rev_nowww
+ ORDER BY frecency DESC, lastVisitDate DESC, url
+ LIMIT ${limit}`;
+
+ let links = yield this.executePlacesQuery(sqlQuery, {
+ columns: [
+ "bookmarkGuid",
+ "frecency",
+ "guid",
+ "lastVisitDate",
+ "title",
+ "type",
+ "url"
+ ]
+ });
+
+ if (!ignoreBlocked) {
+ links = links.filter(link => !BlockedLinks.isBlocked(link));
+ }
+ links = links.slice(0, TOP_SITES_LIMIT);
+ links = yield this._addFavicons(links);
+ return this._processLinks(links);
+ }),
+
+ /**
+ * Gets a specific bookmark given an id
+ *
+ * @param {String} aGuid
+ * A bookmark guid to use as a refrence to fetch the bookmark
+ */
+ getBookmark: Task.async(function*(aGuid) {
+ let bookmark = yield PlacesUtils.bookmarks.fetch(aGuid);
+ if (!bookmark) {
+ return null;
+ }
+ let result = {};
+ result.bookmarkGuid = bookmark.guid;
+ result.bookmarkTitle = bookmark.title;
+ result.lastModified = bookmark.lastModified.getTime();
+ result.url = bookmark.url.href;
+ return result;
+ }),
+
+ /**
+ * Gets History size
+ *
+ * @returns {Promise} Returns a promise with the count of moz_places records
+ */
+ getHistorySize: Task.async(function*() {
+ let sqlQuery = `SELECT count(*) FROM moz_places
+ WHERE hidden = 0 AND last_visit_date NOT NULL`;
+
+ let result = yield this.executePlacesQuery(sqlQuery);
+ return result;
+ }),
+
+ /**
+ * Gets Bookmarks count
+ *
+ * @returns {Promise} Returns a promise with the count of bookmarks
+ */
+ getBookmarksSize: Task.async(function*() {
+ let sqlQuery = `SELECT count(*) FROM moz_bookmarks WHERE type = :type`;
+
+ let result = yield this.executePlacesQuery(sqlQuery, {params: {type: PlacesUtils.bookmarks.TYPE_BOOKMARK}});
+ return result;
+ }),
+
+ /**
+ * Executes arbitrary query against places database
+ *
+ * @param {String} aQuery
+ * SQL query to execute
+ * @param {Object} [optional] aOptions
+ * aOptions.columns - an array of column names. if supplied the return
+ * items will consists of objects keyed on column names. Otherwise
+ * array of raw values is returned in the select order
+ * aOptions.param - an object of SQL binding parameters
+ *
+ * @returns {Promise} Returns a promise with the array of retrieved items
+ */
+ executePlacesQuery: Task.async(function*(aQuery, aOptions = {}) {
+ let {columns, params} = aOptions;
+ let items = [];
+ let queryError = null;
+ let conn = yield PlacesUtils.promiseDBConnection();
+ yield conn.executeCached(aQuery, params, aRow => {
+ try {
+ let item = null;
+ // if columns array is given construct an object
+ if (columns && Array.isArray(columns)) {
+ item = {};
+ columns.forEach(column => {
+ item[column] = aRow.getResultByName(column);
+ });
+ } else {
+ // if no columns - make an array of raw values
+ item = [];
+ for (let i = 0; i < aRow.numEntries; i++) {
+ item.push(aRow.getResultByIndex(i));
+ }
+ }
+ items.push(item);
+ } catch (e) {
+ queryError = e;
+ throw StopIteration;
+ }
+ });
+ if (queryError) {
+ throw new Error(queryError);
+ }
+ return items;
+ })
+};
+
+/**
+ * A set of actions which influence what sites shown on the Activity Stream page
+ */
+var ActivityStreamLinks = {
+ /**
+ * Block a url
+ *
+ * @param {Object} aLink
+ * The link which contains a URL to add to the block list
+ */
+ blockURL(aLink) {
+ BlockedLinks.block(aLink);
+ },
+
+ onLinkBlocked(aLink) {
+ Services.obs.notifyObservers(null, "newtab-linkChanged", {url: aLink.url, blocked: true})
+ },
+
+ /**
+ * Adds a bookmark
+ *
+ * @param {String} aUrl
+ * The url to bookmark
+ *
+ * @returns {Promise} Returns a promise set to an object representing the bookmark
+ */
+ addBookmark(aUrl) {
+ return PlacesUtils.bookmarks.insert({
+ url: aUrl,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid
+ });
+ },
+
+ /**
+ * Removes a bookmark
+ *
+ * @param {String} aBookmarkGuid
+ * The bookmark guid associated with the bookmark to remove
+ *
+ * @returns {Promise} Returns a promise set to an object representing the
+ * removed bookmark
+ */
+ deleteBookmark(aBookmarkGuid) {
+ return PlacesUtils.bookmarks.remove(aBookmarkGuid);
+ },
+
+ /**
+ * Removes a history link
+ *
+ * @param {String} aUrl
+ * The url to be removed from history
+ *
+ * @returns {Promise} Returns a promise set to true if link was removed
+ */
+ deleteHistoryEntry(aUrl) {
+ return PlacesUtils.history.remove(aUrl);
+ },
+
+ /**
+ * Get the top sites to show on Activity Stream
+ *
+ * @return {Promise} Returns a promise with the array of links as the payload
+ */
+ getTopSites: Task.async(function*(aOptions = {}) {
+ return ActivityStreamProvider.getTopFrecentSites(aOptions);
+ })
+};
+
+/**
* Singleton that provides access to all links contained in the grid (including
* the ones that don't fit on the grid). A link is a plain object that looks
* like this:
*
* {
* url: "http://www.mozilla.org/",
* title: "Mozilla",
* frecency: 1337,
@@ -1289,16 +1714,20 @@ Links.compareLinks = Links.compareLinks.
var Telemetry = {
/**
* Initializes object.
*/
init: function Telemetry_init() {
Services.obs.addObserver(this, TOPIC_GATHER_TELEMETRY);
},
+ uninit: function Telemetry_uninit() {
+ Services.obs.removeObserver(this, TOPIC_GATHER_TELEMETRY);
+ },
+
/**
* Collects data.
*/
_collect: function Telemetry_collect() {
let probes = [
{ histogram: "NEWTAB_PAGE_ENABLED",
value: AllPages.enabled },
{ histogram: "NEWTAB_PAGE_ENHANCED",
@@ -1409,31 +1838,41 @@ this.NewTabUtils = {
// Strip off common subdomains of the same site (e.g., www, load balancer)
return host.replace(/^(m|mobile|www\d*)\./, "");
},
init: function NewTabUtils_init() {
if (this.initWithoutProviders()) {
PlacesProvider.init();
+ ActivityStreamProvider.init();
Links.addProvider(PlacesProvider);
BlockedLinks.addObserver(Links);
+ BlockedLinks.addObserver(ActivityStreamLinks);
}
},
initWithoutProviders: function NewTabUtils_initWithoutProviders() {
if (!this._initialized) {
this._initialized = true;
ExpirationFilter.init();
Telemetry.init();
return true;
}
return false;
},
+ uninit: function NewTabUtils_uninit() {
+ if (this.initialized) {
+ Telemetry.uninit();
+ GridPrefs.uninit();
+ BlockedLinks.removeObservers();
+ }
+ },
+
getProviderLinks(aProvider) {
let cache = Links._providers.get(aProvider);
if (cache && cache.sortedLinks) {
return cache.sortedLinks;
}
return [];
},
@@ -1476,10 +1915,12 @@ this.NewTabUtils = {
},
links: Links,
allPages: AllPages,
linkChecker: LinkChecker,
pinnedLinks: PinnedLinks,
blockedLinks: BlockedLinks,
gridPrefs: GridPrefs,
- placesProvider: PlacesProvider
+ placesProvider: PlacesProvider,
+ activityStreamLinks: ActivityStreamLinks,
+ activityStreamProvider: ActivityStreamProvider
};
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/xpcshell/head.js
@@ -0,0 +1,78 @@
+var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+Cu.import("resource://gre/modules/NewTabUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.importGlobalProperties(["btoa"]);
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced";
+
+// use time at the start of the tests, chnaging it inside timeDaysAgo()
+// may cause tiny time differences, which break expected sql ordering
+const TIME_NOW = (new Date()).getTime();
+
+Services.prefs.setBoolPref(PREF_NEWTAB_ENHANCED, true);
+
+do_get_profile();
+
+// utility function to compute past timestamp in microseconds
+function timeDaysAgo(numDays) {
+ return (TIME_NOW - (numDays * 24 * 60 * 60 * 1000)) * 1000;
+}
+
+// tests that timestamp falls within 10 days of now
+function isVisitDateOK(timestampMS) {
+ let range = 10 * 24 * 60 * 60 * 1000;
+ return Math.abs(Date.now() - timestampMS) < range;
+}
+
+// a set up function to prep the activity stream provider
+function setUpActivityStreamTest() {
+ return Task.spawn(function*() {
+ yield PlacesTestUtils.clearHistory();
+ yield PlacesUtils.bookmarks.eraseEverything();
+ let faviconExpiredPromise = new Promise(resolve => {
+ Services.obs.addObserver(resolve, "places-favicons-expired");
+ });
+ PlacesUtils.favicons.expireAllFavicons();
+ yield faviconExpiredPromise;
+ });
+}
+
+function do_check_links(actualLinks, expectedLinks) {
+ Assert.ok(Array.isArray(actualLinks));
+ Assert.equal(actualLinks.length, expectedLinks.length);
+ for (let i = 0; i < expectedLinks.length; i++) {
+ let expected = expectedLinks[i];
+ let actual = actualLinks[i];
+ Assert.equal(actual.url, expected.url);
+ Assert.equal(actual.title, expected.title);
+ Assert.equal(actual.frecency, expected.frecency);
+ Assert.equal(actual.lastVisitDate, expected.lastVisitDate);
+ }
+}
+
+function makeLinks(frecRangeStart, frecRangeEnd, step) {
+ let links = [];
+ // Remember, links are ordered by frecency descending.
+ for (let i = frecRangeEnd; i > frecRangeStart; i -= step) {
+ links.push(makeLink(i));
+ }
+ return links;
+}
+
+function makeLink(frecency) {
+ return {
+ url: "http://example" + frecency + ".com/",
+ title: "My frecency is " + frecency,
+ frecency,
+ lastVisitDate: 0,
+ };
+}
--- a/toolkit/modules/tests/xpcshell/test_FinderIterator.js
+++ b/toolkit/modules/tests/xpcshell/test_FinderIterator.js
@@ -1,11 +1,9 @@
-const { interfaces: Ci, classes: Cc, utils: Cu } = Components;
const { FinderIterator } = Cu.import("resource://gre/modules/FinderIterator.jsm", {});
-Cu.import("resource://gre/modules/Promise.jsm");
var gFindResults = [];
// Stub the method that instantiates nsIFind and does all the interaction with
// the docShell to be searched through.
FinderIterator._iterateDocument = function* (word, window, finder) {
for (let range of gFindResults)
yield range;
};
--- a/toolkit/modules/tests/xpcshell/test_Integration.js
+++ b/toolkit/modules/tests/xpcshell/test_Integration.js
@@ -1,22 +1,17 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* Tests the Integration.jsm module.
*/
"use strict";
-
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
Cu.import("resource://gre/modules/Integration.jsm", this);
-Cu.import("resource://gre/modules/Services.jsm", this);
-Cu.import("resource://gre/modules/Task.jsm", this);
const TestIntegration = {
value: "value",
get valueFromThis() {
return this.value;
},
--- a/toolkit/modules/tests/xpcshell/test_JSONFile.js
+++ b/toolkit/modules/tests/xpcshell/test_JSONFile.js
@@ -1,20 +1,15 @@
/**
* Tests the JSONFile object.
*/
"use strict";
// Globals
-
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
"resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
"resource://gre/modules/DownloadPaths.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
--- a/toolkit/modules/tests/xpcshell/test_NewTabUtils.js
+++ b/toolkit/modules/tests/xpcshell/test_NewTabUtils.js
@@ -1,26 +1,13 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// See also browser/base/content/test/newtab/.
-var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
-Cu.import("resource://gre/modules/NewTabUtils.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced";
-
-function run_test() {
- Services.prefs.setBoolPref(PREF_NEWTAB_ENHANCED, true);
- run_next_test();
-}
-
add_task(function* validCacheMidPopulation() {
let expectedLinks = makeLinks(0, 3, 1);
let provider = new TestProvider(done => done(expectedLinks));
provider.maxNumLinks = expectedLinks.length;
NewTabUtils.initWithoutProviders();
NewTabUtils.links.addProvider(provider);
@@ -315,16 +302,302 @@ add_task(function* extractSite() {
"file:///Users/user/file",
"chrome://browser/something",
"ftp://ftp.mozilla.org/",
].forEach(url => {
do_check_neq(NewTabUtils.extractSite(url), "mozilla.org", "extracted diff url " + url);
});
});
+add_task(function* faviconBytesToDataURI() {
+ let tests = [
+ [{favicon: "bar".split("").map(s => s.charCodeAt(0)), mimeType: "foo"}],
+ [{favicon: "bar".split("").map(s => s.charCodeAt(0)), mimeType: "foo", xxyy: "quz"}]
+ ];
+ let provider = NewTabUtils.activityStreamProvider;
+
+ for (let test of tests) {
+ let clone = JSON.parse(JSON.stringify(test));
+ delete clone[0].mimeType;
+ clone[0].favicon = `data:foo;base64,${btoa("bar")}`;
+ let result = provider._faviconBytesToDataURI(test);
+ Assert.deepEqual(JSON.stringify(clone), JSON.stringify(result), "favicon converted to data uri");
+ }
+});
+
+add_task(function* addFavicons() {
+ yield setUpActivityStreamTest();
+ let provider = NewTabUtils.activityStreamProvider;
+
+ // start by passing in a bad uri and check that we get a null favicon back
+ let links = [{url: "mozilla.com"}];
+ yield provider._addFavicons(links);
+ Assert.equal(links[0].favicon, null, "Got a null favicon because we passed in a bad url");
+ Assert.equal(links[0].mimeType, null, "Got a null mime type because we passed in a bad url");
+
+ // now fix the url and try again - this time we get good favicon data back
+ links[0].url = "https://mozilla.com";
+ let base64URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" +
+ "AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==";
+
+ let visit = [
+ {uri: links[0].url, visitDate: timeDaysAgo(0), transition: PlacesUtils.history.TRANSITION_TYPED}
+ ];
+ yield PlacesTestUtils.addVisits(visit);
+
+ let faviconData = new Map();
+ faviconData.set("https://mozilla.com", base64URL);
+ yield PlacesTestUtils.addFavicons(faviconData);
+
+ yield provider._addFavicons(links);
+ Assert.equal(links[0].mimeType, "image/png", "Got the right mime type before deleting it");
+ Assert.equal(links[0].faviconLength, links[0].favicon.length, "Got the right length for the byte array");
+ Assert.equal(provider._faviconBytesToDataURI(links)[0].favicon, base64URL, "Got the right favicon");
+});
+
+add_task(function* getTopFrecentSites() {
+ yield setUpActivityStreamTest();
+
+ let provider = NewTabUtils.activityStreamLinks;
+ let links = yield provider.getTopSites();
+ Assert.equal(links.length, 0, "empty history yields empty links");
+
+ // add a visit
+ let testURI = "http://mozilla.com/";
+ yield PlacesTestUtils.addVisits(testURI);
+
+ links = yield provider.getTopSites();
+ Assert.equal(links.length, 1, "adding a visit yields a link");
+ Assert.equal(links[0].url, testURI, "added visit corresponds to added url");
+ Assert.equal(links[0].eTLD, "com", "added visit mozilla.com has 'com' eTLD");
+});
+
+add_task(function* getTopFrecentSites_dedupeWWW() {
+ yield setUpActivityStreamTest();
+
+ let provider = NewTabUtils.activityStreamLinks;
+
+ let links = yield provider.getTopSites();
+ Assert.equal(links.length, 0, "empty history yields empty links");
+
+ // add a visit without www
+ let testURI = "http://mozilla.com";
+ yield PlacesTestUtils.addVisits(testURI);
+
+ // add a visit with www
+ testURI = "http://www.mozilla.com";
+ yield PlacesTestUtils.addVisits(testURI);
+
+ // Test combined frecency score
+ links = yield provider.getTopSites();
+ Assert.equal(links.length, 1, "adding both www. and no-www. yields one link");
+ Assert.equal(links[0].frecency, 200, "frecency scores are combined");
+
+ // add another page visit with www and without www
+ testURI = "http://mozilla.com/page";
+ yield PlacesTestUtils.addVisits(testURI);
+ testURI = "http://www.mozilla.com/page";
+ yield PlacesTestUtils.addVisits(testURI);
+ links = yield provider.getTopSites();
+ Assert.equal(links.length, 1, "adding both www. and no-www. yields one link");
+ Assert.equal(links[0].frecency, 200, "frecency scores are combined ignoring extra pages");
+});
+
+add_task(function* getTopFrencentSites_maxLimit() {
+ yield setUpActivityStreamTest();
+
+ let provider = NewTabUtils.activityStreamLinks;
+
+ // add many visits
+ const MANY_LINKS = 20;
+ for (let i = 0; i < MANY_LINKS; i++) {
+ let testURI = `http://mozilla${i}.com`;
+ yield PlacesTestUtils.addVisits(testURI);
+ }
+
+ let links = yield provider.getTopSites();
+ Assert.ok(links.length < MANY_LINKS, "query default limited to less than many");
+ Assert.ok(links.length > 6, "query default to more than visible count");
+});
+
+add_task(function* getTopFrecentSites_order() {
+ yield setUpActivityStreamTest();
+
+ let provider = NewTabUtils.activityStreamLinks;
+ let {TRANSITION_TYPED} = PlacesUtils.history;
+
+ let timeEarlier = timeDaysAgo(0);
+ let timeLater = timeDaysAgo(2);
+
+ let visits = [
+ // frecency 200
+ {uri: "https://mozilla1.com/0", visitDate: timeEarlier, transition: TRANSITION_TYPED},
+ // sort by url, frecency 200
+ {uri: "https://mozilla2.com/1", visitDate: timeEarlier, transition: TRANSITION_TYPED},
+ // sort by last visit date, frecency 200
+ {uri: "https://mozilla3.com/2", visitDate: timeLater, transition: TRANSITION_TYPED},
+ // sort by frecency, frecency 10
+ {uri: "https://mozilla4.com/3", visitDate: timeLater}
+ ];
+
+ let links = yield provider.getTopSites();
+ Assert.equal(links.length, 0, "empty history yields empty links");
+
+ let base64URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" +
+ "AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==";
+
+ // map of page url to favicon url
+ let faviconData = new Map();
+ faviconData.set("https://mozilla3.com/2", base64URL);
+
+ yield PlacesTestUtils.addVisits(visits);
+ yield PlacesTestUtils.addFavicons(faviconData);
+
+ links = yield provider.getTopSites();
+ Assert.equal(links.length, visits.length, "number of links added is the same as obtain by getTopFrecentSites");
+
+ // first link doesn't have a favicon
+ Assert.equal(links[0].url, visits[0].uri, "links are obtained in the expected order");
+ Assert.equal(null, links[0].favicon, "favicon data is stored as expected");
+ Assert.ok(isVisitDateOK(links[0].lastVisitDate), "visit date within expected range");
+
+ // second link doesn't have a favicon
+ Assert.equal(links[1].url, visits[1].uri, "links are obtained in the expected order");
+ Assert.equal(null, links[1].favicon, "favicon data is stored as expected");
+ Assert.ok(isVisitDateOK(links[1].lastVisitDate), "visit date within expected range");
+
+ // third link should have the favicon data that we added
+ Assert.equal(links[2].url, visits[2].uri, "links are obtained in the expected order");
+ Assert.equal(faviconData.get(links[2].url), links[2].favicon, "favicon data is stored as expected");
+ Assert.ok(isVisitDateOK(links[2].lastVisitDate), "visit date within expected range");
+
+ // fourth link doesn't have a favicon
+ Assert.equal(links[3].url, visits[3].uri, "links are obtained in the expected order");
+ Assert.equal(null, links[3].favicon, "favicon data is stored as expected");
+ Assert.ok(isVisitDateOK(links[3].lastVisitDate), "visit date within expected range");
+});
+
+add_task(function* activitySteamProvider_deleteHistoryLink() {
+ yield setUpActivityStreamTest();
+
+ let provider = NewTabUtils.activityStreamLinks;
+
+ let {TRANSITION_TYPED} = PlacesUtils.history;
+
+ let visits = [
+ // frecency 200
+ {uri: "https://mozilla1.com/0", visitDate: timeDaysAgo(1), transition: TRANSITION_TYPED},
+ // sort by url, frecency 200
+ {uri: "https://mozilla2.com/1", visitDate: timeDaysAgo(0)}
+ ];
+
+ let size = yield NewTabUtils.activityStreamProvider.getHistorySize();
+ Assert.equal(size, 0, "empty history has size 0");
+
+ yield PlacesTestUtils.addVisits(visits);
+
+ size = yield NewTabUtils.activityStreamProvider.getHistorySize();
+ Assert.equal(size, 2, "expected history size");
+
+ // delete a link
+ let deleted = yield provider.deleteHistoryEntry("https://mozilla2.com/1");
+ Assert.equal(deleted, true, "link is deleted");
+
+ // ensure that there's only one link left
+ size = yield NewTabUtils.activityStreamProvider.getHistorySize();
+ Assert.equal(size, 1, "expected history size");
+});
+
+add_task(function* activityStream_addBookmark() {
+ yield setUpActivityStreamTest();
+
+ let provider = NewTabUtils.activityStreamLinks;
+ let bookmarks = [
+ "https://mozilla1.com/0",
+ "https://mozilla1.com/1"
+ ];
+
+ let bookmarksSize = yield NewTabUtils.activityStreamProvider.getBookmarksSize();
+ Assert.equal(bookmarksSize, 0, "empty bookmarks yields 0 size");
+
+ for (let url of bookmarks) {
+ yield provider.addBookmark(url);
+ }
+ bookmarksSize = yield NewTabUtils.activityStreamProvider.getBookmarksSize();
+ Assert.equal(bookmarksSize, 2, "size 2 for 2 bookmarks added");
+});
+
+add_task(function* activityStream_getBookmark() {
+ yield setUpActivityStreamTest();
+
+ let provider = NewTabUtils.activityStreamLinks;
+ let bookmark = yield provider.addBookmark("https://mozilla1.com/0");
+
+ let result = yield NewTabUtils.activityStreamProvider.getBookmark(bookmark.guid);
+ Assert.equal(result.bookmarkGuid, bookmark.guid, "got the correct bookmark guid");
+ Assert.equal(result.bookmarkTitle, bookmark.title, "got the correct bookmark title");
+ Assert.equal(result.lastModified, bookmark.lastModified.getTime(), "got the correct bookmark time");
+ Assert.equal(result.url, bookmark.url.href, "got the correct bookmark url");
+});
+
+add_task(function* activityStream_deleteBookmark() {
+ yield setUpActivityStreamTest();
+
+ let provider = NewTabUtils.activityStreamLinks;
+ let bookmarks = [
+ {url: "https://mozilla1.com/0", parentGuid: PlacesUtils.bookmarks.unfiledGuid, type: PlacesUtils.bookmarks.TYPE_BOOKMARK},
+ {url: "https://mozilla1.com/1", parentGuid: PlacesUtils.bookmarks.unfiledGuid, type: PlacesUtils.bookmarks.TYPE_BOOKMARK}
+ ];
+
+ let bookmarksSize = yield NewTabUtils.activityStreamProvider.getBookmarksSize();
+ Assert.equal(bookmarksSize, 0, "empty bookmarks yields 0 size");
+
+ for (let placeInfo of bookmarks) {
+ yield PlacesUtils.bookmarks.insert(placeInfo);
+ }
+
+ bookmarksSize = yield NewTabUtils.activityStreamProvider.getBookmarksSize();
+ Assert.equal(bookmarksSize, 2, "size 2 for 2 bookmarks added");
+
+ let bookmarkGuid = yield new Promise(resolve => PlacesUtils.bookmarks.fetch(
+ {url: bookmarks[0].url}, bookmark => resolve(bookmark.guid)));
+ let deleted = yield provider.deleteBookmark(bookmarkGuid);
+ Assert.equal(deleted.guid, bookmarkGuid, "the correct bookmark was deleted");
+
+ bookmarksSize = yield NewTabUtils.activityStreamProvider.getBookmarksSize();
+ Assert.equal(bookmarksSize, 1, "size 1 after deleting");
+});
+
+add_task(function* activityStream_blockedURLs() {
+ yield setUpActivityStreamTest();
+
+ let provider = NewTabUtils.activityStreamLinks;
+ NewTabUtils.blockedLinks.addObserver(provider);
+
+ let {TRANSITION_TYPED} = PlacesUtils.history;
+
+ let timeToday = timeDaysAgo(0);
+ let timeEarlier = timeDaysAgo(2);
+
+ let visits = [
+ {uri: "https://example1.com/", visitDate: timeToday, transition: TRANSITION_TYPED},
+ {uri: "https://example2.com/", visitDate: timeToday, transition: TRANSITION_TYPED},
+ {uri: "https://example3.com/", visitDate: timeEarlier, transition: TRANSITION_TYPED},
+ {uri: "https://example4.com/", visitDate: timeEarlier, transition: TRANSITION_TYPED}
+ ];
+ yield PlacesTestUtils.addVisits(visits);
+ yield PlacesUtils.bookmarks.insert({url: "https://example5.com/", parentGuid: PlacesUtils.bookmarks.unfiledGuid, type: PlacesUtils.bookmarks.TYPE_BOOKMARK});
+
+ let sizeQueryResult;
+
+ // bookmarks
+ sizeQueryResult = yield NewTabUtils.activityStreamProvider.getBookmarksSize();
+ Assert.equal(sizeQueryResult, 1, "got the correct bookmark size");
+});
+
function TestProvider(getLinksFn) {
this.getLinks = getLinksFn;
this._observers = new Set();
}
TestProvider.prototype = {
addObserver(observer) {
this._observers.add(observer);
@@ -341,38 +614,8 @@ TestProvider.prototype = {
args.unshift(this);
for (let obs of this._observers) {
if (obs[observerMethodName])
obs[observerMethodName].apply(NewTabUtils.links, args);
}
},
};
-function do_check_links(actualLinks, expectedLinks) {
- do_check_true(Array.isArray(actualLinks));
- do_check_eq(actualLinks.length, expectedLinks.length);
- for (let i = 0; i < expectedLinks.length; i++) {
- let expected = expectedLinks[i];
- let actual = actualLinks[i];
- do_check_eq(actual.url, expected.url);
- do_check_eq(actual.title, expected.title);
- do_check_eq(actual.frecency, expected.frecency);
- do_check_eq(actual.lastVisitDate, expected.lastVisitDate);
- }
-}
-
-function makeLinks(frecRangeStart, frecRangeEnd, step) {
- let links = [];
- // Remember, links are ordered by frecency descending.
- for (let i = frecRangeEnd; i > frecRangeStart; i -= step) {
- links.push(makeLink(i));
- }
- return links;
-}
-
-function makeLink(frecency) {
- return {
- url: "http://example" + frecency + ".com/",
- title: "My frecency is " + frecency,
- frecency,
- lastVisitDate: 0,
- };
-}
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -1,10 +1,10 @@
[DEFAULT]
-head =
+head = head.js
support-files =
propertyLists/bug710259_propertyListBinary.plist
propertyLists/bug710259_propertyListXML.plist
chromeappsstore.sqlite
zips/zen.zip
[test_BinarySearch.js]
skip-if = toolkit == 'android'