toolkit/modules/NewTabUtils.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 this.EXPORTED_SYMBOLS = ["NewTabUtils"];
michael@0 8
michael@0 9 const Ci = Components.interfaces;
michael@0 10 const Cc = Components.classes;
michael@0 11 const Cu = Components.utils;
michael@0 12
michael@0 13 Cu.import("resource://gre/modules/Services.jsm");
michael@0 14 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 15
michael@0 16 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
michael@0 17 "resource://gre/modules/PlacesUtils.jsm");
michael@0 18
michael@0 19 XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
michael@0 20 "resource://gre/modules/PageThumbs.jsm");
michael@0 21
michael@0 22 XPCOMUtils.defineLazyModuleGetter(this, "BinarySearch",
michael@0 23 "resource://gre/modules/BinarySearch.jsm");
michael@0 24
michael@0 25 XPCOMUtils.defineLazyGetter(this, "Timer", () => {
michael@0 26 return Cu.import("resource://gre/modules/Timer.jsm", {});
michael@0 27 });
michael@0 28
michael@0 29 XPCOMUtils.defineLazyGetter(this, "gPrincipal", function () {
michael@0 30 let uri = Services.io.newURI("about:newtab", null, null);
michael@0 31 return Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
michael@0 32 });
michael@0 33
michael@0 34 XPCOMUtils.defineLazyGetter(this, "gCryptoHash", function () {
michael@0 35 return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
michael@0 36 });
michael@0 37
michael@0 38 XPCOMUtils.defineLazyGetter(this, "gUnicodeConverter", function () {
michael@0 39 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
michael@0 40 .createInstance(Ci.nsIScriptableUnicodeConverter);
michael@0 41 converter.charset = 'utf8';
michael@0 42 return converter;
michael@0 43 });
michael@0 44
michael@0 45 // The preference that tells whether this feature is enabled.
michael@0 46 const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";
michael@0 47
michael@0 48 // The preference that tells the number of rows of the newtab grid.
michael@0 49 const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
michael@0 50
michael@0 51 // The preference that tells the number of columns of the newtab grid.
michael@0 52 const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
michael@0 53
michael@0 54 // The maximum number of results PlacesProvider retrieves from history.
michael@0 55 const HISTORY_RESULTS_LIMIT = 100;
michael@0 56
michael@0 57 // The maximum number of links Links.getLinks will return.
michael@0 58 const LINKS_GET_LINKS_LIMIT = 100;
michael@0 59
michael@0 60 // The gather telemetry topic.
michael@0 61 const TOPIC_GATHER_TELEMETRY = "gather-telemetry";
michael@0 62
michael@0 63 // The amount of time we wait while coalescing updates for hidden pages.
michael@0 64 const SCHEDULE_UPDATE_TIMEOUT_MS = 1000;
michael@0 65
michael@0 66 /**
michael@0 67 * Calculate the MD5 hash for a string.
michael@0 68 * @param aValue
michael@0 69 * The string to convert.
michael@0 70 * @return The base64 representation of the MD5 hash.
michael@0 71 */
michael@0 72 function toHash(aValue) {
michael@0 73 let value = gUnicodeConverter.convertToByteArray(aValue);
michael@0 74 gCryptoHash.init(gCryptoHash.MD5);
michael@0 75 gCryptoHash.update(value, value.length);
michael@0 76 return gCryptoHash.finish(true);
michael@0 77 }
michael@0 78
michael@0 79 /**
michael@0 80 * Singleton that provides storage functionality.
michael@0 81 */
michael@0 82 XPCOMUtils.defineLazyGetter(this, "Storage", function() {
michael@0 83 return new LinksStorage();
michael@0 84 });
michael@0 85
michael@0 86 function LinksStorage() {
michael@0 87 // Handle migration of data across versions.
michael@0 88 try {
michael@0 89 if (this._storedVersion < this._version) {
michael@0 90 // This is either an upgrade, or version information is missing.
michael@0 91 if (this._storedVersion < 1) {
michael@0 92 // Version 1 moved data from DOM Storage to prefs. Since migrating from
michael@0 93 // version 0 is no more supported, we just reportError a dataloss later.
michael@0 94 throw new Error("Unsupported newTab storage version");
michael@0 95 }
michael@0 96 // Add further migration steps here.
michael@0 97 }
michael@0 98 else {
michael@0 99 // This is a downgrade. Since we cannot predict future, upgrades should
michael@0 100 // be backwards compatible. We will set the version to the old value
michael@0 101 // regardless, so, on next upgrade, the migration steps will run again.
michael@0 102 // For this reason, they should also be able to run multiple times, even
michael@0 103 // on top of an already up-to-date storage.
michael@0 104 }
michael@0 105 } catch (ex) {
michael@0 106 // Something went wrong in the update process, we can't recover from here,
michael@0 107 // so just clear the storage and start from scratch (dataloss!).
michael@0 108 Components.utils.reportError(
michael@0 109 "Unable to migrate the newTab storage to the current version. "+
michael@0 110 "Restarting from scratch.\n" + ex);
michael@0 111 this.clear();
michael@0 112 }
michael@0 113
michael@0 114 // Set the version to the current one.
michael@0 115 this._storedVersion = this._version;
michael@0 116 }
michael@0 117
michael@0 118 LinksStorage.prototype = {
michael@0 119 get _version() 1,
michael@0 120
michael@0 121 get _prefs() Object.freeze({
michael@0 122 pinnedLinks: "browser.newtabpage.pinned",
michael@0 123 blockedLinks: "browser.newtabpage.blocked",
michael@0 124 }),
michael@0 125
michael@0 126 get _storedVersion() {
michael@0 127 if (this.__storedVersion === undefined) {
michael@0 128 try {
michael@0 129 this.__storedVersion =
michael@0 130 Services.prefs.getIntPref("browser.newtabpage.storageVersion");
michael@0 131 } catch (ex) {
michael@0 132 // The storage version is unknown, so either:
michael@0 133 // - it's a new profile
michael@0 134 // - it's a profile where versioning information got lost
michael@0 135 // In this case we still run through all of the valid migrations,
michael@0 136 // starting from 1, as if it was a downgrade. As previously stated the
michael@0 137 // migrations should already support running on an updated store.
michael@0 138 this.__storedVersion = 1;
michael@0 139 }
michael@0 140 }
michael@0 141 return this.__storedVersion;
michael@0 142 },
michael@0 143 set _storedVersion(aValue) {
michael@0 144 Services.prefs.setIntPref("browser.newtabpage.storageVersion", aValue);
michael@0 145 this.__storedVersion = aValue;
michael@0 146 return aValue;
michael@0 147 },
michael@0 148
michael@0 149 /**
michael@0 150 * Gets the value for a given key from the storage.
michael@0 151 * @param aKey The storage key (a string).
michael@0 152 * @param aDefault A default value if the key doesn't exist.
michael@0 153 * @return The value for the given key.
michael@0 154 */
michael@0 155 get: function Storage_get(aKey, aDefault) {
michael@0 156 let value;
michael@0 157 try {
michael@0 158 let prefValue = Services.prefs.getComplexValue(this._prefs[aKey],
michael@0 159 Ci.nsISupportsString).data;
michael@0 160 value = JSON.parse(prefValue);
michael@0 161 } catch (e) {}
michael@0 162 return value || aDefault;
michael@0 163 },
michael@0 164
michael@0 165 /**
michael@0 166 * Sets the storage value for a given key.
michael@0 167 * @param aKey The storage key (a string).
michael@0 168 * @param aValue The value to set.
michael@0 169 */
michael@0 170 set: function Storage_set(aKey, aValue) {
michael@0 171 // Page titles may contain unicode, thus use complex values.
michael@0 172 let string = Cc["@mozilla.org/supports-string;1"]
michael@0 173 .createInstance(Ci.nsISupportsString);
michael@0 174 string.data = JSON.stringify(aValue);
michael@0 175 Services.prefs.setComplexValue(this._prefs[aKey], Ci.nsISupportsString,
michael@0 176 string);
michael@0 177 },
michael@0 178
michael@0 179 /**
michael@0 180 * Removes the storage value for a given key.
michael@0 181 * @param aKey The storage key (a string).
michael@0 182 */
michael@0 183 remove: function Storage_remove(aKey) {
michael@0 184 Services.prefs.clearUserPref(this._prefs[aKey]);
michael@0 185 },
michael@0 186
michael@0 187 /**
michael@0 188 * Clears the storage and removes all values.
michael@0 189 */
michael@0 190 clear: function Storage_clear() {
michael@0 191 for (let key in this._prefs) {
michael@0 192 this.remove(key);
michael@0 193 }
michael@0 194 }
michael@0 195 };
michael@0 196
michael@0 197
michael@0 198 /**
michael@0 199 * Singleton that serves as a registry for all open 'New Tab Page's.
michael@0 200 */
michael@0 201 let AllPages = {
michael@0 202 /**
michael@0 203 * The array containing all active pages.
michael@0 204 */
michael@0 205 _pages: [],
michael@0 206
michael@0 207 /**
michael@0 208 * Cached value that tells whether the New Tab Page feature is enabled.
michael@0 209 */
michael@0 210 _enabled: null,
michael@0 211
michael@0 212 /**
michael@0 213 * Adds a page to the internal list of pages.
michael@0 214 * @param aPage The page to register.
michael@0 215 */
michael@0 216 register: function AllPages_register(aPage) {
michael@0 217 this._pages.push(aPage);
michael@0 218 this._addObserver();
michael@0 219 },
michael@0 220
michael@0 221 /**
michael@0 222 * Removes a page from the internal list of pages.
michael@0 223 * @param aPage The page to unregister.
michael@0 224 */
michael@0 225 unregister: function AllPages_unregister(aPage) {
michael@0 226 let index = this._pages.indexOf(aPage);
michael@0 227 if (index > -1)
michael@0 228 this._pages.splice(index, 1);
michael@0 229 },
michael@0 230
michael@0 231 /**
michael@0 232 * Returns whether the 'New Tab Page' is enabled.
michael@0 233 */
michael@0 234 get enabled() {
michael@0 235 if (this._enabled === null)
michael@0 236 this._enabled = Services.prefs.getBoolPref(PREF_NEWTAB_ENABLED);
michael@0 237
michael@0 238 return this._enabled;
michael@0 239 },
michael@0 240
michael@0 241 /**
michael@0 242 * Enables or disables the 'New Tab Page' feature.
michael@0 243 */
michael@0 244 set enabled(aEnabled) {
michael@0 245 if (this.enabled != aEnabled)
michael@0 246 Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, !!aEnabled);
michael@0 247 },
michael@0 248
michael@0 249 /**
michael@0 250 * Returns the number of registered New Tab Pages (i.e. the number of open
michael@0 251 * about:newtab instances).
michael@0 252 */
michael@0 253 get length() {
michael@0 254 return this._pages.length;
michael@0 255 },
michael@0 256
michael@0 257 /**
michael@0 258 * Updates all currently active pages but the given one.
michael@0 259 * @param aExceptPage The page to exclude from updating.
michael@0 260 * @param aHiddenPagesOnly If true, only pages hidden in the preloader are
michael@0 261 * updated.
michael@0 262 */
michael@0 263 update: function AllPages_update(aExceptPage, aHiddenPagesOnly=false) {
michael@0 264 this._pages.forEach(function (aPage) {
michael@0 265 if (aExceptPage != aPage)
michael@0 266 aPage.update(aHiddenPagesOnly);
michael@0 267 });
michael@0 268 },
michael@0 269
michael@0 270 /**
michael@0 271 * Many individual link changes may happen in a small amount of time over
michael@0 272 * multiple turns of the event loop. This method coalesces updates by waiting
michael@0 273 * a small amount of time before updating hidden pages.
michael@0 274 */
michael@0 275 scheduleUpdateForHiddenPages: function AllPages_scheduleUpdateForHiddenPages() {
michael@0 276 if (!this._scheduleUpdateTimeout) {
michael@0 277 this._scheduleUpdateTimeout = Timer.setTimeout(() => {
michael@0 278 delete this._scheduleUpdateTimeout;
michael@0 279 this.update(null, true);
michael@0 280 }, SCHEDULE_UPDATE_TIMEOUT_MS);
michael@0 281 }
michael@0 282 },
michael@0 283
michael@0 284 get updateScheduledForHiddenPages() {
michael@0 285 return !!this._scheduleUpdateTimeout;
michael@0 286 },
michael@0 287
michael@0 288 /**
michael@0 289 * Implements the nsIObserver interface to get notified when the preference
michael@0 290 * value changes or when a new copy of a page thumbnail is available.
michael@0 291 */
michael@0 292 observe: function AllPages_observe(aSubject, aTopic, aData) {
michael@0 293 if (aTopic == "nsPref:changed") {
michael@0 294 // Clear the cached value.
michael@0 295 this._enabled = null;
michael@0 296 }
michael@0 297 // and all notifications get forwarded to each page.
michael@0 298 this._pages.forEach(function (aPage) {
michael@0 299 aPage.observe(aSubject, aTopic, aData);
michael@0 300 }, this);
michael@0 301 },
michael@0 302
michael@0 303 /**
michael@0 304 * Adds a preference and new thumbnail observer and turns itself into a
michael@0 305 * no-op after the first invokation.
michael@0 306 */
michael@0 307 _addObserver: function AllPages_addObserver() {
michael@0 308 Services.prefs.addObserver(PREF_NEWTAB_ENABLED, this, true);
michael@0 309 Services.obs.addObserver(this, "page-thumbnail:create", true);
michael@0 310 this._addObserver = function () {};
michael@0 311 },
michael@0 312
michael@0 313 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
michael@0 314 Ci.nsISupportsWeakReference])
michael@0 315 };
michael@0 316
michael@0 317 /**
michael@0 318 * Singleton that keeps Grid preferences
michael@0 319 */
michael@0 320 let GridPrefs = {
michael@0 321 /**
michael@0 322 * Cached value that tells the number of rows of newtab grid.
michael@0 323 */
michael@0 324 _gridRows: null,
michael@0 325 get gridRows() {
michael@0 326 if (!this._gridRows) {
michael@0 327 this._gridRows = Math.max(1, Services.prefs.getIntPref(PREF_NEWTAB_ROWS));
michael@0 328 }
michael@0 329
michael@0 330 return this._gridRows;
michael@0 331 },
michael@0 332
michael@0 333 /**
michael@0 334 * Cached value that tells the number of columns of newtab grid.
michael@0 335 */
michael@0 336 _gridColumns: null,
michael@0 337 get gridColumns() {
michael@0 338 if (!this._gridColumns) {
michael@0 339 this._gridColumns = Math.max(1, Services.prefs.getIntPref(PREF_NEWTAB_COLUMNS));
michael@0 340 }
michael@0 341
michael@0 342 return this._gridColumns;
michael@0 343 },
michael@0 344
michael@0 345
michael@0 346 /**
michael@0 347 * Initializes object. Adds a preference observer
michael@0 348 */
michael@0 349 init: function GridPrefs_init() {
michael@0 350 Services.prefs.addObserver(PREF_NEWTAB_ROWS, this, false);
michael@0 351 Services.prefs.addObserver(PREF_NEWTAB_COLUMNS, this, false);
michael@0 352 },
michael@0 353
michael@0 354 /**
michael@0 355 * Implements the nsIObserver interface to get notified when the preference
michael@0 356 * value changes.
michael@0 357 */
michael@0 358 observe: function GridPrefs_observe(aSubject, aTopic, aData) {
michael@0 359 if (aData == PREF_NEWTAB_ROWS) {
michael@0 360 this._gridRows = null;
michael@0 361 } else {
michael@0 362 this._gridColumns = null;
michael@0 363 }
michael@0 364
michael@0 365 AllPages.update();
michael@0 366 }
michael@0 367 };
michael@0 368
michael@0 369 GridPrefs.init();
michael@0 370
michael@0 371 /**
michael@0 372 * Singleton that keeps track of all pinned links and their positions in the
michael@0 373 * grid.
michael@0 374 */
michael@0 375 let PinnedLinks = {
michael@0 376 /**
michael@0 377 * The cached list of pinned links.
michael@0 378 */
michael@0 379 _links: null,
michael@0 380
michael@0 381 /**
michael@0 382 * The array of pinned links.
michael@0 383 */
michael@0 384 get links() {
michael@0 385 if (!this._links)
michael@0 386 this._links = Storage.get("pinnedLinks", []);
michael@0 387
michael@0 388 return this._links;
michael@0 389 },
michael@0 390
michael@0 391 /**
michael@0 392 * Pins a link at the given position.
michael@0 393 * @param aLink The link to pin.
michael@0 394 * @param aIndex The grid index to pin the cell at.
michael@0 395 */
michael@0 396 pin: function PinnedLinks_pin(aLink, aIndex) {
michael@0 397 // Clear the link's old position, if any.
michael@0 398 this.unpin(aLink);
michael@0 399
michael@0 400 this.links[aIndex] = aLink;
michael@0 401 this.save();
michael@0 402 },
michael@0 403
michael@0 404 /**
michael@0 405 * Unpins a given link.
michael@0 406 * @param aLink The link to unpin.
michael@0 407 */
michael@0 408 unpin: function PinnedLinks_unpin(aLink) {
michael@0 409 let index = this._indexOfLink(aLink);
michael@0 410 if (index == -1)
michael@0 411 return;
michael@0 412 let links = this.links;
michael@0 413 links[index] = null;
michael@0 414 // trim trailing nulls
michael@0 415 let i=links.length-1;
michael@0 416 while (i >= 0 && links[i] == null)
michael@0 417 i--;
michael@0 418 links.splice(i +1);
michael@0 419 this.save();
michael@0 420 },
michael@0 421
michael@0 422 /**
michael@0 423 * Saves the current list of pinned links.
michael@0 424 */
michael@0 425 save: function PinnedLinks_save() {
michael@0 426 Storage.set("pinnedLinks", this.links);
michael@0 427 },
michael@0 428
michael@0 429 /**
michael@0 430 * Checks whether a given link is pinned.
michael@0 431 * @params aLink The link to check.
michael@0 432 * @return whether The link is pinned.
michael@0 433 */
michael@0 434 isPinned: function PinnedLinks_isPinned(aLink) {
michael@0 435 return this._indexOfLink(aLink) != -1;
michael@0 436 },
michael@0 437
michael@0 438 /**
michael@0 439 * Resets the links cache.
michael@0 440 */
michael@0 441 resetCache: function PinnedLinks_resetCache() {
michael@0 442 this._links = null;
michael@0 443 },
michael@0 444
michael@0 445 /**
michael@0 446 * Finds the index of a given link in the list of pinned links.
michael@0 447 * @param aLink The link to find an index for.
michael@0 448 * @return The link's index.
michael@0 449 */
michael@0 450 _indexOfLink: function PinnedLinks_indexOfLink(aLink) {
michael@0 451 for (let i = 0; i < this.links.length; i++) {
michael@0 452 let link = this.links[i];
michael@0 453 if (link && link.url == aLink.url)
michael@0 454 return i;
michael@0 455 }
michael@0 456
michael@0 457 // The given link is unpinned.
michael@0 458 return -1;
michael@0 459 }
michael@0 460 };
michael@0 461
michael@0 462 /**
michael@0 463 * Singleton that keeps track of all blocked links in the grid.
michael@0 464 */
michael@0 465 let BlockedLinks = {
michael@0 466 /**
michael@0 467 * The cached list of blocked links.
michael@0 468 */
michael@0 469 _links: null,
michael@0 470
michael@0 471 /**
michael@0 472 * The list of blocked links.
michael@0 473 */
michael@0 474 get links() {
michael@0 475 if (!this._links)
michael@0 476 this._links = Storage.get("blockedLinks", {});
michael@0 477
michael@0 478 return this._links;
michael@0 479 },
michael@0 480
michael@0 481 /**
michael@0 482 * Blocks a given link.
michael@0 483 * @param aLink The link to block.
michael@0 484 */
michael@0 485 block: function BlockedLinks_block(aLink) {
michael@0 486 this.links[toHash(aLink.url)] = 1;
michael@0 487 this.save();
michael@0 488
michael@0 489 // Make sure we unpin blocked links.
michael@0 490 PinnedLinks.unpin(aLink);
michael@0 491 },
michael@0 492
michael@0 493 /**
michael@0 494 * Unblocks a given link.
michael@0 495 * @param aLink The link to unblock.
michael@0 496 */
michael@0 497 unblock: function BlockedLinks_unblock(aLink) {
michael@0 498 if (this.isBlocked(aLink)) {
michael@0 499 delete this.links[toHash(aLink.url)];
michael@0 500 this.save();
michael@0 501 }
michael@0 502 },
michael@0 503
michael@0 504 /**
michael@0 505 * Saves the current list of blocked links.
michael@0 506 */
michael@0 507 save: function BlockedLinks_save() {
michael@0 508 Storage.set("blockedLinks", this.links);
michael@0 509 },
michael@0 510
michael@0 511 /**
michael@0 512 * Returns whether a given link is blocked.
michael@0 513 * @param aLink The link to check.
michael@0 514 */
michael@0 515 isBlocked: function BlockedLinks_isBlocked(aLink) {
michael@0 516 return (toHash(aLink.url) in this.links);
michael@0 517 },
michael@0 518
michael@0 519 /**
michael@0 520 * Checks whether the list of blocked links is empty.
michael@0 521 * @return Whether the list is empty.
michael@0 522 */
michael@0 523 isEmpty: function BlockedLinks_isEmpty() {
michael@0 524 return Object.keys(this.links).length == 0;
michael@0 525 },
michael@0 526
michael@0 527 /**
michael@0 528 * Resets the links cache.
michael@0 529 */
michael@0 530 resetCache: function BlockedLinks_resetCache() {
michael@0 531 this._links = null;
michael@0 532 }
michael@0 533 };
michael@0 534
michael@0 535 /**
michael@0 536 * Singleton that serves as the default link provider for the grid. It queries
michael@0 537 * the history to retrieve the most frequently visited sites.
michael@0 538 */
michael@0 539 let PlacesProvider = {
michael@0 540 /**
michael@0 541 * Set this to change the maximum number of links the provider will provide.
michael@0 542 */
michael@0 543 maxNumLinks: HISTORY_RESULTS_LIMIT,
michael@0 544
michael@0 545 /**
michael@0 546 * Must be called before the provider is used.
michael@0 547 */
michael@0 548 init: function PlacesProvider_init() {
michael@0 549 PlacesUtils.history.addObserver(this, true);
michael@0 550 },
michael@0 551
michael@0 552 /**
michael@0 553 * Gets the current set of links delivered by this provider.
michael@0 554 * @param aCallback The function that the array of links is passed to.
michael@0 555 */
michael@0 556 getLinks: function PlacesProvider_getLinks(aCallback) {
michael@0 557 let options = PlacesUtils.history.getNewQueryOptions();
michael@0 558 options.maxResults = this.maxNumLinks;
michael@0 559
michael@0 560 // Sort by frecency, descending.
michael@0 561 options.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_FRECENCY_DESCENDING
michael@0 562
michael@0 563 let links = [];
michael@0 564
michael@0 565 let callback = {
michael@0 566 handleResult: function (aResultSet) {
michael@0 567 let row;
michael@0 568
michael@0 569 while ((row = aResultSet.getNextRow())) {
michael@0 570 let url = row.getResultByIndex(1);
michael@0 571 if (LinkChecker.checkLoadURI(url)) {
michael@0 572 let title = row.getResultByIndex(2);
michael@0 573 let frecency = row.getResultByIndex(12);
michael@0 574 let lastVisitDate = row.getResultByIndex(5);
michael@0 575 links.push({
michael@0 576 url: url,
michael@0 577 title: title,
michael@0 578 frecency: frecency,
michael@0 579 lastVisitDate: lastVisitDate,
michael@0 580 bgColor: "transparent",
michael@0 581 type: "history",
michael@0 582 imageURI: null,
michael@0 583 });
michael@0 584 }
michael@0 585 }
michael@0 586 },
michael@0 587
michael@0 588 handleError: function (aError) {
michael@0 589 // Should we somehow handle this error?
michael@0 590 aCallback([]);
michael@0 591 },
michael@0 592
michael@0 593 handleCompletion: function (aReason) {
michael@0 594 // The Places query breaks ties in frecency by place ID descending, but
michael@0 595 // that's different from how Links.compareLinks breaks ties, because
michael@0 596 // compareLinks doesn't have access to place IDs. It's very important
michael@0 597 // that the initial list of links is sorted in the same order imposed by
michael@0 598 // compareLinks, because Links uses compareLinks to perform binary
michael@0 599 // searches on the list. So, ensure the list is so ordered.
michael@0 600 let i = 1;
michael@0 601 let outOfOrder = [];
michael@0 602 while (i < links.length) {
michael@0 603 if (Links.compareLinks(links[i - 1], links[i]) > 0)
michael@0 604 outOfOrder.push(links.splice(i, 1)[0]);
michael@0 605 else
michael@0 606 i++;
michael@0 607 }
michael@0 608 for (let link of outOfOrder) {
michael@0 609 i = BinarySearch.insertionIndexOf(links, link,
michael@0 610 Links.compareLinks.bind(Links));
michael@0 611 links.splice(i, 0, link);
michael@0 612 }
michael@0 613
michael@0 614 aCallback(links);
michael@0 615 }
michael@0 616 };
michael@0 617
michael@0 618 // Execute the query.
michael@0 619 let query = PlacesUtils.history.getNewQuery();
michael@0 620 let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase);
michael@0 621 db.asyncExecuteLegacyQueries([query], 1, options, callback);
michael@0 622 },
michael@0 623
michael@0 624 /**
michael@0 625 * Registers an object that will be notified when the provider's links change.
michael@0 626 * @param aObserver An object with the following optional properties:
michael@0 627 * * onLinkChanged: A function that's called when a single link
michael@0 628 * changes. It's passed the provider and the link object. Only the
michael@0 629 * link's `url` property is guaranteed to be present. If its `title`
michael@0 630 * property is present, then its title has changed, and the
michael@0 631 * property's value is the new title. If any sort properties are
michael@0 632 * present, then its position within the provider's list of links may
michael@0 633 * have changed, and the properties' values are the new sort-related
michael@0 634 * values. Note that this link may not necessarily have been present
michael@0 635 * in the lists returned from any previous calls to getLinks.
michael@0 636 * * onManyLinksChanged: A function that's called when many links
michael@0 637 * change at once. It's passed the provider. You should call
michael@0 638 * getLinks to get the provider's new list of links.
michael@0 639 */
michael@0 640 addObserver: function PlacesProvider_addObserver(aObserver) {
michael@0 641 this._observers.push(aObserver);
michael@0 642 },
michael@0 643
michael@0 644 _observers: [],
michael@0 645
michael@0 646 /**
michael@0 647 * Called by the history service.
michael@0 648 */
michael@0 649 onFrecencyChanged: function PlacesProvider_onFrecencyChanged(aURI, aNewFrecency, aGUID, aHidden, aLastVisitDate) {
michael@0 650 // The implementation of the query in getLinks excludes hidden and
michael@0 651 // unvisited pages, so it's important to exclude them here, too.
michael@0 652 if (!aHidden && aLastVisitDate) {
michael@0 653 this._callObservers("onLinkChanged", {
michael@0 654 url: aURI.spec,
michael@0 655 frecency: aNewFrecency,
michael@0 656 lastVisitDate: aLastVisitDate,
michael@0 657 });
michael@0 658 }
michael@0 659 },
michael@0 660
michael@0 661 /**
michael@0 662 * Called by the history service.
michael@0 663 */
michael@0 664 onManyFrecenciesChanged: function PlacesProvider_onManyFrecenciesChanged() {
michael@0 665 this._callObservers("onManyLinksChanged");
michael@0 666 },
michael@0 667
michael@0 668 /**
michael@0 669 * Called by the history service.
michael@0 670 */
michael@0 671 onTitleChanged: function PlacesProvider_onTitleChanged(aURI, aNewTitle, aGUID) {
michael@0 672 this._callObservers("onLinkChanged", {
michael@0 673 url: aURI.spec,
michael@0 674 title: aNewTitle
michael@0 675 });
michael@0 676 },
michael@0 677
michael@0 678 _callObservers: function PlacesProvider__callObservers(aMethodName, aArg) {
michael@0 679 for (let obs of this._observers) {
michael@0 680 if (obs[aMethodName]) {
michael@0 681 try {
michael@0 682 obs[aMethodName](this, aArg);
michael@0 683 } catch (err) {
michael@0 684 Cu.reportError(err);
michael@0 685 }
michael@0 686 }
michael@0 687 }
michael@0 688 },
michael@0 689
michael@0 690 QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver,
michael@0 691 Ci.nsISupportsWeakReference]),
michael@0 692 };
michael@0 693
michael@0 694 /**
michael@0 695 * Singleton that provides access to all links contained in the grid (including
michael@0 696 * the ones that don't fit on the grid). A link is a plain object that looks
michael@0 697 * like this:
michael@0 698 *
michael@0 699 * {
michael@0 700 * url: "http://www.mozilla.org/",
michael@0 701 * title: "Mozilla",
michael@0 702 * frecency: 1337,
michael@0 703 * lastVisitDate: 1394678824766431,
michael@0 704 * }
michael@0 705 */
michael@0 706 let Links = {
michael@0 707 /**
michael@0 708 * The maximum number of links returned by getLinks.
michael@0 709 */
michael@0 710 maxNumLinks: LINKS_GET_LINKS_LIMIT,
michael@0 711
michael@0 712 /**
michael@0 713 * The link providers.
michael@0 714 */
michael@0 715 _providers: new Set(),
michael@0 716
michael@0 717 /**
michael@0 718 * A mapping from each provider to an object { sortedLinks, linkMap }.
michael@0 719 * sortedLinks is the cached, sorted array of links for the provider. linkMap
michael@0 720 * is a Map from link URLs to link objects.
michael@0 721 */
michael@0 722 _providerLinks: new Map(),
michael@0 723
michael@0 724 /**
michael@0 725 * The properties of link objects used to sort them.
michael@0 726 */
michael@0 727 _sortProperties: [
michael@0 728 "frecency",
michael@0 729 "lastVisitDate",
michael@0 730 "url",
michael@0 731 ],
michael@0 732
michael@0 733 /**
michael@0 734 * List of callbacks waiting for the cache to be populated.
michael@0 735 */
michael@0 736 _populateCallbacks: [],
michael@0 737
michael@0 738 /**
michael@0 739 * Adds a link provider.
michael@0 740 * @param aProvider The link provider.
michael@0 741 */
michael@0 742 addProvider: function Links_addProvider(aProvider) {
michael@0 743 this._providers.add(aProvider);
michael@0 744 aProvider.addObserver(this);
michael@0 745 },
michael@0 746
michael@0 747 /**
michael@0 748 * Removes a link provider.
michael@0 749 * @param aProvider The link provider.
michael@0 750 */
michael@0 751 removeProvider: function Links_removeProvider(aProvider) {
michael@0 752 if (!this._providers.delete(aProvider))
michael@0 753 throw new Error("Unknown provider");
michael@0 754 this._providerLinks.delete(aProvider);
michael@0 755 },
michael@0 756
michael@0 757 /**
michael@0 758 * Populates the cache with fresh links from the providers.
michael@0 759 * @param aCallback The callback to call when finished (optional).
michael@0 760 * @param aForce When true, populates the cache even when it's already filled.
michael@0 761 */
michael@0 762 populateCache: function Links_populateCache(aCallback, aForce) {
michael@0 763 let callbacks = this._populateCallbacks;
michael@0 764
michael@0 765 // Enqueue the current callback.
michael@0 766 callbacks.push(aCallback);
michael@0 767
michael@0 768 // There was a callback waiting already, thus the cache has not yet been
michael@0 769 // populated.
michael@0 770 if (callbacks.length > 1)
michael@0 771 return;
michael@0 772
michael@0 773 function executeCallbacks() {
michael@0 774 while (callbacks.length) {
michael@0 775 let callback = callbacks.shift();
michael@0 776 if (callback) {
michael@0 777 try {
michael@0 778 callback();
michael@0 779 } catch (e) {
michael@0 780 // We want to proceed even if a callback fails.
michael@0 781 }
michael@0 782 }
michael@0 783 }
michael@0 784 }
michael@0 785
michael@0 786 let numProvidersRemaining = this._providers.size;
michael@0 787 for (let provider of this._providers) {
michael@0 788 this._populateProviderCache(provider, () => {
michael@0 789 if (--numProvidersRemaining == 0)
michael@0 790 executeCallbacks();
michael@0 791 }, aForce);
michael@0 792 }
michael@0 793
michael@0 794 this._addObserver();
michael@0 795 },
michael@0 796
michael@0 797 /**
michael@0 798 * Gets the current set of links contained in the grid.
michael@0 799 * @return The links in the grid.
michael@0 800 */
michael@0 801 getLinks: function Links_getLinks() {
michael@0 802 let pinnedLinks = Array.slice(PinnedLinks.links);
michael@0 803 let links = this._getMergedProviderLinks();
michael@0 804
michael@0 805 // Filter blocked and pinned links.
michael@0 806 links = links.filter(function (link) {
michael@0 807 return !BlockedLinks.isBlocked(link) && !PinnedLinks.isPinned(link);
michael@0 808 });
michael@0 809
michael@0 810 // Try to fill the gaps between pinned links.
michael@0 811 for (let i = 0; i < pinnedLinks.length && links.length; i++)
michael@0 812 if (!pinnedLinks[i])
michael@0 813 pinnedLinks[i] = links.shift();
michael@0 814
michael@0 815 // Append the remaining links if any.
michael@0 816 if (links.length)
michael@0 817 pinnedLinks = pinnedLinks.concat(links);
michael@0 818
michael@0 819 return pinnedLinks;
michael@0 820 },
michael@0 821
michael@0 822 /**
michael@0 823 * Resets the links cache.
michael@0 824 */
michael@0 825 resetCache: function Links_resetCache() {
michael@0 826 this._providerLinks.clear();
michael@0 827 },
michael@0 828
michael@0 829 /**
michael@0 830 * Compares two links.
michael@0 831 * @param aLink1 The first link.
michael@0 832 * @param aLink2 The second link.
michael@0 833 * @return A negative number if aLink1 is ordered before aLink2, zero if
michael@0 834 * aLink1 and aLink2 have the same ordering, or a positive number if
michael@0 835 * aLink1 is ordered after aLink2.
michael@0 836 */
michael@0 837 compareLinks: function Links_compareLinks(aLink1, aLink2) {
michael@0 838 for (let prop of this._sortProperties) {
michael@0 839 if (!(prop in aLink1) || !(prop in aLink2))
michael@0 840 throw new Error("Comparable link missing required property: " + prop);
michael@0 841 }
michael@0 842 return aLink2.frecency - aLink1.frecency ||
michael@0 843 aLink2.lastVisitDate - aLink1.lastVisitDate ||
michael@0 844 aLink1.url.localeCompare(aLink2.url);
michael@0 845 },
michael@0 846
michael@0 847 /**
michael@0 848 * Calls getLinks on the given provider and populates our cache for it.
michael@0 849 * @param aProvider The provider whose cache will be populated.
michael@0 850 * @param aCallback The callback to call when finished.
michael@0 851 * @param aForce When true, populates the provider's cache even when it's
michael@0 852 * already filled.
michael@0 853 */
michael@0 854 _populateProviderCache: function Links_populateProviderCache(aProvider, aCallback, aForce) {
michael@0 855 if (this._providerLinks.has(aProvider) && !aForce) {
michael@0 856 aCallback();
michael@0 857 } else {
michael@0 858 aProvider.getLinks(links => {
michael@0 859 // Filter out null and undefined links so we don't have to deal with
michael@0 860 // them in getLinks when merging links from providers.
michael@0 861 links = links.filter((link) => !!link);
michael@0 862 this._providerLinks.set(aProvider, {
michael@0 863 sortedLinks: links,
michael@0 864 linkMap: links.reduce((map, link) => {
michael@0 865 map.set(link.url, link);
michael@0 866 return map;
michael@0 867 }, new Map()),
michael@0 868 });
michael@0 869 aCallback();
michael@0 870 });
michael@0 871 }
michael@0 872 },
michael@0 873
michael@0 874 /**
michael@0 875 * Merges the cached lists of links from all providers whose lists are cached.
michael@0 876 * @return The merged list.
michael@0 877 */
michael@0 878 _getMergedProviderLinks: function Links__getMergedProviderLinks() {
michael@0 879 // Build a list containing a copy of each provider's sortedLinks list.
michael@0 880 let linkLists = [];
michael@0 881 for (let links of this._providerLinks.values()) {
michael@0 882 linkLists.push(links.sortedLinks.slice());
michael@0 883 }
michael@0 884
michael@0 885 function getNextLink() {
michael@0 886 let minLinks = null;
michael@0 887 for (let links of linkLists) {
michael@0 888 if (links.length &&
michael@0 889 (!minLinks || Links.compareLinks(links[0], minLinks[0]) < 0))
michael@0 890 minLinks = links;
michael@0 891 }
michael@0 892 return minLinks ? minLinks.shift() : null;
michael@0 893 }
michael@0 894
michael@0 895 let finalLinks = [];
michael@0 896 for (let nextLink = getNextLink();
michael@0 897 nextLink && finalLinks.length < this.maxNumLinks;
michael@0 898 nextLink = getNextLink()) {
michael@0 899 finalLinks.push(nextLink);
michael@0 900 }
michael@0 901
michael@0 902 return finalLinks;
michael@0 903 },
michael@0 904
michael@0 905 /**
michael@0 906 * Called by a provider to notify us when a single link changes.
michael@0 907 * @param aProvider The provider whose link changed.
michael@0 908 * @param aLink The link that changed. If the link is new, it must have all
michael@0 909 * of the _sortProperties. Otherwise, it may have as few or as
michael@0 910 * many as is convenient.
michael@0 911 */
michael@0 912 onLinkChanged: function Links_onLinkChanged(aProvider, aLink) {
michael@0 913 if (!("url" in aLink))
michael@0 914 throw new Error("Changed links must have a url property");
michael@0 915
michael@0 916 let links = this._providerLinks.get(aProvider);
michael@0 917 if (!links)
michael@0 918 // This is not an error, it just means that between the time the provider
michael@0 919 // was added and the future time we call getLinks on it, it notified us of
michael@0 920 // a change.
michael@0 921 return;
michael@0 922
michael@0 923 let { sortedLinks, linkMap } = links;
michael@0 924 let existingLink = linkMap.get(aLink.url);
michael@0 925 let insertionLink = null;
michael@0 926 let updatePages = false;
michael@0 927
michael@0 928 if (existingLink) {
michael@0 929 // Update our copy's position in O(lg n) by first removing it from its
michael@0 930 // list. It's important to do this before modifying its properties.
michael@0 931 if (this._sortProperties.some(prop => prop in aLink)) {
michael@0 932 let idx = this._indexOf(sortedLinks, existingLink);
michael@0 933 if (idx < 0) {
michael@0 934 throw new Error("Link should be in _sortedLinks if in _linkMap");
michael@0 935 }
michael@0 936 sortedLinks.splice(idx, 1);
michael@0 937 // Update our copy's properties.
michael@0 938 for (let prop of this._sortProperties) {
michael@0 939 if (prop in aLink) {
michael@0 940 existingLink[prop] = aLink[prop];
michael@0 941 }
michael@0 942 }
michael@0 943 // Finally, reinsert our copy below.
michael@0 944 insertionLink = existingLink;
michael@0 945 }
michael@0 946 // Update our copy's title in O(1).
michael@0 947 if ("title" in aLink && aLink.title != existingLink.title) {
michael@0 948 existingLink.title = aLink.title;
michael@0 949 updatePages = true;
michael@0 950 }
michael@0 951 }
michael@0 952 else if (this._sortProperties.every(prop => prop in aLink)) {
michael@0 953 // Before doing the O(lg n) insertion below, do an O(1) check for the
michael@0 954 // common case where the new link is too low-ranked to be in the list.
michael@0 955 if (sortedLinks.length && sortedLinks.length == aProvider.maxNumLinks) {
michael@0 956 let lastLink = sortedLinks[sortedLinks.length - 1];
michael@0 957 if (this.compareLinks(lastLink, aLink) < 0) {
michael@0 958 return;
michael@0 959 }
michael@0 960 }
michael@0 961 // Copy the link object so that changes later made to it by the caller
michael@0 962 // don't affect our copy.
michael@0 963 insertionLink = {};
michael@0 964 for (let prop in aLink) {
michael@0 965 insertionLink[prop] = aLink[prop];
michael@0 966 }
michael@0 967 linkMap.set(aLink.url, insertionLink);
michael@0 968 }
michael@0 969
michael@0 970 if (insertionLink) {
michael@0 971 let idx = this._insertionIndexOf(sortedLinks, insertionLink);
michael@0 972 sortedLinks.splice(idx, 0, insertionLink);
michael@0 973 if (sortedLinks.length > aProvider.maxNumLinks) {
michael@0 974 let lastLink = sortedLinks.pop();
michael@0 975 linkMap.delete(lastLink.url);
michael@0 976 }
michael@0 977 updatePages = true;
michael@0 978 }
michael@0 979
michael@0 980 if (updatePages)
michael@0 981 AllPages.scheduleUpdateForHiddenPages();
michael@0 982 },
michael@0 983
michael@0 984 /**
michael@0 985 * Called by a provider to notify us when many links change.
michael@0 986 */
michael@0 987 onManyLinksChanged: function Links_onManyLinksChanged(aProvider) {
michael@0 988 this._populateProviderCache(aProvider, () => {
michael@0 989 AllPages.scheduleUpdateForHiddenPages();
michael@0 990 }, true);
michael@0 991 },
michael@0 992
michael@0 993 _indexOf: function Links__indexOf(aArray, aLink) {
michael@0 994 return this._binsearch(aArray, aLink, "indexOf");
michael@0 995 },
michael@0 996
michael@0 997 _insertionIndexOf: function Links__insertionIndexOf(aArray, aLink) {
michael@0 998 return this._binsearch(aArray, aLink, "insertionIndexOf");
michael@0 999 },
michael@0 1000
michael@0 1001 _binsearch: function Links__binsearch(aArray, aLink, aMethod) {
michael@0 1002 return BinarySearch[aMethod](aArray, aLink, this.compareLinks.bind(this));
michael@0 1003 },
michael@0 1004
michael@0 1005 /**
michael@0 1006 * Implements the nsIObserver interface to get notified about browser history
michael@0 1007 * sanitization.
michael@0 1008 */
michael@0 1009 observe: function Links_observe(aSubject, aTopic, aData) {
michael@0 1010 // Make sure to update open about:newtab instances. If there are no opened
michael@0 1011 // pages we can just wait for the next new tab to populate the cache again.
michael@0 1012 if (AllPages.length && AllPages.enabled)
michael@0 1013 this.populateCache(function () { AllPages.update() }, true);
michael@0 1014 else
michael@0 1015 this.resetCache();
michael@0 1016 },
michael@0 1017
michael@0 1018 /**
michael@0 1019 * Adds a sanitization observer and turns itself into a no-op after the first
michael@0 1020 * invokation.
michael@0 1021 */
michael@0 1022 _addObserver: function Links_addObserver() {
michael@0 1023 Services.obs.addObserver(this, "browser:purge-session-history", true);
michael@0 1024 this._addObserver = function () {};
michael@0 1025 },
michael@0 1026
michael@0 1027 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
michael@0 1028 Ci.nsISupportsWeakReference])
michael@0 1029 };
michael@0 1030
michael@0 1031 /**
michael@0 1032 * Singleton used to collect telemetry data.
michael@0 1033 *
michael@0 1034 */
michael@0 1035 let Telemetry = {
michael@0 1036 /**
michael@0 1037 * Initializes object.
michael@0 1038 */
michael@0 1039 init: function Telemetry_init() {
michael@0 1040 Services.obs.addObserver(this, TOPIC_GATHER_TELEMETRY, false);
michael@0 1041 },
michael@0 1042
michael@0 1043 /**
michael@0 1044 * Collects data.
michael@0 1045 */
michael@0 1046 _collect: function Telemetry_collect() {
michael@0 1047 let probes = [
michael@0 1048 { histogram: "NEWTAB_PAGE_ENABLED",
michael@0 1049 value: AllPages.enabled },
michael@0 1050 { histogram: "NEWTAB_PAGE_PINNED_SITES_COUNT",
michael@0 1051 value: PinnedLinks.links.length },
michael@0 1052 { histogram: "NEWTAB_PAGE_BLOCKED_SITES_COUNT",
michael@0 1053 value: Object.keys(BlockedLinks.links).length }
michael@0 1054 ];
michael@0 1055
michael@0 1056 probes.forEach(function Telemetry_collect_forEach(aProbe) {
michael@0 1057 Services.telemetry.getHistogramById(aProbe.histogram)
michael@0 1058 .add(aProbe.value);
michael@0 1059 });
michael@0 1060 },
michael@0 1061
michael@0 1062 /**
michael@0 1063 * Listens for gather telemetry topic.
michael@0 1064 */
michael@0 1065 observe: function Telemetry_observe(aSubject, aTopic, aData) {
michael@0 1066 this._collect();
michael@0 1067 }
michael@0 1068 };
michael@0 1069
michael@0 1070 /**
michael@0 1071 * Singleton that checks if a given link should be displayed on about:newtab
michael@0 1072 * or if we should rather not do it for security reasons. URIs that inherit
michael@0 1073 * their caller's principal will be filtered.
michael@0 1074 */
michael@0 1075 let LinkChecker = {
michael@0 1076 _cache: {},
michael@0 1077
michael@0 1078 get flags() {
michael@0 1079 return Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL |
michael@0 1080 Ci.nsIScriptSecurityManager.DONT_REPORT_ERRORS;
michael@0 1081 },
michael@0 1082
michael@0 1083 checkLoadURI: function LinkChecker_checkLoadURI(aURI) {
michael@0 1084 if (!(aURI in this._cache))
michael@0 1085 this._cache[aURI] = this._doCheckLoadURI(aURI);
michael@0 1086
michael@0 1087 return this._cache[aURI];
michael@0 1088 },
michael@0 1089
michael@0 1090 _doCheckLoadURI: function Links_doCheckLoadURI(aURI) {
michael@0 1091 try {
michael@0 1092 Services.scriptSecurityManager.
michael@0 1093 checkLoadURIStrWithPrincipal(gPrincipal, aURI, this.flags);
michael@0 1094 return true;
michael@0 1095 } catch (e) {
michael@0 1096 // We got a weird URI or one that would inherit the caller's principal.
michael@0 1097 return false;
michael@0 1098 }
michael@0 1099 }
michael@0 1100 };
michael@0 1101
michael@0 1102 let ExpirationFilter = {
michael@0 1103 init: function ExpirationFilter_init() {
michael@0 1104 PageThumbs.addExpirationFilter(this);
michael@0 1105 },
michael@0 1106
michael@0 1107 filterForThumbnailExpiration:
michael@0 1108 function ExpirationFilter_filterForThumbnailExpiration(aCallback) {
michael@0 1109 if (!AllPages.enabled) {
michael@0 1110 aCallback([]);
michael@0 1111 return;
michael@0 1112 }
michael@0 1113
michael@0 1114 Links.populateCache(function () {
michael@0 1115 let urls = [];
michael@0 1116
michael@0 1117 // Add all URLs to the list that we want to keep thumbnails for.
michael@0 1118 for (let link of Links.getLinks().slice(0, 25)) {
michael@0 1119 if (link && link.url)
michael@0 1120 urls.push(link.url);
michael@0 1121 }
michael@0 1122
michael@0 1123 aCallback(urls);
michael@0 1124 });
michael@0 1125 }
michael@0 1126 };
michael@0 1127
michael@0 1128 /**
michael@0 1129 * Singleton that provides the public API of this JSM.
michael@0 1130 */
michael@0 1131 this.NewTabUtils = {
michael@0 1132 _initialized: false,
michael@0 1133
michael@0 1134 init: function NewTabUtils_init() {
michael@0 1135 if (this.initWithoutProviders()) {
michael@0 1136 PlacesProvider.init();
michael@0 1137 Links.addProvider(PlacesProvider);
michael@0 1138 }
michael@0 1139 },
michael@0 1140
michael@0 1141 initWithoutProviders: function NewTabUtils_initWithoutProviders() {
michael@0 1142 if (!this._initialized) {
michael@0 1143 this._initialized = true;
michael@0 1144 ExpirationFilter.init();
michael@0 1145 Telemetry.init();
michael@0 1146 return true;
michael@0 1147 }
michael@0 1148 return false;
michael@0 1149 },
michael@0 1150
michael@0 1151 /**
michael@0 1152 * Restores all sites that have been removed from the grid.
michael@0 1153 */
michael@0 1154 restore: function NewTabUtils_restore() {
michael@0 1155 Storage.clear();
michael@0 1156 Links.resetCache();
michael@0 1157 PinnedLinks.resetCache();
michael@0 1158 BlockedLinks.resetCache();
michael@0 1159
michael@0 1160 Links.populateCache(function () {
michael@0 1161 AllPages.update();
michael@0 1162 }, true);
michael@0 1163 },
michael@0 1164
michael@0 1165 /**
michael@0 1166 * Undoes all sites that have been removed from the grid and keep the pinned
michael@0 1167 * tabs.
michael@0 1168 * @param aCallback the callback method.
michael@0 1169 */
michael@0 1170 undoAll: function NewTabUtils_undoAll(aCallback) {
michael@0 1171 Storage.remove("blockedLinks");
michael@0 1172 Links.resetCache();
michael@0 1173 BlockedLinks.resetCache();
michael@0 1174 Links.populateCache(aCallback, true);
michael@0 1175 },
michael@0 1176
michael@0 1177 links: Links,
michael@0 1178 allPages: AllPages,
michael@0 1179 linkChecker: LinkChecker,
michael@0 1180 pinnedLinks: PinnedLinks,
michael@0 1181 blockedLinks: BlockedLinks,
michael@0 1182 gridPrefs: GridPrefs
michael@0 1183 };

mercurial