Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* Any copyright is dedicated to the Public Domain. |
michael@0 | 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ |
michael@0 | 3 | |
michael@0 | 4 | Cu.import("resource://gre/modules/PlacesUtils.jsm"); |
michael@0 | 5 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 6 | Cu.import("resource://services-common/async.js"); |
michael@0 | 7 | Cu.import("resource://services-sync/engines/history.js"); |
michael@0 | 8 | Cu.import("resource://services-sync/service.js"); |
michael@0 | 9 | Cu.import("resource://services-sync/util.js"); |
michael@0 | 10 | |
michael@0 | 11 | const TIMESTAMP1 = (Date.now() - 103406528) * 1000; |
michael@0 | 12 | const TIMESTAMP2 = (Date.now() - 6592903) * 1000; |
michael@0 | 13 | const TIMESTAMP3 = (Date.now() - 123894) * 1000; |
michael@0 | 14 | |
michael@0 | 15 | function queryPlaces(uri, options) { |
michael@0 | 16 | let query = PlacesUtils.history.getNewQuery(); |
michael@0 | 17 | query.uri = uri; |
michael@0 | 18 | let res = PlacesUtils.history.executeQuery(query, options); |
michael@0 | 19 | res.root.containerOpen = true; |
michael@0 | 20 | |
michael@0 | 21 | let results = []; |
michael@0 | 22 | for (let i = 0; i < res.root.childCount; i++) |
michael@0 | 23 | results.push(res.root.getChild(i)); |
michael@0 | 24 | res.root.containerOpen = false; |
michael@0 | 25 | return results; |
michael@0 | 26 | } |
michael@0 | 27 | |
michael@0 | 28 | function queryHistoryVisits(uri) { |
michael@0 | 29 | let options = PlacesUtils.history.getNewQueryOptions(); |
michael@0 | 30 | options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY; |
michael@0 | 31 | options.resultType = Ci.nsINavHistoryQueryOptions.RESULTS_AS_VISIT; |
michael@0 | 32 | options.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING; |
michael@0 | 33 | return queryPlaces(uri, options); |
michael@0 | 34 | } |
michael@0 | 35 | |
michael@0 | 36 | function onNextTitleChanged(callback) { |
michael@0 | 37 | PlacesUtils.history.addObserver({ |
michael@0 | 38 | onBeginUpdateBatch: function onBeginUpdateBatch() {}, |
michael@0 | 39 | onEndUpdateBatch: function onEndUpdateBatch() {}, |
michael@0 | 40 | onPageChanged: function onPageChanged() {}, |
michael@0 | 41 | onTitleChanged: function onTitleChanged() { |
michael@0 | 42 | PlacesUtils.history.removeObserver(this); |
michael@0 | 43 | Utils.nextTick(callback); |
michael@0 | 44 | }, |
michael@0 | 45 | onVisit: function onVisit() {}, |
michael@0 | 46 | onDeleteVisits: function onDeleteVisits() {}, |
michael@0 | 47 | onPageExpired: function onPageExpired() {}, |
michael@0 | 48 | onDeleteURI: function onDeleteURI() {}, |
michael@0 | 49 | onClearHistory: function onClearHistory() {}, |
michael@0 | 50 | QueryInterface: XPCOMUtils.generateQI([ |
michael@0 | 51 | Ci.nsINavHistoryObserver, |
michael@0 | 52 | Ci.nsINavHistoryObserver_MOZILLA_1_9_1_ADDITIONS, |
michael@0 | 53 | Ci.nsISupportsWeakReference |
michael@0 | 54 | ]) |
michael@0 | 55 | }, true); |
michael@0 | 56 | } |
michael@0 | 57 | |
michael@0 | 58 | // Ensure exceptions from inside callbacks leads to test failures while |
michael@0 | 59 | // we still clean up properly. |
michael@0 | 60 | function ensureThrows(func) { |
michael@0 | 61 | return function() { |
michael@0 | 62 | try { |
michael@0 | 63 | func.apply(this, arguments); |
michael@0 | 64 | } catch (ex) { |
michael@0 | 65 | PlacesUtils.history.removeAllPages(); |
michael@0 | 66 | do_throw(ex); |
michael@0 | 67 | } |
michael@0 | 68 | }; |
michael@0 | 69 | } |
michael@0 | 70 | |
michael@0 | 71 | let store = new HistoryEngine(Service)._store; |
michael@0 | 72 | function applyEnsureNoFailures(records) { |
michael@0 | 73 | do_check_eq(store.applyIncomingBatch(records).length, 0); |
michael@0 | 74 | } |
michael@0 | 75 | |
michael@0 | 76 | let fxuri, fxguid, tburi, tbguid; |
michael@0 | 77 | |
michael@0 | 78 | function run_test() { |
michael@0 | 79 | initTestLogging("Trace"); |
michael@0 | 80 | run_next_test(); |
michael@0 | 81 | } |
michael@0 | 82 | |
michael@0 | 83 | add_test(function test_store() { |
michael@0 | 84 | _("Verify that we've got an empty store to work with."); |
michael@0 | 85 | do_check_empty(store.getAllIDs()); |
michael@0 | 86 | |
michael@0 | 87 | _("Let's create an entry in the database."); |
michael@0 | 88 | fxuri = Utils.makeURI("http://getfirefox.com/"); |
michael@0 | 89 | |
michael@0 | 90 | let place = { |
michael@0 | 91 | uri: fxuri, |
michael@0 | 92 | title: "Get Firefox!", |
michael@0 | 93 | visits: [{ |
michael@0 | 94 | visitDate: TIMESTAMP1, |
michael@0 | 95 | transitionType: Ci.nsINavHistoryService.TRANSITION_LINK |
michael@0 | 96 | }] |
michael@0 | 97 | }; |
michael@0 | 98 | PlacesUtils.asyncHistory.updatePlaces(place, { |
michael@0 | 99 | handleError: function handleError() { |
michael@0 | 100 | do_throw("Unexpected error in adding visit."); |
michael@0 | 101 | }, |
michael@0 | 102 | handleResult: function handleResult() {}, |
michael@0 | 103 | handleCompletion: onVisitAdded |
michael@0 | 104 | }); |
michael@0 | 105 | |
michael@0 | 106 | function onVisitAdded() { |
michael@0 | 107 | _("Verify that the entry exists."); |
michael@0 | 108 | let ids = Object.keys(store.getAllIDs()); |
michael@0 | 109 | do_check_eq(ids.length, 1); |
michael@0 | 110 | fxguid = ids[0]; |
michael@0 | 111 | do_check_true(store.itemExists(fxguid)); |
michael@0 | 112 | |
michael@0 | 113 | _("If we query a non-existent record, it's marked as deleted."); |
michael@0 | 114 | let record = store.createRecord("non-existent"); |
michael@0 | 115 | do_check_true(record.deleted); |
michael@0 | 116 | |
michael@0 | 117 | _("Verify createRecord() returns a complete record."); |
michael@0 | 118 | record = store.createRecord(fxguid); |
michael@0 | 119 | do_check_eq(record.histUri, fxuri.spec); |
michael@0 | 120 | do_check_eq(record.title, "Get Firefox!"); |
michael@0 | 121 | do_check_eq(record.visits.length, 1); |
michael@0 | 122 | do_check_eq(record.visits[0].date, TIMESTAMP1); |
michael@0 | 123 | do_check_eq(record.visits[0].type, Ci.nsINavHistoryService.TRANSITION_LINK); |
michael@0 | 124 | |
michael@0 | 125 | _("Let's modify the record and have the store update the database."); |
michael@0 | 126 | let secondvisit = {date: TIMESTAMP2, |
michael@0 | 127 | type: Ci.nsINavHistoryService.TRANSITION_TYPED}; |
michael@0 | 128 | onNextTitleChanged(ensureThrows(function() { |
michael@0 | 129 | let queryres = queryHistoryVisits(fxuri); |
michael@0 | 130 | do_check_eq(queryres.length, 2); |
michael@0 | 131 | do_check_eq(queryres[0].time, TIMESTAMP1); |
michael@0 | 132 | do_check_eq(queryres[0].title, "Hol Dir Firefox!"); |
michael@0 | 133 | do_check_eq(queryres[1].time, TIMESTAMP2); |
michael@0 | 134 | do_check_eq(queryres[1].title, "Hol Dir Firefox!"); |
michael@0 | 135 | run_next_test(); |
michael@0 | 136 | })); |
michael@0 | 137 | applyEnsureNoFailures([ |
michael@0 | 138 | {id: fxguid, |
michael@0 | 139 | histUri: record.histUri, |
michael@0 | 140 | title: "Hol Dir Firefox!", |
michael@0 | 141 | visits: [record.visits[0], secondvisit]} |
michael@0 | 142 | ]); |
michael@0 | 143 | } |
michael@0 | 144 | }); |
michael@0 | 145 | |
michael@0 | 146 | add_test(function test_store_create() { |
michael@0 | 147 | _("Create a brand new record through the store."); |
michael@0 | 148 | tbguid = Utils.makeGUID(); |
michael@0 | 149 | tburi = Utils.makeURI("http://getthunderbird.com"); |
michael@0 | 150 | onNextTitleChanged(ensureThrows(function() { |
michael@0 | 151 | do_check_attribute_count(store.getAllIDs(), 2); |
michael@0 | 152 | let queryres = queryHistoryVisits(tburi); |
michael@0 | 153 | do_check_eq(queryres.length, 1); |
michael@0 | 154 | do_check_eq(queryres[0].time, TIMESTAMP3); |
michael@0 | 155 | do_check_eq(queryres[0].title, "The bird is the word!"); |
michael@0 | 156 | run_next_test(); |
michael@0 | 157 | })); |
michael@0 | 158 | applyEnsureNoFailures([ |
michael@0 | 159 | {id: tbguid, |
michael@0 | 160 | histUri: tburi.spec, |
michael@0 | 161 | title: "The bird is the word!", |
michael@0 | 162 | visits: [{date: TIMESTAMP3, |
michael@0 | 163 | type: Ci.nsINavHistoryService.TRANSITION_TYPED}]} |
michael@0 | 164 | ]); |
michael@0 | 165 | }); |
michael@0 | 166 | |
michael@0 | 167 | add_test(function test_null_title() { |
michael@0 | 168 | _("Make sure we handle a null title gracefully (it can happen in some cases, e.g. for resource:// URLs)"); |
michael@0 | 169 | let resguid = Utils.makeGUID(); |
michael@0 | 170 | let resuri = Utils.makeURI("unknown://title"); |
michael@0 | 171 | applyEnsureNoFailures([ |
michael@0 | 172 | {id: resguid, |
michael@0 | 173 | histUri: resuri.spec, |
michael@0 | 174 | title: null, |
michael@0 | 175 | visits: [{date: TIMESTAMP3, |
michael@0 | 176 | type: Ci.nsINavHistoryService.TRANSITION_TYPED}]} |
michael@0 | 177 | ]); |
michael@0 | 178 | do_check_attribute_count(store.getAllIDs(), 3); |
michael@0 | 179 | let queryres = queryHistoryVisits(resuri); |
michael@0 | 180 | do_check_eq(queryres.length, 1); |
michael@0 | 181 | do_check_eq(queryres[0].time, TIMESTAMP3); |
michael@0 | 182 | run_next_test(); |
michael@0 | 183 | }); |
michael@0 | 184 | |
michael@0 | 185 | add_test(function test_invalid_records() { |
michael@0 | 186 | _("Make sure we handle invalid URLs in places databases gracefully."); |
michael@0 | 187 | let connection = PlacesUtils.history |
michael@0 | 188 | .QueryInterface(Ci.nsPIPlacesDatabase) |
michael@0 | 189 | .DBConnection; |
michael@0 | 190 | let stmt = connection.createAsyncStatement( |
michael@0 | 191 | "INSERT INTO moz_places " |
michael@0 | 192 | + "(url, title, rev_host, visit_count, last_visit_date) " |
michael@0 | 193 | + "VALUES ('invalid-uri', 'Invalid URI', '.', 1, " + TIMESTAMP3 + ")" |
michael@0 | 194 | ); |
michael@0 | 195 | Async.querySpinningly(stmt); |
michael@0 | 196 | stmt.finalize(); |
michael@0 | 197 | // Add the corresponding visit to retain database coherence. |
michael@0 | 198 | stmt = connection.createAsyncStatement( |
michael@0 | 199 | "INSERT INTO moz_historyvisits " |
michael@0 | 200 | + "(place_id, visit_date, visit_type, session) " |
michael@0 | 201 | + "VALUES ((SELECT id FROM moz_places WHERE url = 'invalid-uri'), " |
michael@0 | 202 | + TIMESTAMP3 + ", " + Ci.nsINavHistoryService.TRANSITION_TYPED + ", 1)" |
michael@0 | 203 | ); |
michael@0 | 204 | Async.querySpinningly(stmt); |
michael@0 | 205 | stmt.finalize(); |
michael@0 | 206 | do_check_attribute_count(store.getAllIDs(), 4); |
michael@0 | 207 | |
michael@0 | 208 | _("Make sure we report records with invalid URIs."); |
michael@0 | 209 | let invalid_uri_guid = Utils.makeGUID(); |
michael@0 | 210 | let failed = store.applyIncomingBatch([{ |
michael@0 | 211 | id: invalid_uri_guid, |
michael@0 | 212 | histUri: ":::::::::::::::", |
michael@0 | 213 | title: "Doesn't have a valid URI", |
michael@0 | 214 | visits: [{date: TIMESTAMP3, |
michael@0 | 215 | type: Ci.nsINavHistoryService.TRANSITION_EMBED}]} |
michael@0 | 216 | ]); |
michael@0 | 217 | do_check_eq(failed.length, 1); |
michael@0 | 218 | do_check_eq(failed[0], invalid_uri_guid); |
michael@0 | 219 | |
michael@0 | 220 | _("Make sure we handle records with invalid GUIDs gracefully (ignore)."); |
michael@0 | 221 | applyEnsureNoFailures([ |
michael@0 | 222 | {id: "invalid", |
michael@0 | 223 | histUri: "http://invalid.guid/", |
michael@0 | 224 | title: "Doesn't have a valid GUID", |
michael@0 | 225 | visits: [{date: TIMESTAMP3, |
michael@0 | 226 | type: Ci.nsINavHistoryService.TRANSITION_EMBED}]} |
michael@0 | 227 | ]); |
michael@0 | 228 | |
michael@0 | 229 | _("Make sure we report records with invalid visits, gracefully handle non-integer dates."); |
michael@0 | 230 | let no_date_visit_guid = Utils.makeGUID(); |
michael@0 | 231 | let no_type_visit_guid = Utils.makeGUID(); |
michael@0 | 232 | let invalid_type_visit_guid = Utils.makeGUID(); |
michael@0 | 233 | let non_integer_visit_guid = Utils.makeGUID(); |
michael@0 | 234 | failed = store.applyIncomingBatch([ |
michael@0 | 235 | {id: no_date_visit_guid, |
michael@0 | 236 | histUri: "http://no.date.visit/", |
michael@0 | 237 | title: "Visit has no date", |
michael@0 | 238 | visits: [{date: TIMESTAMP3}]}, |
michael@0 | 239 | {id: no_type_visit_guid, |
michael@0 | 240 | histUri: "http://no.type.visit/", |
michael@0 | 241 | title: "Visit has no type", |
michael@0 | 242 | visits: [{type: Ci.nsINavHistoryService.TRANSITION_EMBED}]}, |
michael@0 | 243 | {id: invalid_type_visit_guid, |
michael@0 | 244 | histUri: "http://invalid.type.visit/", |
michael@0 | 245 | title: "Visit has invalid type", |
michael@0 | 246 | visits: [{date: TIMESTAMP3, |
michael@0 | 247 | type: Ci.nsINavHistoryService.TRANSITION_LINK - 1}]}, |
michael@0 | 248 | {id: non_integer_visit_guid, |
michael@0 | 249 | histUri: "http://non.integer.visit/", |
michael@0 | 250 | title: "Visit has non-integer date", |
michael@0 | 251 | visits: [{date: 1234.567, |
michael@0 | 252 | type: Ci.nsINavHistoryService.TRANSITION_EMBED}]} |
michael@0 | 253 | ]); |
michael@0 | 254 | do_check_eq(failed.length, 3); |
michael@0 | 255 | failed.sort(); |
michael@0 | 256 | let expected = [no_date_visit_guid, |
michael@0 | 257 | no_type_visit_guid, |
michael@0 | 258 | invalid_type_visit_guid].sort(); |
michael@0 | 259 | for (let i = 0; i < expected.length; i++) { |
michael@0 | 260 | do_check_eq(failed[i], expected[i]); |
michael@0 | 261 | } |
michael@0 | 262 | |
michael@0 | 263 | _("Make sure we handle records with javascript: URLs gracefully."); |
michael@0 | 264 | applyEnsureNoFailures([ |
michael@0 | 265 | {id: Utils.makeGUID(), |
michael@0 | 266 | histUri: "javascript:''", |
michael@0 | 267 | title: "javascript:''", |
michael@0 | 268 | visits: [{date: TIMESTAMP3, |
michael@0 | 269 | type: Ci.nsINavHistoryService.TRANSITION_EMBED}]} |
michael@0 | 270 | ]); |
michael@0 | 271 | |
michael@0 | 272 | _("Make sure we handle records without any visits gracefully."); |
michael@0 | 273 | applyEnsureNoFailures([ |
michael@0 | 274 | {id: Utils.makeGUID(), |
michael@0 | 275 | histUri: "http://getfirebug.com", |
michael@0 | 276 | title: "Get Firebug!", |
michael@0 | 277 | visits: []} |
michael@0 | 278 | ]); |
michael@0 | 279 | |
michael@0 | 280 | run_next_test(); |
michael@0 | 281 | }); |
michael@0 | 282 | |
michael@0 | 283 | add_test(function test_remove() { |
michael@0 | 284 | _("Remove an existent record and a non-existent from the store."); |
michael@0 | 285 | applyEnsureNoFailures([{id: fxguid, deleted: true}, |
michael@0 | 286 | {id: Utils.makeGUID(), deleted: true}]); |
michael@0 | 287 | do_check_false(store.itemExists(fxguid)); |
michael@0 | 288 | let queryres = queryHistoryVisits(fxuri); |
michael@0 | 289 | do_check_eq(queryres.length, 0); |
michael@0 | 290 | |
michael@0 | 291 | _("Make sure wipe works."); |
michael@0 | 292 | store.wipe(); |
michael@0 | 293 | do_check_empty(store.getAllIDs()); |
michael@0 | 294 | queryres = queryHistoryVisits(fxuri); |
michael@0 | 295 | do_check_eq(queryres.length, 0); |
michael@0 | 296 | queryres = queryHistoryVisits(tburi); |
michael@0 | 297 | do_check_eq(queryres.length, 0); |
michael@0 | 298 | run_next_test(); |
michael@0 | 299 | }); |
michael@0 | 300 | |
michael@0 | 301 | add_test(function cleanup() { |
michael@0 | 302 | _("Clean up."); |
michael@0 | 303 | PlacesUtils.history.removeAllPages(); |
michael@0 | 304 | run_next_test(); |
michael@0 | 305 | }); |