2012-01-25 14:44:27 -08:00
|
|
|
/* 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/. */
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
2012-10-31 09:13:28 -07:00
|
|
|
this.EXPORTED_SYMBOLS = ["NewTabUtils"];
|
2012-01-25 14:44:27 -08:00
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
|
|
|
"resource://gre/modules/PlacesUtils.jsm");
|
|
|
|
|
2012-08-21 11:17:18 -07:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
|
|
|
|
"resource:///modules/PageThumbs.jsm");
|
|
|
|
|
2012-12-03 11:19:17 -08:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
|
|
|
"resource://gre/modules/FileUtils.jsm");
|
|
|
|
|
2012-07-16 15:35:13 -07:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "gPrincipal", function () {
|
|
|
|
let uri = Services.io.newURI("about:newtab", null, null);
|
2012-07-21 00:29:40 -07:00
|
|
|
return Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
|
2012-07-16 15:35:13 -07:00
|
|
|
});
|
|
|
|
|
2012-12-03 11:19:17 -08:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "gCryptoHash", function () {
|
|
|
|
return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
|
|
|
|
});
|
|
|
|
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "gUnicodeConverter", function () {
|
|
|
|
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
|
|
|
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
|
|
|
converter.charset = 'utf8';
|
|
|
|
return converter;
|
|
|
|
});
|
|
|
|
|
2012-01-25 14:44:27 -08:00
|
|
|
// The preference that tells whether this feature is enabled.
|
|
|
|
const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";
|
|
|
|
|
2012-08-16 22:01:03 -07:00
|
|
|
// The preference that tells the number of rows of the newtab grid.
|
|
|
|
const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
|
|
|
|
|
|
|
|
// The preference that tells the number of columns of the newtab grid.
|
|
|
|
const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
|
|
|
|
|
2012-01-25 14:44:27 -08:00
|
|
|
// The maximum number of results we want to retrieve from history.
|
|
|
|
const HISTORY_RESULTS_LIMIT = 100;
|
|
|
|
|
2012-04-24 18:45:01 -07:00
|
|
|
// The gather telemetry topic.
|
|
|
|
const TOPIC_GATHER_TELEMETRY = "gather-telemetry";
|
|
|
|
|
2012-12-03 11:19:17 -08:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
gCryptoHash.init(gCryptoHash.MD5);
|
|
|
|
gCryptoHash.update(value, value.length);
|
|
|
|
return gCryptoHash.finish(true);
|
|
|
|
}
|
|
|
|
|
2012-01-25 14:44:27 -08:00
|
|
|
/**
|
|
|
|
* Singleton that provides storage functionality.
|
|
|
|
*/
|
2012-12-03 11:19:17 -08:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "Storage", function() {
|
|
|
|
return new LinksStorage();
|
|
|
|
});
|
|
|
|
|
|
|
|
function LinksStorage() {
|
|
|
|
// Handle migration of data across versions.
|
|
|
|
try {
|
|
|
|
if (this._storedVersion < this._version) {
|
|
|
|
// This is either an upgrade, or version information is missing.
|
|
|
|
if (this._storedVersion < 1) {
|
|
|
|
this._migrateToV1();
|
|
|
|
}
|
|
|
|
// Add further migration steps here.
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// This is a downgrade. Since we cannot predict future, upgrades should
|
|
|
|
// be backwards compatible. We will set the version to the old value
|
|
|
|
// regardless, so, on next upgrade, the migration steps will run again.
|
|
|
|
// For this reason, they should also be able to run multiple times, even
|
|
|
|
// on top of an already up-to-date storage.
|
|
|
|
}
|
|
|
|
} catch (ex) {
|
|
|
|
// Something went wrong in the update process, we can't recover from here,
|
|
|
|
// so just clear the storage and start from scratch (dataloss!).
|
|
|
|
Components.utils.reportError(
|
|
|
|
"Unable to migrate the newTab storage to the current version. "+
|
|
|
|
"Restarting from scratch.\n" + ex);
|
|
|
|
this.clear();
|
|
|
|
}
|
2012-01-25 14:44:27 -08:00
|
|
|
|
2012-12-03 11:19:17 -08:00
|
|
|
// Set the version to the current one.
|
|
|
|
this._storedVersion = this._version;
|
|
|
|
}
|
|
|
|
|
|
|
|
LinksStorage.prototype = {
|
|
|
|
get _version() 1,
|
|
|
|
|
|
|
|
get _prefs() Object.freeze({
|
|
|
|
pinnedLinks: "browser.newtabpage.pinned",
|
|
|
|
blockedLinks: "browser.newtabpage.blocked",
|
|
|
|
}),
|
|
|
|
|
|
|
|
get _storedVersion() {
|
|
|
|
if (this.__storedVersion === undefined) {
|
|
|
|
try {
|
|
|
|
this.__storedVersion =
|
|
|
|
Services.prefs.getIntPref("browser.newtabpage.storageVersion");
|
|
|
|
} catch (ex) {
|
|
|
|
this.__storedVersion = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.__storedVersion;
|
|
|
|
},
|
|
|
|
set _storedVersion(aValue) {
|
|
|
|
Services.prefs.setIntPref("browser.newtabpage.storageVersion", aValue);
|
|
|
|
this.__storedVersion = aValue;
|
|
|
|
return aValue;
|
|
|
|
},
|
2012-01-25 14:44:27 -08:00
|
|
|
|
2012-12-03 11:19:17 -08:00
|
|
|
/**
|
|
|
|
* V1 changes storage from chromeappsstore.sqlite to prefs.
|
|
|
|
*/
|
|
|
|
_migrateToV1: function Storage__migrateToV1() {
|
|
|
|
// Import data from the old chromeappsstore.sqlite file, if exists.
|
|
|
|
let file = FileUtils.getFile("ProfD", ["chromeappsstore.sqlite"]);
|
|
|
|
if (!file.exists())
|
|
|
|
return;
|
|
|
|
let db = Services.storage.openUnsharedDatabase(file);
|
|
|
|
let stmt = db.createStatement(
|
|
|
|
"SELECT key, value FROM webappsstore2 WHERE scope = 'batwen.:about'");
|
|
|
|
try {
|
|
|
|
while (stmt.executeStep()) {
|
|
|
|
let key = stmt.row.key;
|
|
|
|
let value = JSON.parse(stmt.row.value);
|
|
|
|
switch (key) {
|
|
|
|
case "pinnedLinks":
|
|
|
|
this.set(key, value);
|
|
|
|
break;
|
|
|
|
case "blockedLinks":
|
|
|
|
// Convert urls to hashes.
|
|
|
|
let hashes = {};
|
|
|
|
for (let url in value) {
|
|
|
|
hashes[toHash(url)] = 1;
|
|
|
|
}
|
|
|
|
this.set(key, hashes);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// Ignore unknown keys.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
stmt.finalize();
|
|
|
|
db.close();
|
|
|
|
}
|
2012-01-25 14:44:27 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the value for a given key from the storage.
|
|
|
|
* @param aKey The storage key (a string).
|
|
|
|
* @param aDefault A default value if the key doesn't exist.
|
|
|
|
* @return The value for the given key.
|
|
|
|
*/
|
|
|
|
get: function Storage_get(aKey, aDefault) {
|
|
|
|
let value;
|
|
|
|
try {
|
2012-12-03 11:19:17 -08:00
|
|
|
let prefValue = Services.prefs.getComplexValue(this._prefs[aKey],
|
|
|
|
Ci.nsISupportsString).data;
|
|
|
|
value = JSON.parse(prefValue);
|
2012-01-25 14:44:27 -08:00
|
|
|
} catch (e) {}
|
|
|
|
return value || aDefault;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the storage value for a given key.
|
|
|
|
* @param aKey The storage key (a string).
|
|
|
|
* @param aValue The value to set.
|
|
|
|
*/
|
|
|
|
set: function Storage_set(aKey, aValue) {
|
2012-12-03 11:19:17 -08:00
|
|
|
// Page titles may contain unicode, thus use complex values.
|
|
|
|
let string = Cc["@mozilla.org/supports-string;1"]
|
|
|
|
.createInstance(Ci.nsISupportsString);
|
|
|
|
string.data = JSON.stringify(aValue);
|
|
|
|
Services.prefs.setComplexValue(this._prefs[aKey], Ci.nsISupportsString,
|
|
|
|
string);
|
2012-01-25 14:44:27 -08:00
|
|
|
},
|
|
|
|
|
2012-08-23 15:34:52 -07:00
|
|
|
/**
|
|
|
|
* Removes the storage value for a given key.
|
|
|
|
* @param aKey The storage key (a string).
|
|
|
|
*/
|
|
|
|
remove: function Storage_remove(aKey) {
|
|
|
|
Services.prefs.clearUserPref(this._prefs[aKey]);
|
|
|
|
},
|
|
|
|
|
2012-01-25 14:44:27 -08:00
|
|
|
/**
|
|
|
|
* Clears the storage and removes all values.
|
|
|
|
*/
|
|
|
|
clear: function Storage_clear() {
|
2012-08-23 15:34:52 -07:00
|
|
|
for (let key in this._prefs) {
|
|
|
|
this.remove(key);
|
2012-12-03 11:19:17 -08:00
|
|
|
}
|
|
|
|
}
|
2012-01-25 14:44:27 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Singleton that serves as a registry for all open 'New Tab Page's.
|
|
|
|
*/
|
|
|
|
let AllPages = {
|
|
|
|
/**
|
|
|
|
* The array containing all active pages.
|
|
|
|
*/
|
|
|
|
_pages: [],
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cached value that tells whether the New Tab Page feature is enabled.
|
|
|
|
*/
|
|
|
|
_enabled: null,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a page to the internal list of pages.
|
|
|
|
* @param aPage The page to register.
|
|
|
|
*/
|
|
|
|
register: function AllPages_register(aPage) {
|
|
|
|
this._pages.push(aPage);
|
2012-03-05 11:59:56 -08:00
|
|
|
this._addObserver();
|
2012-01-25 14:44:27 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes a page from the internal list of pages.
|
|
|
|
* @param aPage The page to unregister.
|
|
|
|
*/
|
|
|
|
unregister: function AllPages_unregister(aPage) {
|
|
|
|
let index = this._pages.indexOf(aPage);
|
2012-06-15 10:55:45 -07:00
|
|
|
if (index > -1)
|
|
|
|
this._pages.splice(index, 1);
|
2012-01-25 14:44:27 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns whether the 'New Tab Page' is enabled.
|
|
|
|
*/
|
|
|
|
get enabled() {
|
|
|
|
if (this._enabled === null)
|
|
|
|
this._enabled = Services.prefs.getBoolPref(PREF_NEWTAB_ENABLED);
|
|
|
|
|
|
|
|
return this._enabled;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enables or disables the 'New Tab Page' feature.
|
|
|
|
*/
|
|
|
|
set enabled(aEnabled) {
|
|
|
|
if (this.enabled != aEnabled)
|
|
|
|
Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, !!aEnabled);
|
|
|
|
},
|
|
|
|
|
2012-03-05 11:59:56 -08:00
|
|
|
/**
|
|
|
|
* Returns the number of registered New Tab Pages (i.e. the number of open
|
|
|
|
* about:newtab instances).
|
|
|
|
*/
|
|
|
|
get length() {
|
|
|
|
return this._pages.length;
|
|
|
|
},
|
|
|
|
|
2012-01-25 14:44:27 -08:00
|
|
|
/**
|
|
|
|
* Updates all currently active pages but the given one.
|
|
|
|
* @param aExceptPage The page to exclude from updating.
|
|
|
|
*/
|
|
|
|
update: function AllPages_update(aExceptPage) {
|
|
|
|
this._pages.forEach(function (aPage) {
|
|
|
|
if (aExceptPage != aPage)
|
|
|
|
aPage.update();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements the nsIObserver interface to get notified when the preference
|
|
|
|
* value changes.
|
|
|
|
*/
|
|
|
|
observe: function AllPages_observe() {
|
|
|
|
// Clear the cached value.
|
|
|
|
this._enabled = null;
|
|
|
|
|
|
|
|
let args = Array.slice(arguments);
|
|
|
|
|
|
|
|
this._pages.forEach(function (aPage) {
|
|
|
|
aPage.observe.apply(aPage, args);
|
|
|
|
}, this);
|
|
|
|
},
|
|
|
|
|
2012-03-05 11:59:56 -08:00
|
|
|
/**
|
|
|
|
* Adds a preference observer and turns itself into a no-op after the first
|
|
|
|
* invokation.
|
|
|
|
*/
|
|
|
|
_addObserver: function AllPages_addObserver() {
|
|
|
|
Services.prefs.addObserver(PREF_NEWTAB_ENABLED, this, true);
|
|
|
|
this._addObserver = function () {};
|
|
|
|
},
|
|
|
|
|
2012-01-25 14:44:27 -08:00
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
|
|
|
Ci.nsISupportsWeakReference])
|
|
|
|
};
|
|
|
|
|
2012-08-16 22:01:03 -07:00
|
|
|
/**
|
|
|
|
* Singleton that keeps Grid preferences
|
|
|
|
*/
|
|
|
|
let GridPrefs = {
|
|
|
|
/**
|
|
|
|
* Cached value that tells the number of rows of newtab grid.
|
|
|
|
*/
|
|
|
|
_gridRows: null,
|
|
|
|
get gridRows() {
|
|
|
|
if (!this._gridRows) {
|
|
|
|
this._gridRows = Math.max(1, Services.prefs.getIntPref(PREF_NEWTAB_ROWS));
|
|
|
|
}
|
|
|
|
|
|
|
|
return this._gridRows;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cached value that tells the number of columns of newtab grid.
|
|
|
|
*/
|
|
|
|
_gridColumns: null,
|
|
|
|
get gridColumns() {
|
|
|
|
if (!this._gridColumns) {
|
|
|
|
this._gridColumns = Math.max(1, Services.prefs.getIntPref(PREF_NEWTAB_COLUMNS));
|
|
|
|
}
|
|
|
|
|
|
|
|
return this._gridColumns;
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes object. Adds a preference observer
|
|
|
|
*/
|
|
|
|
init: function GridPrefs_init() {
|
|
|
|
Services.prefs.addObserver(PREF_NEWTAB_ROWS, this, false);
|
|
|
|
Services.prefs.addObserver(PREF_NEWTAB_COLUMNS, this, false);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 {
|
|
|
|
this._gridColumns = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
AllPages.update();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
GridPrefs.init();
|
|
|
|
|
2012-01-25 14:44:27 -08:00
|
|
|
/**
|
|
|
|
* Singleton that keeps track of all pinned links and their positions in the
|
|
|
|
* grid.
|
|
|
|
*/
|
|
|
|
let PinnedLinks = {
|
|
|
|
/**
|
|
|
|
* The cached list of pinned links.
|
|
|
|
*/
|
|
|
|
_links: null,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The array of pinned links.
|
|
|
|
*/
|
|
|
|
get links() {
|
|
|
|
if (!this._links)
|
|
|
|
this._links = Storage.get("pinnedLinks", []);
|
|
|
|
|
|
|
|
return this._links;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Pins a link at the given position.
|
|
|
|
* @param aLink The link to pin.
|
|
|
|
* @param aIndex The grid index to pin the cell at.
|
|
|
|
*/
|
|
|
|
pin: function PinnedLinks_pin(aLink, aIndex) {
|
|
|
|
// Clear the link's old position, if any.
|
|
|
|
this.unpin(aLink);
|
|
|
|
|
|
|
|
this.links[aIndex] = aLink;
|
2012-06-26 12:44:29 -07:00
|
|
|
this.save();
|
2012-01-25 14:44:27 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unpins a given link.
|
|
|
|
* @param aLink The link to unpin.
|
|
|
|
*/
|
|
|
|
unpin: function PinnedLinks_unpin(aLink) {
|
|
|
|
let index = this._indexOfLink(aLink);
|
|
|
|
if (index != -1) {
|
|
|
|
this.links[index] = null;
|
2012-06-26 12:44:29 -07:00
|
|
|
this.save();
|
2012-01-25 14:44:27 -08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-06-26 12:44:29 -07:00
|
|
|
/**
|
|
|
|
* Saves the current list of pinned links.
|
|
|
|
*/
|
|
|
|
save: function PinnedLinks_save() {
|
|
|
|
Storage.set("pinnedLinks", this.links);
|
|
|
|
},
|
|
|
|
|
2012-01-25 14:44:27 -08:00
|
|
|
/**
|
|
|
|
* Checks whether a given link is pinned.
|
|
|
|
* @params aLink The link to check.
|
|
|
|
* @return whether The link is pinned.
|
|
|
|
*/
|
|
|
|
isPinned: function PinnedLinks_isPinned(aLink) {
|
|
|
|
return this._indexOfLink(aLink) != -1;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resets the links cache.
|
|
|
|
*/
|
|
|
|
resetCache: function PinnedLinks_resetCache() {
|
|
|
|
this._links = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Finds the index of a given link in the list of pinned links.
|
|
|
|
* @param aLink The link to find an index for.
|
|
|
|
* @return The link's index.
|
|
|
|
*/
|
|
|
|
_indexOfLink: function PinnedLinks_indexOfLink(aLink) {
|
|
|
|
for (let i = 0; i < this.links.length; i++) {
|
|
|
|
let link = this.links[i];
|
|
|
|
if (link && link.url == aLink.url)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The given link is unpinned.
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Singleton that keeps track of all blocked links in the grid.
|
|
|
|
*/
|
|
|
|
let BlockedLinks = {
|
|
|
|
/**
|
|
|
|
* The cached list of blocked links.
|
|
|
|
*/
|
|
|
|
_links: null,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The list of blocked links.
|
|
|
|
*/
|
|
|
|
get links() {
|
|
|
|
if (!this._links)
|
|
|
|
this._links = Storage.get("blockedLinks", {});
|
|
|
|
|
|
|
|
return this._links;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Blocks a given link.
|
|
|
|
* @param aLink The link to block.
|
|
|
|
*/
|
|
|
|
block: function BlockedLinks_block(aLink) {
|
2012-12-03 11:19:17 -08:00
|
|
|
this.links[toHash(aLink.url)] = 1;
|
2012-06-26 12:44:29 -07:00
|
|
|
this.save();
|
2012-01-25 14:44:27 -08:00
|
|
|
|
|
|
|
// Make sure we unpin blocked links.
|
|
|
|
PinnedLinks.unpin(aLink);
|
|
|
|
},
|
|
|
|
|
2012-03-16 01:00:18 -07:00
|
|
|
/**
|
|
|
|
* Unblocks a given link.
|
|
|
|
* @param aLink The link to unblock.
|
|
|
|
*/
|
|
|
|
unblock: function BlockedLinks_unblock(aLink) {
|
2012-06-26 12:44:29 -07:00
|
|
|
if (this.isBlocked(aLink)) {
|
2012-12-03 11:19:17 -08:00
|
|
|
delete this.links[toHash(aLink.url)];
|
2012-06-26 12:44:29 -07:00
|
|
|
this.save();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Saves the current list of blocked links.
|
|
|
|
*/
|
|
|
|
save: function BlockedLinks_save() {
|
|
|
|
Storage.set("blockedLinks", this.links);
|
2012-03-16 01:00:18 -07:00
|
|
|
},
|
|
|
|
|
2012-01-25 14:44:27 -08:00
|
|
|
/**
|
|
|
|
* Returns whether a given link is blocked.
|
|
|
|
* @param aLink The link to check.
|
|
|
|
*/
|
|
|
|
isBlocked: function BlockedLinks_isBlocked(aLink) {
|
2012-12-03 11:19:17 -08:00
|
|
|
return (toHash(aLink.url) in this.links);
|
2012-01-25 14:44:27 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks whether the list of blocked links is empty.
|
|
|
|
* @return Whether the list is empty.
|
|
|
|
*/
|
|
|
|
isEmpty: function BlockedLinks_isEmpty() {
|
|
|
|
return Object.keys(this.links).length == 0;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resets the links cache.
|
|
|
|
*/
|
|
|
|
resetCache: function BlockedLinks_resetCache() {
|
|
|
|
this._links = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Singleton that serves as the default link provider for the grid. It queries
|
|
|
|
* the history to retrieve the most frequently visited sites.
|
|
|
|
*/
|
|
|
|
let PlacesProvider = {
|
|
|
|
/**
|
|
|
|
* Gets the current set of links delivered by this provider.
|
|
|
|
* @param aCallback The function that the array of links is passed to.
|
|
|
|
*/
|
|
|
|
getLinks: function PlacesProvider_getLinks(aCallback) {
|
|
|
|
let options = PlacesUtils.history.getNewQueryOptions();
|
|
|
|
options.maxResults = HISTORY_RESULTS_LIMIT;
|
|
|
|
|
|
|
|
// Sort by frecency, descending.
|
|
|
|
options.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_FRECENCY_DESCENDING
|
|
|
|
|
|
|
|
let links = [];
|
|
|
|
|
|
|
|
let callback = {
|
|
|
|
handleResult: function (aResultSet) {
|
|
|
|
let row;
|
|
|
|
|
2012-12-18 14:27:56 -08:00
|
|
|
while ((row = aResultSet.getNextRow())) {
|
2012-01-25 14:44:27 -08:00
|
|
|
let url = row.getResultByIndex(1);
|
2012-07-16 15:35:13 -07:00
|
|
|
if (LinkChecker.checkLoadURI(url)) {
|
|
|
|
let title = row.getResultByIndex(2);
|
|
|
|
links.push({url: url, title: title});
|
|
|
|
}
|
2012-01-25 14:44:27 -08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
handleError: function (aError) {
|
|
|
|
// Should we somehow handle this error?
|
|
|
|
aCallback([]);
|
|
|
|
},
|
|
|
|
|
|
|
|
handleCompletion: function (aReason) {
|
|
|
|
aCallback(links);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Execute the query.
|
|
|
|
let query = PlacesUtils.history.getNewQuery();
|
|
|
|
let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase);
|
|
|
|
db.asyncExecuteLegacyQueries([query], 1, options, callback);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 with title
|
|
|
|
* and url properties.
|
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
*
|
|
|
|
* {url: "http://www.mozilla.org/", title: "Mozilla"}
|
|
|
|
*/
|
|
|
|
let Links = {
|
|
|
|
/**
|
|
|
|
* The links cache.
|
|
|
|
*/
|
2012-01-27 05:55:03 -08:00
|
|
|
_links: null,
|
2012-01-25 14:44:27 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The default provider for links.
|
|
|
|
*/
|
|
|
|
_provider: PlacesProvider,
|
|
|
|
|
2012-01-27 05:55:03 -08:00
|
|
|
/**
|
|
|
|
* List of callbacks waiting for the cache to be populated.
|
|
|
|
*/
|
|
|
|
_populateCallbacks: [],
|
|
|
|
|
2012-01-25 14:44:27 -08:00
|
|
|
/**
|
|
|
|
* Populates the cache with fresh links from the current provider.
|
|
|
|
* @param aCallback The callback to call when finished (optional).
|
2012-01-27 05:55:03 -08:00
|
|
|
* @param aForce When true, populates the cache even when it's already filled.
|
|
|
|
*/
|
|
|
|
populateCache: function Links_populateCache(aCallback, aForce) {
|
|
|
|
let callbacks = this._populateCallbacks;
|
|
|
|
|
|
|
|
// Enqueue the current callback.
|
|
|
|
callbacks.push(aCallback);
|
|
|
|
|
|
|
|
// There was a callback waiting already, thus the cache has not yet been
|
|
|
|
// populated.
|
|
|
|
if (callbacks.length > 1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
function executeCallbacks() {
|
|
|
|
while (callbacks.length) {
|
|
|
|
let callback = callbacks.shift();
|
|
|
|
if (callback) {
|
|
|
|
try {
|
|
|
|
callback();
|
|
|
|
} catch (e) {
|
|
|
|
// We want to proceed even if a callback fails.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-01-25 14:44:27 -08:00
|
|
|
|
2012-01-27 05:55:03 -08:00
|
|
|
if (this._links && !aForce) {
|
|
|
|
executeCallbacks();
|
|
|
|
} else {
|
|
|
|
this._provider.getLinks(function (aLinks) {
|
|
|
|
this._links = aLinks;
|
|
|
|
executeCallbacks();
|
|
|
|
}.bind(this));
|
2012-03-05 11:59:56 -08:00
|
|
|
|
|
|
|
this._addObserver();
|
2012-01-27 05:55:03 -08:00
|
|
|
}
|
2012-01-25 14:44:27 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the current set of links contained in the grid.
|
|
|
|
* @return The links in the grid.
|
|
|
|
*/
|
|
|
|
getLinks: function Links_getLinks() {
|
|
|
|
let pinnedLinks = Array.slice(PinnedLinks.links);
|
|
|
|
|
|
|
|
// Filter blocked and pinned links.
|
|
|
|
let links = this._links.filter(function (link) {
|
|
|
|
return !BlockedLinks.isBlocked(link) && !PinnedLinks.isPinned(link);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Try to fill the gaps between pinned links.
|
|
|
|
for (let i = 0; i < pinnedLinks.length && links.length; i++)
|
|
|
|
if (!pinnedLinks[i])
|
|
|
|
pinnedLinks[i] = links.shift();
|
|
|
|
|
|
|
|
// Append the remaining links if any.
|
|
|
|
if (links.length)
|
|
|
|
pinnedLinks = pinnedLinks.concat(links);
|
|
|
|
|
|
|
|
return pinnedLinks;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resets the links cache.
|
|
|
|
*/
|
|
|
|
resetCache: function Links_resetCache() {
|
2012-03-15 15:28:22 -07:00
|
|
|
this._links = null;
|
2012-03-05 11:59:56 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements the nsIObserver interface to get notified about browser history
|
|
|
|
* sanitization.
|
|
|
|
*/
|
|
|
|
observe: function Links_observe(aSubject, aTopic, aData) {
|
|
|
|
// Make sure to update open about:newtab instances. If there are no opened
|
|
|
|
// pages we can just wait for the next new tab to populate the cache again.
|
|
|
|
if (AllPages.length && AllPages.enabled)
|
|
|
|
this.populateCache(function () { AllPages.update() }, true);
|
|
|
|
else
|
|
|
|
this._links = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a sanitization observer and turns itself into a no-op after the first
|
|
|
|
* invokation.
|
|
|
|
*/
|
|
|
|
_addObserver: function Links_addObserver() {
|
|
|
|
Services.obs.addObserver(this, "browser:purge-session-history", true);
|
|
|
|
this._addObserver = function () {};
|
|
|
|
},
|
|
|
|
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
|
|
|
Ci.nsISupportsWeakReference])
|
2012-01-25 14:44:27 -08:00
|
|
|
};
|
|
|
|
|
2012-04-24 18:45:01 -07:00
|
|
|
/**
|
|
|
|
* Singleton used to collect telemetry data.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
let Telemetry = {
|
|
|
|
/**
|
|
|
|
* Initializes object.
|
|
|
|
*/
|
|
|
|
init: function Telemetry_init() {
|
|
|
|
Services.obs.addObserver(this, TOPIC_GATHER_TELEMETRY, false);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Collects data.
|
|
|
|
*/
|
|
|
|
_collect: function Telemetry_collect() {
|
|
|
|
let probes = [
|
|
|
|
{ histogram: "NEWTAB_PAGE_ENABLED",
|
|
|
|
value: AllPages.enabled },
|
|
|
|
{ histogram: "NEWTAB_PAGE_PINNED_SITES_COUNT",
|
|
|
|
value: PinnedLinks.links.length },
|
|
|
|
{ histogram: "NEWTAB_PAGE_BLOCKED_SITES_COUNT",
|
|
|
|
value: Object.keys(BlockedLinks.links).length }
|
|
|
|
];
|
|
|
|
|
|
|
|
probes.forEach(function Telemetry_collect_forEach(aProbe) {
|
|
|
|
Services.telemetry.getHistogramById(aProbe.histogram)
|
|
|
|
.add(aProbe.value);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Listens for gather telemetry topic.
|
|
|
|
*/
|
|
|
|
observe: function Telemetry_observe(aSubject, aTopic, aData) {
|
|
|
|
this._collect();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-07-16 15:35:13 -07:00
|
|
|
/**
|
|
|
|
* Singleton that checks if a given link should be displayed on about:newtab
|
|
|
|
* or if we should rather not do it for security reasons. URIs that inherit
|
|
|
|
* their caller's principal will be filtered.
|
|
|
|
*/
|
|
|
|
let LinkChecker = {
|
|
|
|
_cache: {},
|
|
|
|
|
|
|
|
get flags() {
|
2012-09-13 07:17:00 -07:00
|
|
|
return Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL |
|
|
|
|
Ci.nsIScriptSecurityManager.DONT_REPORT_ERRORS;
|
2012-07-16 15:35:13 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
checkLoadURI: function LinkChecker_checkLoadURI(aURI) {
|
|
|
|
if (!(aURI in this._cache))
|
|
|
|
this._cache[aURI] = this._doCheckLoadURI(aURI);
|
|
|
|
|
|
|
|
return this._cache[aURI];
|
|
|
|
},
|
|
|
|
|
|
|
|
_doCheckLoadURI: function Links_doCheckLoadURI(aURI) {
|
|
|
|
try {
|
|
|
|
Services.scriptSecurityManager.
|
|
|
|
checkLoadURIStrWithPrincipal(gPrincipal, aURI, this.flags);
|
|
|
|
return true;
|
|
|
|
} catch (e) {
|
|
|
|
// We got a weird URI or one that would inherit the caller's principal.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-08-21 11:17:18 -07:00
|
|
|
let ExpirationFilter = {
|
|
|
|
init: function ExpirationFilter_init() {
|
|
|
|
PageThumbs.addExpirationFilter(this);
|
|
|
|
},
|
|
|
|
|
|
|
|
filterForThumbnailExpiration:
|
|
|
|
function ExpirationFilter_filterForThumbnailExpiration(aCallback) {
|
|
|
|
if (!AllPages.enabled) {
|
|
|
|
aCallback([]);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Links.populateCache(function () {
|
|
|
|
let urls = [];
|
|
|
|
|
|
|
|
// Add all URLs to the list that we want to keep thumbnails for.
|
|
|
|
for (let link of Links.getLinks().slice(0, 25)) {
|
|
|
|
if (link && link.url)
|
|
|
|
urls.push(link.url);
|
|
|
|
}
|
|
|
|
|
|
|
|
aCallback(urls);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-01-25 14:44:27 -08:00
|
|
|
/**
|
|
|
|
* Singleton that provides the public API of this JSM.
|
|
|
|
*/
|
2012-10-31 09:13:28 -07:00
|
|
|
this.NewTabUtils = {
|
2012-08-21 11:17:18 -07:00
|
|
|
_initialized: false,
|
|
|
|
|
|
|
|
init: function NewTabUtils_init() {
|
|
|
|
if (!this._initialized) {
|
|
|
|
this._initialized = true;
|
|
|
|
ExpirationFilter.init();
|
|
|
|
Telemetry.init();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-01-25 14:44:27 -08:00
|
|
|
/**
|
2012-03-15 15:28:22 -07:00
|
|
|
* Restores all sites that have been removed from the grid.
|
2012-01-25 14:44:27 -08:00
|
|
|
*/
|
2012-03-15 15:28:22 -07:00
|
|
|
restore: function NewTabUtils_restore() {
|
2012-01-25 14:44:27 -08:00
|
|
|
Storage.clear();
|
|
|
|
Links.resetCache();
|
|
|
|
PinnedLinks.resetCache();
|
|
|
|
BlockedLinks.resetCache();
|
2012-03-15 15:28:22 -07:00
|
|
|
|
|
|
|
Links.populateCache(function () {
|
|
|
|
AllPages.update();
|
|
|
|
}, true);
|
2012-01-25 14:44:27 -08:00
|
|
|
},
|
|
|
|
|
2012-08-23 15:34:52 -07:00
|
|
|
/**
|
|
|
|
* Undoes all sites that have been removed from the grid and keep the pinned
|
|
|
|
* tabs.
|
|
|
|
* @param aCallback the callback method.
|
|
|
|
*/
|
|
|
|
undoAll: function NewTabUtils_undoAll(aCallback) {
|
|
|
|
Storage.remove("blockedLinks");
|
|
|
|
Links.resetCache();
|
|
|
|
BlockedLinks.resetCache();
|
|
|
|
Links.populateCache(aCallback, true);
|
|
|
|
},
|
|
|
|
|
2012-01-25 14:44:27 -08:00
|
|
|
links: Links,
|
2012-07-16 15:35:13 -07:00
|
|
|
allPages: AllPages,
|
|
|
|
linkChecker: LinkChecker,
|
2012-01-25 14:44:27 -08:00
|
|
|
pinnedLinks: PinnedLinks,
|
2012-08-16 22:01:03 -07:00
|
|
|
blockedLinks: BlockedLinks,
|
|
|
|
gridPrefs: GridPrefs
|
2012-01-25 14:44:27 -08:00
|
|
|
};
|