зеркало из https://github.com/mozilla/gecko-dev.git
Bug 455553 - Part 4 - Shared Module; r=blair,mak,dietrich
This commit is contained in:
Родитель
8d07f57696
Коммит
6aa1de4ea5
|
@ -51,6 +51,7 @@ endif
|
|||
EXTRA_JS_MODULES = \
|
||||
openLocationLastURL.jsm \
|
||||
NetworkPrioritizer.jsm \
|
||||
NewTabUtils.jsm \
|
||||
offlineAppCache.jsm \
|
||||
PageThumbs.jsm \
|
||||
$(NULL)
|
||||
|
|
|
@ -0,0 +1,551 @@
|
|||
/* 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";
|
||||
|
||||
let 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");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||
"resource://gre/modules/PlacesUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gPrivateBrowsing",
|
||||
"@mozilla.org/privatebrowsing;1", "nsIPrivateBrowsingService");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Dict", "resource://gre/modules/Dict.jsm");
|
||||
|
||||
// The preference that tells whether this feature is enabled.
|
||||
const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";
|
||||
|
||||
// The maximum number of results we want to retrieve from history.
|
||||
const HISTORY_RESULTS_LIMIT = 100;
|
||||
|
||||
/**
|
||||
* Singleton that provides storage functionality.
|
||||
*/
|
||||
let Storage = {
|
||||
/**
|
||||
* The dom storage instance used to persist data belonging to the New Tab Page.
|
||||
*/
|
||||
get domStorage() {
|
||||
let uri = Services.io.newURI("about:newtab", null, null);
|
||||
let principal = Services.scriptSecurityManager.getCodebasePrincipal(uri);
|
||||
|
||||
let sm = Services.domStorageManager;
|
||||
let storage = sm.getLocalStorageForPrincipal(principal, "");
|
||||
|
||||
// Cache this value, overwrite the getter.
|
||||
let descriptor = {value: storage, enumerable: true};
|
||||
Object.defineProperty(this, "domStorage", descriptor);
|
||||
|
||||
return storage;
|
||||
},
|
||||
|
||||
/**
|
||||
* The current storage used to persist New Tab Page data. If we're currently
|
||||
* in private browsing mode this will return a PrivateBrowsingStorage
|
||||
* instance.
|
||||
*/
|
||||
get currentStorage() {
|
||||
let storage = this.domStorage;
|
||||
|
||||
// Check if we're starting in private browsing mode.
|
||||
if (gPrivateBrowsing.privateBrowsingEnabled)
|
||||
storage = new PrivateBrowsingStorage(storage);
|
||||
|
||||
// Register an observer to listen for private browsing mode changes.
|
||||
Services.obs.addObserver(this, "private-browsing", true);
|
||||
|
||||
// Cache this value, overwrite the getter.
|
||||
let descriptor = {value: storage, enumerable: true, writable: true};
|
||||
Object.defineProperty(this, "currentStorage", descriptor);
|
||||
|
||||
return storage;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
value = JSON.parse(this.currentStorage.getItem(aKey));
|
||||
} 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) {
|
||||
this.currentStorage.setItem(aKey, JSON.stringify(aValue));
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the storage and removes all values.
|
||||
*/
|
||||
clear: function Storage_clear() {
|
||||
this.currentStorage.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Implements the nsIObserver interface to get notified about private
|
||||
* browsing mode changes.
|
||||
*/
|
||||
observe: function Storage_observe(aSubject, aTopic, aData) {
|
||||
if (aData == "enter") {
|
||||
// When switching to private browsing mode we keep the current state
|
||||
// of the grid and provide a volatile storage for it that is
|
||||
// discarded upon leaving private browsing.
|
||||
this.currentStorage = new PrivateBrowsingStorage(this.domStorage);
|
||||
} else {
|
||||
// Reset to normal DOM storage.
|
||||
this.currentStorage = this.domStorage;
|
||||
|
||||
// When switching back from private browsing we need to reset the
|
||||
// grid and re-read its values from the underlying storage. We don't
|
||||
// want any data from private browsing to show up.
|
||||
PinnedLinks.resetCache();
|
||||
BlockedLinks.resetCache();
|
||||
|
||||
Pages.update();
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference])
|
||||
};
|
||||
|
||||
/**
|
||||
* This class implements a temporary storage used while the user is in private
|
||||
* browsing mode. It is discarded when leaving pb mode.
|
||||
*/
|
||||
function PrivateBrowsingStorage(aStorage) {
|
||||
this._data = new Dict();
|
||||
|
||||
for (let i = 0; i < aStorage.length; i++) {
|
||||
let key = aStorage.key(i);
|
||||
this._data.set(key, aStorage.getItem(key));
|
||||
}
|
||||
}
|
||||
|
||||
PrivateBrowsingStorage.prototype = {
|
||||
/**
|
||||
* The data store.
|
||||
*/
|
||||
_data: null,
|
||||
|
||||
/**
|
||||
* Gets the value for a given key from the storage.
|
||||
* @param aKey The storage key.
|
||||
* @param aDefault A default value if the key doesn't exist.
|
||||
* @return The value for the given key.
|
||||
*/
|
||||
getItem: function PrivateBrowsingStorage_getItem(aKey) {
|
||||
return this._data.get(aKey);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the storage value for a given key.
|
||||
* @param aKey The storage key.
|
||||
* @param aValue The value to set.
|
||||
*/
|
||||
setItem: function PrivateBrowsingStorage_setItem(aKey, aValue) {
|
||||
this._data.set(aKey, aValue);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the storage and removes all values.
|
||||
*/
|
||||
clear: function PrivateBrowsingStorage_clear() {
|
||||
this._data.listkeys().forEach(function (akey) {
|
||||
this._data.del(aKey);
|
||||
}, this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Singleton that serves as a registry for all open 'New Tab Page's.
|
||||
*/
|
||||
let AllPages = {
|
||||
/**
|
||||
* The array containing all active pages.
|
||||
*/
|
||||
_pages: [],
|
||||
|
||||
/**
|
||||
* Tells whether we already added a preference observer.
|
||||
*/
|
||||
_observing: false,
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
// Add the preference observer if we haven't already.
|
||||
if (!this._observing) {
|
||||
this._observing = true;
|
||||
Services.prefs.addObserver(PREF_NEWTAB_ENABLED, this, true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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);
|
||||
this._pages.splice(index, 1);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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);
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference])
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
Storage.set("pinnedLinks", this.links);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
Storage.set("pinnedLinks", this.links);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.links[aLink.url] = 1;
|
||||
|
||||
// Make sure we unpin blocked links.
|
||||
PinnedLinks.unpin(aLink);
|
||||
|
||||
Storage.set("blockedLinks", this.links);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns whether a given link is blocked.
|
||||
* @param aLink The link to check.
|
||||
*/
|
||||
isBlocked: function BlockedLinks_isBlocked(aLink) {
|
||||
return (aLink.url in this.links);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
// We don't want source redirects for this query.
|
||||
options.redirectsMode = Ci.nsINavHistoryQueryOptions.REDIRECTS_MODE_TARGET;
|
||||
|
||||
let links = [];
|
||||
|
||||
let callback = {
|
||||
handleResult: function (aResultSet) {
|
||||
let row;
|
||||
|
||||
while (row = aResultSet.getNextRow()) {
|
||||
let url = row.getResultByIndex(1);
|
||||
let title = row.getResultByIndex(2);
|
||||
links.push({url: url, title: title});
|
||||
}
|
||||
},
|
||||
|
||||
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.
|
||||
*/
|
||||
_links: [],
|
||||
|
||||
/**
|
||||
* The default provider for links.
|
||||
*/
|
||||
_provider: PlacesProvider,
|
||||
|
||||
/**
|
||||
* Populates the cache with fresh links from the current provider.
|
||||
* @param aCallback The callback to call when finished (optional).
|
||||
*/
|
||||
populateCache: function Links_populateCache(aCallback) {
|
||||
let self = this;
|
||||
|
||||
this._provider.getLinks(function (aLinks) {
|
||||
self._links = aLinks;
|
||||
aCallback && aCallback();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
this._links = [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Singleton that provides the public API of this JSM.
|
||||
*/
|
||||
let NewTabUtils = {
|
||||
_initialized: false,
|
||||
|
||||
/**
|
||||
* Initializes and prepares the NewTabUtils module.
|
||||
*/
|
||||
init: function NewTabUtils_init() {
|
||||
if (!this._initialized) {
|
||||
// Prefetch the links.
|
||||
Links.populateCache();
|
||||
|
||||
this._initialized = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets the NewTabUtils module, its links and its storage.
|
||||
*/
|
||||
reset: function NewTabUtils_reset() {
|
||||
Storage.clear();
|
||||
Links.resetCache();
|
||||
PinnedLinks.resetCache();
|
||||
BlockedLinks.resetCache();
|
||||
},
|
||||
|
||||
allPages: AllPages,
|
||||
links: Links,
|
||||
pinnedLinks: PinnedLinks,
|
||||
blockedLinks: BlockedLinks
|
||||
};
|
|
@ -77,10 +77,12 @@ let initTable = [
|
|||
["perms", "@mozilla.org/permissionmanager;1", "nsIPermissionManager"],
|
||||
["prompt", "@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService"],
|
||||
["scriptloader", "@mozilla.org/moz/jssubscript-loader;1", "mozIJSSubScriptLoader"],
|
||||
["scriptSecurityManager", "@mozilla.org/scriptsecuritymanager;1", "nsIScriptSecurityManager"],
|
||||
#ifdef MOZ_TOOLKIT_SEARCH
|
||||
["search", "@mozilla.org/browser/search-service;1", "nsIBrowserSearchService"],
|
||||
#endif
|
||||
["storage", "@mozilla.org/storage/service;1", "mozIStorageService"],
|
||||
["domStorageManager", "@mozilla.org/dom/storagemanager;1", "nsIDOMStorageManager"],
|
||||
["strings", "@mozilla.org/intl/stringbundle;1", "nsIStringBundleService"],
|
||||
["telemetry", "@mozilla.org/base/telemetry;1", "nsITelemetry"],
|
||||
["tm", "@mozilla.org/thread-manager;1", "nsIThreadManager"],
|
||||
|
|
Загрузка…
Ссылка в новой задаче