1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/places/nsPlacesExpiration.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,996 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * vim: sw=2 ts=2 sts=2 expandtab 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/** 1.11 + * This component handles history and orphans expiration through asynchronous 1.12 + * Storage statements. 1.13 + * Expiration runs: 1.14 + * - At idle, but just once, we stop any other kind of expiration during idle 1.15 + * to preserve batteries in portable devices. 1.16 + * - At shutdown, only if the database is dirty, we should still avoid to 1.17 + * expire too heavily on shutdown. 1.18 + * - On ClearHistory we run a full expiration for privacy reasons. 1.19 + * - On a repeating timer we expire in small chunks. 1.20 + * 1.21 + * Expiration algorithm will adapt itself based on: 1.22 + * - Memory size of the device. 1.23 + * - Status of the database (clean or dirty). 1.24 + */ 1.25 + 1.26 +const Cc = Components.classes; 1.27 +const Ci = Components.interfaces; 1.28 +const Cu = Components.utils; 1.29 + 1.30 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.31 +Cu.import("resource://gre/modules/Services.jsm"); 1.32 + 1.33 +//////////////////////////////////////////////////////////////////////////////// 1.34 +//// Constants 1.35 + 1.36 +// Last expiration step should run before the final sync. 1.37 +const TOPIC_SHUTDOWN = "places-will-close-connection"; 1.38 +const TOPIC_PREF_CHANGED = "nsPref:changed"; 1.39 +const TOPIC_DEBUG_START_EXPIRATION = "places-debug-start-expiration"; 1.40 +const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished"; 1.41 +const TOPIC_IDLE_BEGIN = "idle"; 1.42 +const TOPIC_IDLE_END = "active"; 1.43 +const TOPIC_IDLE_DAILY = "idle-daily"; 1.44 + 1.45 +// Branch for all expiration preferences. 1.46 +const PREF_BRANCH = "places.history.expiration."; 1.47 + 1.48 +// Max number of unique URIs to retain in history. 1.49 +// Notice this is a lazy limit. This means we will start to expire if we will 1.50 +// go over it, but we won't ensure that we will stop exactly when we reach it, 1.51 +// instead we will stop after the next expiration step that will bring us 1.52 +// below it. 1.53 +// If this preference does not exist or has a negative value, we will calculate 1.54 +// a limit based on current hardware. 1.55 +const PREF_MAX_URIS = "max_pages"; 1.56 +const PREF_MAX_URIS_NOTSET = -1; // Use our internally calculated limit. 1.57 + 1.58 +// We save the current unique URIs limit to this pref, to make it available to 1.59 +// other components without having to duplicate the full logic. 1.60 +const PREF_READONLY_CALCULATED_MAX_URIS = "transient_current_max_pages"; 1.61 + 1.62 +// Seconds between each expiration step. 1.63 +const PREF_INTERVAL_SECONDS = "interval_seconds"; 1.64 +const PREF_INTERVAL_SECONDS_NOTSET = 3 * 60; 1.65 + 1.66 +// We calculate an optimal database size, based on hardware specs. 1.67 +// This percentage of memory size is used to protect against calculating a too 1.68 +// large database size on systems with small memory. 1.69 +const DATABASE_TO_MEMORY_PERC = 4; 1.70 +// This percentage of disk size is used to protect against calculating a too 1.71 +// large database size on disks with tiny quota or available space. 1.72 +const DATABASE_TO_DISK_PERC = 2; 1.73 +// Maximum size of the optimal database. High-end hardware has plenty of 1.74 +// memory and disk space, but performances don't grow linearly. 1.75 +const DATABASE_MAX_SIZE = 167772160; // 160MiB 1.76 +// If the physical memory size is bogus, fallback to this. 1.77 +const MEMSIZE_FALLBACK_BYTES = 268435456; // 256 MiB 1.78 +// If the disk available space is bogus, fallback to this. 1.79 +const DISKSIZE_FALLBACK_BYTES = 268435456; // 256 MiB 1.80 + 1.81 +// Max number of entries to expire at each expiration step. 1.82 +// This value is globally used for different kind of data we expire, can be 1.83 +// tweaked based on data type. See below in getBoundStatement. 1.84 +const EXPIRE_LIMIT_PER_STEP = 6; 1.85 +// When we run a large expiration step, the above limit is multiplied by this. 1.86 +const EXPIRE_LIMIT_PER_LARGE_STEP_MULTIPLIER = 10; 1.87 + 1.88 +// When history is clean or dirty enough we will adapt the expiration algorithm 1.89 +// to be more lazy or more aggressive. 1.90 +// This is done acting on the interval between expiration steps and the number 1.91 +// of expirable items. 1.92 +// 1. Clean history: 1.93 +// We expire at (default interval * EXPIRE_AGGRESSIVITY_MULTIPLIER) the 1.94 +// default number of entries. 1.95 +// 2. Dirty history: 1.96 +// We expire at the default interval, but a greater number of entries 1.97 +// (default number of entries * EXPIRE_AGGRESSIVITY_MULTIPLIER). 1.98 +const EXPIRE_AGGRESSIVITY_MULTIPLIER = 3; 1.99 + 1.100 +// This is the average size in bytes of an URI entry in the database. 1.101 +// Magic numbers are determined through analysis of the distribution of a ratio 1.102 +// between number of unique URIs and database size among our users. 1.103 +// Based on these values we evaluate how many unique URIs we can handle before 1.104 +// starting expiring some. 1.105 +const URIENTRY_AVG_SIZE = 1600; 1.106 + 1.107 +// Seconds of idle time before starting a larger expiration step. 1.108 +// Notice during idle we stop the expiration timer since we don't want to hurt 1.109 +// stand-by or mobile devices batteries. 1.110 +const IDLE_TIMEOUT_SECONDS = 5 * 60; 1.111 + 1.112 +// If a clear history ran just before we shutdown, we will skip most of the 1.113 +// expiration at shutdown. This is maximum number of seconds from last 1.114 +// clearHistory to decide to skip expiration at shutdown. 1.115 +const SHUTDOWN_WITH_RECENT_CLEARHISTORY_TIMEOUT_SECONDS = 10; 1.116 + 1.117 +// If the pages delta from the last ANALYZE is over this threashold, the tables 1.118 +// should be analyzed again. 1.119 +const ANALYZE_PAGES_THRESHOLD = 100; 1.120 + 1.121 +// If the number of pages over history limit is greater than this threshold, 1.122 +// expiration will be more aggressive, to bring back history to a saner size. 1.123 +const OVERLIMIT_PAGES_THRESHOLD = 1000; 1.124 + 1.125 +const USECS_PER_DAY = 86400000000; 1.126 +const ANNOS_EXPIRE_POLICIES = [ 1.127 + { bind: "expire_days", 1.128 + type: Ci.nsIAnnotationService.EXPIRE_DAYS, 1.129 + time: 7 * USECS_PER_DAY }, 1.130 + { bind: "expire_weeks", 1.131 + type: Ci.nsIAnnotationService.EXPIRE_WEEKS, 1.132 + time: 30 * USECS_PER_DAY }, 1.133 + { bind: "expire_months", 1.134 + type: Ci.nsIAnnotationService.EXPIRE_MONTHS, 1.135 + time: 180 * USECS_PER_DAY }, 1.136 +]; 1.137 + 1.138 +// When we expire we can use these limits: 1.139 +// - SMALL for usual partial expirations, will expire a small chunk. 1.140 +// - LARGE for idle or shutdown expirations, will expire a large chunk. 1.141 +// - UNLIMITED for clearHistory, will expire everything. 1.142 +// - DEBUG will use a known limit, passed along with the debug notification. 1.143 +const LIMIT = { 1.144 + SMALL: 0, 1.145 + LARGE: 1, 1.146 + UNLIMITED: 2, 1.147 + DEBUG: 3, 1.148 +}; 1.149 + 1.150 +// Represents the status of history database. 1.151 +const STATUS = { 1.152 + CLEAN: 0, 1.153 + DIRTY: 1, 1.154 + UNKNOWN: 2, 1.155 +}; 1.156 + 1.157 +// Represents actions on which a query will run. 1.158 +const ACTION = { 1.159 + TIMED: 1 << 0, // happens every this._interval 1.160 + TIMED_OVERLIMIT: 1 << 1, // like TIMED but only when history is over limits 1.161 + TIMED_ANALYZE: 1 << 2, // happens when ANALYZE statistics should be updated 1.162 + CLEAR_HISTORY: 1 << 3, // happens when history is cleared 1.163 + SHUTDOWN_DIRTY: 1 << 4, // happens at shutdown for DIRTY state 1.164 + IDLE_DIRTY: 1 << 5, // happens on idle for DIRTY state 1.165 + IDLE_DAILY: 1 << 6, // happens once a day on idle 1.166 + DEBUG: 1 << 7, // happens on TOPIC_DEBUG_START_EXPIRATION 1.167 +}; 1.168 + 1.169 +// The queries we use to expire. 1.170 +const EXPIRATION_QUERIES = { 1.171 + 1.172 + // Finds visits to be expired when history is over the unique pages limit, 1.173 + // otherwise will return nothing. 1.174 + // This explicitly excludes any visits added in the last 7 days, to protect 1.175 + // users with thousands of bookmarks from constantly losing history. 1.176 + QUERY_FIND_VISITS_TO_EXPIRE: { 1.177 + sql: "INSERT INTO expiration_notify " 1.178 + + "(v_id, url, guid, visit_date, expected_results) " 1.179 + + "SELECT v.id, h.url, h.guid, v.visit_date, :limit_visits " 1.180 + + "FROM moz_historyvisits v " 1.181 + + "JOIN moz_places h ON h.id = v.place_id " 1.182 + + "WHERE (SELECT COUNT(*) FROM moz_places) > :max_uris " 1.183 + + "AND visit_date < strftime('%s','now','localtime','start of day','-7 days','utc') * 1000000 " 1.184 + + "ORDER BY v.visit_date ASC " 1.185 + + "LIMIT :limit_visits", 1.186 + actions: ACTION.TIMED_OVERLIMIT | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | 1.187 + ACTION.DEBUG 1.188 + }, 1.189 + 1.190 + // Removes the previously found visits. 1.191 + QUERY_EXPIRE_VISITS: { 1.192 + sql: "DELETE FROM moz_historyvisits WHERE id IN ( " 1.193 + + "SELECT v_id FROM expiration_notify WHERE v_id NOTNULL " 1.194 + + ")", 1.195 + actions: ACTION.TIMED_OVERLIMIT | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | 1.196 + ACTION.DEBUG 1.197 + }, 1.198 + 1.199 + // Finds orphan URIs in the database. 1.200 + // Notice we won't notify single removed URIs on removeAllPages, so we don't 1.201 + // run this query in such a case, but just delete URIs. 1.202 + // This could run in the middle of adding a visit or bookmark to a new page. 1.203 + // In such a case since it is async, could end up expiring the orphan page 1.204 + // before it actually gets the new visit or bookmark. 1.205 + // Thus, since new pages get frecency -1, we filter on that. 1.206 + QUERY_FIND_URIS_TO_EXPIRE: { 1.207 + sql: "INSERT INTO expiration_notify " 1.208 + + "(p_id, url, guid, visit_date, expected_results) " 1.209 + + "SELECT h.id, h.url, h.guid, h.last_visit_date, :limit_uris " 1.210 + + "FROM moz_places h " 1.211 + + "LEFT JOIN moz_historyvisits v ON h.id = v.place_id " 1.212 + + "LEFT JOIN moz_bookmarks b ON h.id = b.fk " 1.213 + + "WHERE h.last_visit_date IS NULL " 1.214 + + "AND v.id IS NULL " 1.215 + + "AND b.id IS NULL " 1.216 + + "AND frecency <> -1 " 1.217 + + "LIMIT :limit_uris", 1.218 + actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY | 1.219 + ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG 1.220 + }, 1.221 + 1.222 + // Expire found URIs from the database. 1.223 + QUERY_EXPIRE_URIS: { 1.224 + sql: "DELETE FROM moz_places WHERE id IN ( " 1.225 + + "SELECT p_id FROM expiration_notify WHERE p_id NOTNULL " 1.226 + + ")", 1.227 + actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY | 1.228 + ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG 1.229 + }, 1.230 + 1.231 + // Expire orphan URIs from the database. 1.232 + QUERY_SILENT_EXPIRE_ORPHAN_URIS: { 1.233 + sql: "DELETE FROM moz_places WHERE id IN ( " 1.234 + + "SELECT h.id " 1.235 + + "FROM moz_places h " 1.236 + + "LEFT JOIN moz_historyvisits v ON h.id = v.place_id " 1.237 + + "LEFT JOIN moz_bookmarks b ON h.id = b.fk " 1.238 + + "WHERE h.last_visit_date IS NULL " 1.239 + + "AND v.id IS NULL " 1.240 + + "AND b.id IS NULL " 1.241 + + "LIMIT :limit_uris " 1.242 + + ")", 1.243 + actions: ACTION.CLEAR_HISTORY 1.244 + }, 1.245 + 1.246 + // Expire orphan icons from the database. 1.247 + QUERY_EXPIRE_FAVICONS: { 1.248 + sql: "DELETE FROM moz_favicons WHERE id IN ( " 1.249 + + "SELECT f.id FROM moz_favicons f " 1.250 + + "LEFT JOIN moz_places h ON f.id = h.favicon_id " 1.251 + + "WHERE h.favicon_id IS NULL " 1.252 + + "LIMIT :limit_favicons " 1.253 + + ")", 1.254 + actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY | 1.255 + ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | 1.256 + ACTION.DEBUG 1.257 + }, 1.258 + 1.259 + // Expire orphan page annotations from the database. 1.260 + QUERY_EXPIRE_ANNOS: { 1.261 + sql: "DELETE FROM moz_annos WHERE id in ( " 1.262 + + "SELECT a.id FROM moz_annos a " 1.263 + + "LEFT JOIN moz_places h ON a.place_id = h.id " 1.264 + + "WHERE h.id IS NULL " 1.265 + + "LIMIT :limit_annos " 1.266 + + ")", 1.267 + actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY | 1.268 + ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | 1.269 + ACTION.DEBUG 1.270 + }, 1.271 + 1.272 + // Expire page annotations based on expiration policy. 1.273 + QUERY_EXPIRE_ANNOS_WITH_POLICY: { 1.274 + sql: "DELETE FROM moz_annos " 1.275 + + "WHERE (expiration = :expire_days " 1.276 + + "AND :expire_days_time > MAX(lastModified, dateAdded)) " 1.277 + + "OR (expiration = :expire_weeks " 1.278 + + "AND :expire_weeks_time > MAX(lastModified, dateAdded)) " 1.279 + + "OR (expiration = :expire_months " 1.280 + + "AND :expire_months_time > MAX(lastModified, dateAdded))", 1.281 + actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY | 1.282 + ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | 1.283 + ACTION.DEBUG 1.284 + }, 1.285 + 1.286 + // Expire items annotations based on expiration policy. 1.287 + QUERY_EXPIRE_ITEMS_ANNOS_WITH_POLICY: { 1.288 + sql: "DELETE FROM moz_items_annos " 1.289 + + "WHERE (expiration = :expire_days " 1.290 + + "AND :expire_days_time > MAX(lastModified, dateAdded)) " 1.291 + + "OR (expiration = :expire_weeks " 1.292 + + "AND :expire_weeks_time > MAX(lastModified, dateAdded)) " 1.293 + + "OR (expiration = :expire_months " 1.294 + + "AND :expire_months_time > MAX(lastModified, dateAdded))", 1.295 + actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY | 1.296 + ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | 1.297 + ACTION.DEBUG 1.298 + }, 1.299 + 1.300 + // Expire page annotations based on expiration policy. 1.301 + QUERY_EXPIRE_ANNOS_WITH_HISTORY: { 1.302 + sql: "DELETE FROM moz_annos " 1.303 + + "WHERE expiration = :expire_with_history " 1.304 + + "AND NOT EXISTS (SELECT id FROM moz_historyvisits " 1.305 + + "WHERE place_id = moz_annos.place_id LIMIT 1)", 1.306 + actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY | 1.307 + ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | 1.308 + ACTION.DEBUG 1.309 + }, 1.310 + 1.311 + // Expire item annos without a corresponding item id. 1.312 + QUERY_EXPIRE_ITEMS_ANNOS: { 1.313 + sql: "DELETE FROM moz_items_annos WHERE id IN ( " 1.314 + + "SELECT a.id FROM moz_items_annos a " 1.315 + + "LEFT JOIN moz_bookmarks b ON a.item_id = b.id " 1.316 + + "WHERE b.id IS NULL " 1.317 + + "LIMIT :limit_annos " 1.318 + + ")", 1.319 + actions: ACTION.CLEAR_HISTORY | ACTION.IDLE_DAILY | ACTION.DEBUG 1.320 + }, 1.321 + 1.322 + // Expire all annotation names without a corresponding annotation. 1.323 + QUERY_EXPIRE_ANNO_ATTRIBUTES: { 1.324 + sql: "DELETE FROM moz_anno_attributes WHERE id IN ( " 1.325 + + "SELECT n.id FROM moz_anno_attributes n " 1.326 + + "LEFT JOIN moz_annos a ON n.id = a.anno_attribute_id " 1.327 + + "LEFT JOIN moz_items_annos t ON n.id = t.anno_attribute_id " 1.328 + + "WHERE a.anno_attribute_id IS NULL " 1.329 + + "AND t.anno_attribute_id IS NULL " 1.330 + + "LIMIT :limit_annos" 1.331 + + ")", 1.332 + actions: ACTION.CLEAR_HISTORY | ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | 1.333 + ACTION.IDLE_DAILY | ACTION.DEBUG 1.334 + }, 1.335 + 1.336 + // Expire orphan inputhistory. 1.337 + QUERY_EXPIRE_INPUTHISTORY: { 1.338 + sql: "DELETE FROM moz_inputhistory WHERE place_id IN ( " 1.339 + + "SELECT i.place_id FROM moz_inputhistory i " 1.340 + + "LEFT JOIN moz_places h ON h.id = i.place_id " 1.341 + + "WHERE h.id IS NULL " 1.342 + + "LIMIT :limit_inputhistory " 1.343 + + ")", 1.344 + actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY | 1.345 + ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | 1.346 + ACTION.DEBUG 1.347 + }, 1.348 + 1.349 + // Expire all session annotations. Should only be called at shutdown. 1.350 + QUERY_EXPIRE_ANNOS_SESSION: { 1.351 + sql: "DELETE FROM moz_annos WHERE expiration = :expire_session", 1.352 + actions: ACTION.CLEAR_HISTORY | ACTION.DEBUG 1.353 + }, 1.354 + 1.355 + // Expire all session item annotations. Should only be called at shutdown. 1.356 + QUERY_EXPIRE_ITEMS_ANNOS_SESSION: { 1.357 + sql: "DELETE FROM moz_items_annos WHERE expiration = :expire_session", 1.358 + actions: ACTION.CLEAR_HISTORY | ACTION.DEBUG 1.359 + }, 1.360 + 1.361 + // Select entries for notifications. 1.362 + // If p_id is set whole_entry = 1, then we have expired the full page. 1.363 + // Either p_id or v_id are always set. 1.364 + QUERY_SELECT_NOTIFICATIONS: { 1.365 + sql: "SELECT url, guid, MAX(visit_date) AS visit_date, " 1.366 + + "MAX(IFNULL(MIN(p_id, 1), MIN(v_id, 0))) AS whole_entry, " 1.367 + + "expected_results " 1.368 + + "FROM expiration_notify " 1.369 + + "GROUP BY url", 1.370 + actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY | 1.371 + ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG 1.372 + }, 1.373 + 1.374 + // Empty the notifications table. 1.375 + QUERY_DELETE_NOTIFICATIONS: { 1.376 + sql: "DELETE FROM expiration_notify", 1.377 + actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY | 1.378 + ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG 1.379 + }, 1.380 + 1.381 + // The following queries are used to adjust the sqlite_stat1 table to help the 1.382 + // query planner create better queries. These should always be run LAST, and 1.383 + // are therefore at the end of the object. 1.384 + // Since also nsNavHistory.cpp executes ANALYZE, the analyzed tables 1.385 + // must be the same in both components. So ensure they are in sync. 1.386 + 1.387 + QUERY_ANALYZE_MOZ_PLACES: { 1.388 + sql: "ANALYZE moz_places", 1.389 + actions: ACTION.TIMED_OVERLIMIT | ACTION.TIMED_ANALYZE | 1.390 + ACTION.CLEAR_HISTORY | ACTION.IDLE_DAILY | ACTION.DEBUG 1.391 + }, 1.392 + QUERY_ANALYZE_MOZ_BOOKMARKS: { 1.393 + sql: "ANALYZE moz_bookmarks", 1.394 + actions: ACTION.TIMED_ANALYZE | ACTION.IDLE_DAILY | ACTION.DEBUG 1.395 + }, 1.396 + QUERY_ANALYZE_MOZ_HISTORYVISITS: { 1.397 + sql: "ANALYZE moz_historyvisits", 1.398 + actions: ACTION.TIMED_OVERLIMIT | ACTION.TIMED_ANALYZE | 1.399 + ACTION.CLEAR_HISTORY | ACTION.IDLE_DAILY | ACTION.DEBUG 1.400 + }, 1.401 + QUERY_ANALYZE_MOZ_INPUTHISTORY: { 1.402 + sql: "ANALYZE moz_inputhistory", 1.403 + actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.TIMED_ANALYZE | 1.404 + ACTION.CLEAR_HISTORY | ACTION.IDLE_DAILY | ACTION.DEBUG 1.405 + }, 1.406 +}; 1.407 + 1.408 +//////////////////////////////////////////////////////////////////////////////// 1.409 +//// nsPlacesExpiration definition 1.410 + 1.411 +function nsPlacesExpiration() 1.412 +{ 1.413 + ////////////////////////////////////////////////////////////////////////////// 1.414 + //// Smart Getters 1.415 + 1.416 + XPCOMUtils.defineLazyGetter(this, "_db", function () { 1.417 + let db = Cc["@mozilla.org/browser/nav-history-service;1"]. 1.418 + getService(Ci.nsPIPlacesDatabase). 1.419 + DBConnection; 1.420 + 1.421 + // Create the temporary notifications table. 1.422 + let stmt = db.createAsyncStatement( 1.423 + "CREATE TEMP TABLE expiration_notify ( " 1.424 + + " id INTEGER PRIMARY KEY " 1.425 + + ", v_id INTEGER " 1.426 + + ", p_id INTEGER " 1.427 + + ", url TEXT NOT NULL " 1.428 + + ", guid TEXT NOT NULL " 1.429 + + ", visit_date INTEGER " 1.430 + + ", expected_results INTEGER NOT NULL " 1.431 + + ") "); 1.432 + stmt.executeAsync(); 1.433 + stmt.finalize(); 1.434 + 1.435 + return db; 1.436 + }); 1.437 + 1.438 + XPCOMUtils.defineLazyServiceGetter(this, "_hsn", 1.439 + "@mozilla.org/browser/nav-history-service;1", 1.440 + "nsPIPlacesHistoryListenersNotifier"); 1.441 + XPCOMUtils.defineLazyServiceGetter(this, "_sys", 1.442 + "@mozilla.org/system-info;1", 1.443 + "nsIPropertyBag2"); 1.444 + XPCOMUtils.defineLazyServiceGetter(this, "_idle", 1.445 + "@mozilla.org/widget/idleservice;1", 1.446 + "nsIIdleService"); 1.447 + 1.448 + this._prefBranch = Cc["@mozilla.org/preferences-service;1"]. 1.449 + getService(Ci.nsIPrefService). 1.450 + getBranch(PREF_BRANCH); 1.451 + this._loadPrefs(); 1.452 + 1.453 + // Observe our preferences branch for changes. 1.454 + this._prefBranch.addObserver("", this, false); 1.455 + 1.456 + // Register topic observers. 1.457 + Services.obs.addObserver(this, TOPIC_SHUTDOWN, false); 1.458 + Services.obs.addObserver(this, TOPIC_DEBUG_START_EXPIRATION, false); 1.459 + Services.obs.addObserver(this, TOPIC_IDLE_DAILY, false); 1.460 + 1.461 + // Create our expiration timer. 1.462 + this._newTimer(); 1.463 +} 1.464 + 1.465 +nsPlacesExpiration.prototype = { 1.466 + 1.467 + ////////////////////////////////////////////////////////////////////////////// 1.468 + //// nsIObserver 1.469 + 1.470 + observe: function PEX_observe(aSubject, aTopic, aData) 1.471 + { 1.472 + if (aTopic == TOPIC_SHUTDOWN) { 1.473 + this._shuttingDown = true; 1.474 + Services.obs.removeObserver(this, TOPIC_SHUTDOWN); 1.475 + Services.obs.removeObserver(this, TOPIC_DEBUG_START_EXPIRATION); 1.476 + Services.obs.removeObserver(this, TOPIC_IDLE_DAILY); 1.477 + 1.478 + this._prefBranch.removeObserver("", this); 1.479 + 1.480 + this.expireOnIdle = false; 1.481 + 1.482 + if (this._timer) { 1.483 + this._timer.cancel(); 1.484 + this._timer = null; 1.485 + } 1.486 + 1.487 + // If we didn't ran a clearHistory recently and database is dirty, we 1.488 + // want to expire some entries, to speed up the expiration process. 1.489 + let hasRecentClearHistory = 1.490 + Date.now() - this._lastClearHistoryTime < 1.491 + SHUTDOWN_WITH_RECENT_CLEARHISTORY_TIMEOUT_SECONDS * 1000; 1.492 + if (!hasRecentClearHistory && this.status == STATUS.DIRTY) { 1.493 + this._expireWithActionAndLimit(ACTION.SHUTDOWN_DIRTY, LIMIT.LARGE); 1.494 + } 1.495 + 1.496 + this._finalizeInternalStatements(); 1.497 + } 1.498 + else if (aTopic == TOPIC_PREF_CHANGED) { 1.499 + this._loadPrefs(); 1.500 + 1.501 + if (aData == PREF_INTERVAL_SECONDS) { 1.502 + // Renew the timer with the new interval value. 1.503 + this._newTimer(); 1.504 + } 1.505 + } 1.506 + else if (aTopic == TOPIC_DEBUG_START_EXPIRATION) { 1.507 + // The passed-in limit is the maximum number of visits to expire when 1.508 + // history is over capacity. Mind to correctly handle the NaN value. 1.509 + let limit = parseInt(aData); 1.510 + if (limit == -1) { 1.511 + // Everything should be expired without any limit. If history is over 1.512 + // capacity then all existing visits will be expired. 1.513 + // Should only be used in tests, since may cause dataloss. 1.514 + this._expireWithActionAndLimit(ACTION.DEBUG, LIMIT.UNLIMITED); 1.515 + } 1.516 + else if (limit > 0) { 1.517 + // The number of expired visits is limited by this amount. It may be 1.518 + // used for testing purposes, like checking that limited queries work. 1.519 + this._debugLimit = limit; 1.520 + this._expireWithActionAndLimit(ACTION.DEBUG, LIMIT.DEBUG); 1.521 + } 1.522 + else { 1.523 + // Any other value is intended as a 0 limit, that means no visits 1.524 + // will be expired. Even if this doesn't touch visits, it will remove 1.525 + // any orphan pages, icons, annotations and similar from the database, 1.526 + // so it may be used for cleanup purposes. 1.527 + this._debugLimit = -1; 1.528 + this._expireWithActionAndLimit(ACTION.DEBUG, LIMIT.DEBUG); 1.529 + } 1.530 + } 1.531 + else if (aTopic == TOPIC_IDLE_BEGIN) { 1.532 + // Stop the expiration timer. We don't want to keep up expiring on idle 1.533 + // to preserve batteries on mobile devices and avoid killing stand-by. 1.534 + if (this._timer) { 1.535 + this._timer.cancel(); 1.536 + this._timer = null; 1.537 + } 1.538 + if (this.expireOnIdle) 1.539 + this._expireWithActionAndLimit(ACTION.IDLE_DIRTY, LIMIT.LARGE); 1.540 + } 1.541 + else if (aTopic == TOPIC_IDLE_END) { 1.542 + // Restart the expiration timer. 1.543 + if (!this._timer) 1.544 + this._newTimer(); 1.545 + } 1.546 + else if (aTopic == TOPIC_IDLE_DAILY) { 1.547 + this._expireWithActionAndLimit(ACTION.IDLE_DAILY, LIMIT.LARGE); 1.548 + } 1.549 + }, 1.550 + 1.551 + ////////////////////////////////////////////////////////////////////////////// 1.552 + //// nsINavHistoryObserver 1.553 + 1.554 + _inBatchMode: false, 1.555 + onBeginUpdateBatch: function PEX_onBeginUpdateBatch() 1.556 + { 1.557 + this._inBatchMode = true; 1.558 + 1.559 + // We do not want to expire while we are doing batch work. 1.560 + if (this._timer) { 1.561 + this._timer.cancel(); 1.562 + this._timer = null; 1.563 + } 1.564 + }, 1.565 + 1.566 + onEndUpdateBatch: function PEX_onEndUpdateBatch() 1.567 + { 1.568 + this._inBatchMode = false; 1.569 + 1.570 + // Restore timer. 1.571 + if (!this._timer) 1.572 + this._newTimer(); 1.573 + }, 1.574 + 1.575 + _lastClearHistoryTime: 0, 1.576 + onClearHistory: function PEX_onClearHistory() { 1.577 + this._lastClearHistoryTime = Date.now(); 1.578 + // Expire orphans. History status is clean after a clear history. 1.579 + this.status = STATUS.CLEAN; 1.580 + this._expireWithActionAndLimit(ACTION.CLEAR_HISTORY, LIMIT.UNLIMITED); 1.581 + }, 1.582 + 1.583 + onVisit: function() {}, 1.584 + onTitleChanged: function() {}, 1.585 + onDeleteURI: function() {}, 1.586 + onPageChanged: function() {}, 1.587 + onDeleteVisits: function() {}, 1.588 + 1.589 + ////////////////////////////////////////////////////////////////////////////// 1.590 + //// nsITimerCallback 1.591 + 1.592 + notify: function PEX_timerCallback() 1.593 + { 1.594 + // Check if we are over history capacity, if so visits must be expired. 1.595 + this._getPagesStats((function onPagesCount(aPagesCount, aStatsCount) { 1.596 + let overLimitPages = aPagesCount - this._urisLimit; 1.597 + this._overLimit = overLimitPages > 0; 1.598 + 1.599 + let action = this._overLimit ? ACTION.TIMED_OVERLIMIT : ACTION.TIMED; 1.600 + // If the number of pages changed significantly from the last ANALYZE 1.601 + // update SQLite statistics. 1.602 + if (Math.abs(aPagesCount - aStatsCount) >= ANALYZE_PAGES_THRESHOLD) { 1.603 + action = action | ACTION.TIMED_ANALYZE; 1.604 + } 1.605 + 1.606 + // Adapt expiration aggressivity to the number of pages over the limit. 1.607 + let limit = overLimitPages > OVERLIMIT_PAGES_THRESHOLD ? LIMIT.LARGE 1.608 + : LIMIT.SMALL; 1.609 + 1.610 + this._expireWithActionAndLimit(action, limit); 1.611 + }).bind(this)); 1.612 + }, 1.613 + 1.614 + ////////////////////////////////////////////////////////////////////////////// 1.615 + //// mozIStorageStatementCallback 1.616 + 1.617 + handleResult: function PEX_handleResult(aResultSet) 1.618 + { 1.619 + // We don't want to notify after shutdown. 1.620 + if (this._shuttingDown) 1.621 + return; 1.622 + 1.623 + let row; 1.624 + while ((row = aResultSet.getNextRow())) { 1.625 + if (!("_expectedResultsCount" in this)) 1.626 + this._expectedResultsCount = row.getResultByName("expected_results"); 1.627 + if (this._expectedResultsCount > 0) 1.628 + this._expectedResultsCount--; 1.629 + 1.630 + let uri = Services.io.newURI(row.getResultByName("url"), null, null); 1.631 + let guid = row.getResultByName("guid"); 1.632 + let visitDate = row.getResultByName("visit_date"); 1.633 + let wholeEntry = row.getResultByName("whole_entry"); 1.634 + // Dispatch expiration notifications to history. 1.635 + this._hsn.notifyOnPageExpired(uri, visitDate, wholeEntry, guid, 1.636 + Ci.nsINavHistoryObserver.REASON_EXPIRED, 0); 1.637 + } 1.638 + }, 1.639 + 1.640 + handleError: function PEX_handleError(aError) 1.641 + { 1.642 + Cu.reportError("Async statement execution returned with '" + 1.643 + aError.result + "', '" + aError.message + "'"); 1.644 + }, 1.645 + 1.646 + // Number of expiration steps needed to reach a CLEAN status. 1.647 + _telemetrySteps: 1, 1.648 + handleCompletion: function PEX_handleCompletion(aReason) 1.649 + { 1.650 + if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) { 1.651 + if ("_expectedResultsCount" in this) { 1.652 + // Adapt the aggressivity of steps based on the status of history. 1.653 + // A dirty history will return all the entries we are expecting bringing 1.654 + // our countdown to zero, while a clean one will not. 1.655 + let oldStatus = this.status; 1.656 + this.status = this._expectedResultsCount == 0 ? STATUS.DIRTY 1.657 + : STATUS.CLEAN; 1.658 + 1.659 + // Collect or send telemetry data. 1.660 + if (this.status == STATUS.DIRTY) { 1.661 + this._telemetrySteps++; 1.662 + } 1.663 + else { 1.664 + // Avoid reporting the common cases where the database is clean, or 1.665 + // a single step is needed. 1.666 + if (oldStatus == STATUS.DIRTY) { 1.667 + try { 1.668 + Services.telemetry 1.669 + .getHistogramById("PLACES_EXPIRATION_STEPS_TO_CLEAN2") 1.670 + .add(this._telemetrySteps); 1.671 + } catch (ex) { 1.672 + Components.utils.reportError("Unable to report telemetry."); 1.673 + } 1.674 + } 1.675 + this._telemetrySteps = 1; 1.676 + } 1.677 + 1.678 + delete this._expectedResultsCount; 1.679 + } 1.680 + 1.681 + // Dispatch a notification that expiration has finished. 1.682 + Services.obs.notifyObservers(null, TOPIC_EXPIRATION_FINISHED, null); 1.683 + } 1.684 + }, 1.685 + 1.686 + ////////////////////////////////////////////////////////////////////////////// 1.687 + //// nsPlacesExpiration 1.688 + 1.689 + _urisLimit: PREF_MAX_URIS_NOTSET, 1.690 + _interval: PREF_INTERVAL_SECONDS_NOTSET, 1.691 + _shuttingDown: false, 1.692 + 1.693 + _status: STATUS.UNKNOWN, 1.694 + set status(aNewStatus) { 1.695 + if (aNewStatus != this._status) { 1.696 + // If status changes we should restart the timer. 1.697 + this._status = aNewStatus; 1.698 + this._newTimer(); 1.699 + // If needed add/remove the cleanup step on idle. We want to expire on 1.700 + // idle only if history is dirty, to preserve mobile devices batteries. 1.701 + this.expireOnIdle = aNewStatus == STATUS.DIRTY; 1.702 + } 1.703 + return aNewStatus; 1.704 + }, 1.705 + get status() this._status, 1.706 + 1.707 + _isIdleObserver: false, 1.708 + _expireOnIdle: false, 1.709 + set expireOnIdle(aExpireOnIdle) { 1.710 + // Observe idle regardless aExpireOnIdle, since we always want to stop 1.711 + // timed expiration on idle, to preserve mobile battery life. 1.712 + if (!this._isIdleObserver && !this._shuttingDown) { 1.713 + this._idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS); 1.714 + this._isIdleObserver = true; 1.715 + } 1.716 + else if (this._isIdleObserver && this._shuttingDown) { 1.717 + this._idle.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS); 1.718 + this._isIdleObserver = false; 1.719 + } 1.720 + 1.721 + // If running a debug expiration we need full control of what happens 1.722 + // but idle cleanup could activate in the middle, since tinderboxes are 1.723 + // permanently idle. That would cause unexpected oranges, so disable it. 1.724 + if (this._debugLimit !== undefined) 1.725 + this._expireOnIdle = false; 1.726 + else 1.727 + this._expireOnIdle = aExpireOnIdle; 1.728 + return this._expireOnIdle; 1.729 + }, 1.730 + get expireOnIdle() this._expireOnIdle, 1.731 + 1.732 + _loadPrefs: function PEX__loadPrefs() { 1.733 + // Get the user's limit, if it was set. 1.734 + try { 1.735 + // We want to silently fail since getIntPref throws if it does not exist, 1.736 + // and use a default to fallback to. 1.737 + this._urisLimit = this._prefBranch.getIntPref(PREF_MAX_URIS); 1.738 + } 1.739 + catch(e) {} 1.740 + 1.741 + if (this._urisLimit < 0) { 1.742 + // The preference did not exist or has a negative value. 1.743 + // Calculate the number of unique places that may fit an optimal database 1.744 + // size on this hardware. If there are more than these unique pages, 1.745 + // some will be expired. 1.746 + 1.747 + let memSizeBytes = MEMSIZE_FALLBACK_BYTES; 1.748 + try { 1.749 + // Limit the size on systems with small memory. 1.750 + memSizeBytes = this._sys.getProperty("memsize"); 1.751 + } catch (ex) {} 1.752 + if (memSizeBytes <= 0) { 1.753 + memsize = MEMSIZE_FALLBACK_BYTES; 1.754 + } 1.755 + 1.756 + let diskAvailableBytes = DISKSIZE_FALLBACK_BYTES; 1.757 + try { 1.758 + // Protect against a full disk or tiny quota. 1.759 + let dbFile = this._db.databaseFile; 1.760 + dbFile.QueryInterface(Ci.nsILocalFile); 1.761 + diskAvailableBytes = dbFile.diskSpaceAvailable; 1.762 + } catch (ex) {} 1.763 + if (diskAvailableBytes <= 0) { 1.764 + diskAvailableBytes = DISKSIZE_FALLBACK_BYTES; 1.765 + } 1.766 + 1.767 + let optimalDatabaseSize = Math.min( 1.768 + memSizeBytes * DATABASE_TO_MEMORY_PERC / 100, 1.769 + diskAvailableBytes * DATABASE_TO_DISK_PERC / 100, 1.770 + DATABASE_MAX_SIZE 1.771 + ); 1.772 + 1.773 + this._urisLimit = Math.ceil(optimalDatabaseSize / URIENTRY_AVG_SIZE); 1.774 + } 1.775 + 1.776 + // Expose the calculated limit to other components. 1.777 + this._prefBranch.setIntPref(PREF_READONLY_CALCULATED_MAX_URIS, 1.778 + this._urisLimit); 1.779 + 1.780 + // Get the expiration interval value. 1.781 + try { 1.782 + // We want to silently fail since getIntPref throws if it does not exist, 1.783 + // and use a default to fallback to. 1.784 + this._interval = this._prefBranch.getIntPref(PREF_INTERVAL_SECONDS); 1.785 + } 1.786 + catch (e) {} 1.787 + if (this._interval <= 0) 1.788 + this._interval = PREF_INTERVAL_SECONDS_NOTSET; 1.789 + }, 1.790 + 1.791 + /** 1.792 + * Evaluates the real number of pages in the database and the value currently 1.793 + * used by the SQLite query planner. 1.794 + * 1.795 + * @param aCallback 1.796 + * invoked on success, function (aPagesCount, aStatsCount). 1.797 + */ 1.798 + _getPagesStats: function PEX__getPagesStats(aCallback) { 1.799 + if (!this._cachedStatements["LIMIT_COUNT"]) { 1.800 + this._cachedStatements["LIMIT_COUNT"] = this._db.createAsyncStatement( 1.801 + "SELECT (SELECT COUNT(*) FROM moz_places), " 1.802 + + "(SELECT SUBSTR(stat,1,LENGTH(stat)-2) FROM sqlite_stat1 " 1.803 + + "WHERE idx = 'moz_places_url_uniqueindex')" 1.804 + ); 1.805 + } 1.806 + this._cachedStatements["LIMIT_COUNT"].executeAsync({ 1.807 + _pagesCount: 0, 1.808 + _statsCount: 0, 1.809 + handleResult: function(aResults) { 1.810 + let row = aResults.getNextRow(); 1.811 + this._pagesCount = row.getResultByIndex(0); 1.812 + this._statsCount = row.getResultByIndex(1); 1.813 + }, 1.814 + handleCompletion: function (aReason) { 1.815 + if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) { 1.816 + aCallback(this._pagesCount, this._statsCount); 1.817 + } 1.818 + }, 1.819 + handleError: function(aError) { 1.820 + Cu.reportError("Async statement execution returned with '" + 1.821 + aError.result + "', '" + aError.message + "'"); 1.822 + } 1.823 + }); 1.824 + }, 1.825 + 1.826 + /** 1.827 + * Execute async statements to expire with the specified queries. 1.828 + * 1.829 + * @param aAction 1.830 + * The ACTION we are expiring for. See the ACTION const for values. 1.831 + * @param aLimit 1.832 + * Whether to use small, large or no limits when expiring. See the 1.833 + * LIMIT const for values. 1.834 + */ 1.835 + _expireWithActionAndLimit: 1.836 + function PEX__expireWithActionAndLimit(aAction, aLimit) 1.837 + { 1.838 + // Skip expiration during batch mode. 1.839 + if (this._inBatchMode) 1.840 + return; 1.841 + // Don't try to further expire after shutdown. 1.842 + if (this._shuttingDown && aAction != ACTION.SHUTDOWN_DIRTY) { 1.843 + return; 1.844 + } 1.845 + 1.846 + let boundStatements = []; 1.847 + for (let queryType in EXPIRATION_QUERIES) { 1.848 + if (EXPIRATION_QUERIES[queryType].actions & aAction) 1.849 + boundStatements.push(this._getBoundStatement(queryType, aLimit, aAction)); 1.850 + } 1.851 + 1.852 + // Execute statements asynchronously in a transaction. 1.853 + this._db.executeAsync(boundStatements, boundStatements.length, this); 1.854 + }, 1.855 + 1.856 + /** 1.857 + * Finalizes all of our mozIStorageStatements so we can properly close the 1.858 + * database. 1.859 + */ 1.860 + _finalizeInternalStatements: function PEX__finalizeInternalStatements() 1.861 + { 1.862 + for each (let stmt in this._cachedStatements) { 1.863 + stmt.finalize(); 1.864 + } 1.865 + }, 1.866 + 1.867 + /** 1.868 + * Generate the statement used for expiration. 1.869 + * 1.870 + * @param aQueryType 1.871 + * Type of the query to build statement for. 1.872 + * @param aLimit 1.873 + * Whether to use small, large or no limits when expiring. See the 1.874 + * LIMIT const for values. 1.875 + * @param aAction 1.876 + * Current action causing the expiration. See the ACTION const. 1.877 + */ 1.878 + _cachedStatements: {}, 1.879 + _getBoundStatement: function PEX__getBoundStatement(aQueryType, aLimit, aAction) 1.880 + { 1.881 + // Statements creation can be expensive, so we want to cache them. 1.882 + let stmt = this._cachedStatements[aQueryType]; 1.883 + if (stmt === undefined) { 1.884 + stmt = this._cachedStatements[aQueryType] = 1.885 + this._db.createAsyncStatement(EXPIRATION_QUERIES[aQueryType].sql); 1.886 + } 1.887 + 1.888 + let baseLimit; 1.889 + switch (aLimit) { 1.890 + case LIMIT.UNLIMITED: 1.891 + baseLimit = -1; 1.892 + break; 1.893 + case LIMIT.SMALL: 1.894 + baseLimit = EXPIRE_LIMIT_PER_STEP; 1.895 + break; 1.896 + case LIMIT.LARGE: 1.897 + baseLimit = EXPIRE_LIMIT_PER_STEP * EXPIRE_LIMIT_PER_LARGE_STEP_MULTIPLIER; 1.898 + break; 1.899 + case LIMIT.DEBUG: 1.900 + baseLimit = this._debugLimit; 1.901 + break; 1.902 + } 1.903 + if (this.status == STATUS.DIRTY && aAction != ACTION.DEBUG && 1.904 + baseLimit > 0) { 1.905 + baseLimit *= EXPIRE_AGGRESSIVITY_MULTIPLIER; 1.906 + } 1.907 + 1.908 + // Bind the appropriate parameters. 1.909 + let params = stmt.params; 1.910 + switch (aQueryType) { 1.911 + case "QUERY_FIND_VISITS_TO_EXPIRE": 1.912 + params.max_uris = this._urisLimit; 1.913 + // Avoid expiring all visits in case of an unlimited debug expiration, 1.914 + // just remove orphans instead. 1.915 + params.limit_visits = 1.916 + aLimit == LIMIT.DEBUG && baseLimit == -1 ? 0 : baseLimit; 1.917 + break; 1.918 + case "QUERY_FIND_URIS_TO_EXPIRE": 1.919 + params.limit_uris = baseLimit; 1.920 + break; 1.921 + case "QUERY_SILENT_EXPIRE_ORPHAN_URIS": 1.922 + params.limit_uris = baseLimit; 1.923 + break; 1.924 + case "QUERY_EXPIRE_FAVICONS": 1.925 + params.limit_favicons = baseLimit; 1.926 + break; 1.927 + case "QUERY_EXPIRE_ANNOS": 1.928 + // Each page may have multiple annos. 1.929 + params.limit_annos = baseLimit * EXPIRE_AGGRESSIVITY_MULTIPLIER; 1.930 + break; 1.931 + case "QUERY_EXPIRE_ANNOS_WITH_POLICY": 1.932 + case "QUERY_EXPIRE_ITEMS_ANNOS_WITH_POLICY": 1.933 + let microNow = Date.now() * 1000; 1.934 + ANNOS_EXPIRE_POLICIES.forEach(function(policy) { 1.935 + params[policy.bind] = policy.type; 1.936 + params[policy.bind + "_time"] = microNow - policy.time; 1.937 + }); 1.938 + break; 1.939 + case "QUERY_EXPIRE_ANNOS_WITH_HISTORY": 1.940 + params.expire_with_history = Ci.nsIAnnotationService.EXPIRE_WITH_HISTORY; 1.941 + break; 1.942 + case "QUERY_EXPIRE_ITEMS_ANNOS": 1.943 + params.limit_annos = baseLimit; 1.944 + break; 1.945 + case "QUERY_EXPIRE_ANNO_ATTRIBUTES": 1.946 + params.limit_annos = baseLimit; 1.947 + break; 1.948 + case "QUERY_EXPIRE_INPUTHISTORY": 1.949 + params.limit_inputhistory = baseLimit; 1.950 + break; 1.951 + case "QUERY_EXPIRE_ANNOS_SESSION": 1.952 + case "QUERY_EXPIRE_ITEMS_ANNOS_SESSION": 1.953 + params.expire_session = Ci.nsIAnnotationService.EXPIRE_SESSION; 1.954 + break; 1.955 + } 1.956 + 1.957 + return stmt; 1.958 + }, 1.959 + 1.960 + /** 1.961 + * Creates a new timer based on this._interval. 1.962 + * 1.963 + * @return a REPEATING_SLACK nsITimer that runs every this._interval. 1.964 + */ 1.965 + _newTimer: function PEX__newTimer() 1.966 + { 1.967 + if (this._timer) 1.968 + this._timer.cancel(); 1.969 + if (this._shuttingDown) 1.970 + return; 1.971 + let interval = this.status != STATUS.DIRTY ? 1.972 + this._interval * EXPIRE_AGGRESSIVITY_MULTIPLIER : this._interval; 1.973 + 1.974 + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.975 + timer.initWithCallback(this, interval * 1000, 1.976 + Ci.nsITimer.TYPE_REPEATING_SLACK); 1.977 + return this._timer = timer; 1.978 + }, 1.979 + 1.980 + ////////////////////////////////////////////////////////////////////////////// 1.981 + //// nsISupports 1.982 + 1.983 + classID: Components.ID("705a423f-2f69-42f3-b9fe-1517e0dee56f"), 1.984 + 1.985 + _xpcom_factory: XPCOMUtils.generateSingletonFactory(nsPlacesExpiration), 1.986 + 1.987 + QueryInterface: XPCOMUtils.generateQI([ 1.988 + Ci.nsIObserver 1.989 + , Ci.nsINavHistoryObserver 1.990 + , Ci.nsITimerCallback 1.991 + , Ci.mozIStorageStatementCallback 1.992 + ]) 1.993 +}; 1.994 + 1.995 +//////////////////////////////////////////////////////////////////////////////// 1.996 +//// Module Registration 1.997 + 1.998 +let components = [nsPlacesExpiration]; 1.999 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);