Bug 542941 - Part4 and 5: fix a sessionID bug, and introduce a new test for redirects, r=dietrich

This commit is contained in:
Marco Bonardo 2010-03-10 13:40:37 +01:00
Родитель 236497986e
Коммит 702b498222
4 изменённых файлов: 598 добавлений и 15 удалений

Просмотреть файл

@ -2780,13 +2780,17 @@ nsNavHistory::AddVisit(nsIURI* aURI, PRTime aTime, nsIURI* aReferringURI,
NS_ENSURE_SUCCESS(rv, rv);
}
// Get the place id for the referrer, if it exists.
// Get the visit id for the referrer, if it exists.
PRInt64 referringVisitID = 0;
PRInt64 referringSessionID;
PRTime referringTime;
PRBool referrerIsSame;
if (aReferringURI &&
NS_SUCCEEDED(aReferringURI->Equals(aURI, &referrerIsSame)) &&
!referrerIsSame &&
!FindLastVisit(aReferringURI, &referringVisitID, &referringTime, &referringSessionID)) {
// Add the referrer
// The referrer is not in the database and is not the same as aURI, so it
// must be added.
rv = AddVisit(aReferringURI, aTime - 1, nsnull, TRANSITION_LINK, PR_FALSE,
aSessionID, &referringVisitID);
if (NS_FAILED(rv))
@ -5153,7 +5157,8 @@ nsNavHistory::AddURIInternal(nsIURI* aURI, PRTime aTime, PRBool aRedirect,
mozStorageTransaction transaction(mDBConn, PR_FALSE);
PRInt64 redirectBookmark = 0;
PRInt64 visitID, sessionID;
PRInt64 visitID = 0;
PRInt64 sessionID = 0;
nsresult rv = AddVisitChain(aURI, aTime, aToplevel, aRedirect, aReferrer,
&visitID, &sessionID, &redirectBookmark);
NS_ENSURE_SUCCESS(rv, rv);
@ -5236,6 +5241,12 @@ nsNavHistory::AddVisitChain(nsIURI* aURI,
rv = NS_NewURI(getter_AddRefs(redirectSourceURI), redirectSourceUrl);
NS_ENSURE_SUCCESS(rv, rv);
// Don't add a new visit if a page redirects to itself.
PRBool redirectIsSame;
if (NS_SUCCEEDED(aURI->Equals(redirectSourceURI, &redirectIsSame)) &&
redirectIsSame)
return NS_OK;
// remember if any redirect sources were bookmarked
nsNavBookmarks *bookmarkService = nsNavBookmarks::GetBookmarksService();
PRBool isBookmarked;
@ -5276,12 +5287,31 @@ nsNavHistory::AddVisitChain(nsIURI* aURI,
else if (aReferrerURI) {
// This page does not come from a redirect and had a referrer.
// Check if the referrer has a previous visit.
PRTime lastVisitTime;
PRInt64 referringVisitId;
PRBool referrerHasPreviousVisit =
FindLastVisit(aReferrerURI, &referringVisitId, &lastVisitTime, aSessionID);
// Don't add a new visit if the referring site is the same as
// the new site. This happens when a page refreshes itself.
// Otherwise, if the page has never been added, the visit should be
// registered regardless.
PRBool referrerIsSame;
if (NS_SUCCEEDED(aURI->Equals(aReferrerURI, &referrerIsSame)) &&
referrerIsSame)
referrerIsSame && referrerHasPreviousVisit) {
// Ensure a valid session id to the chain.
if (aIsRedirect)
*aSessionID = GetNewSessionID();
return NS_OK;
}
if (!referrerHasPreviousVisit ||
aTime - lastVisitTime > RECENT_EVENT_THRESHOLD) {
// Either the referrer has no visits or the last visit is too
// old to be part of this session. Thus start a new session.
*aSessionID = GetNewSessionID();
}
// Since referrer is set, this visit comes from an originating page.
// For top-level windows, visit is considered user-initiated and it should
@ -5294,17 +5324,6 @@ nsNavHistory::AddVisitChain(nsIURI* aURI,
transitionType = nsINavHistoryService::TRANSITION_FRAMED_LINK;
else
transitionType = nsINavHistoryService::TRANSITION_LINK;
// Check if the referrer has a visit,
// This also populates the session id.
PRTime lastVisitTime;
PRInt64 referringVisitId;
if (!FindLastVisit(aReferrerURI, &referringVisitId, &lastVisitTime, aSessionID) ||
aTime - lastVisitTime > RECENT_EVENT_THRESHOLD) {
// Either referrer does not have any visit, or that visit is too
// old to be part of this session. Start a new session then.
*aSessionID = GetNewSessionID();
}
}
else {
// When there is no referrer:

Просмотреть файл

@ -53,6 +53,7 @@ XPCSHELL_TESTS = \
bookmarks \
queries \
unit \
network \
$(NULL)
# Simple MochiTests

Просмотреть файл

@ -0,0 +1,328 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Places.
*
* The Initial Developer of the Original Code is
* Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2005
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Brian Ryner <bryner@brianryner.com>
* Dietrich Ayala <dietrich@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
const NS_APP_USER_PROFILE_50_DIR = "ProfD";
const NS_APP_HISTORY_50_FILE = "UHist";
// This will also define Cc, Ci.
do_load_httpd_js();
function LOG(aMsg) {
aMsg = ("*** PLACES TESTS: " + aMsg);
Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).
logStringMessage(aMsg);
print(aMsg);
}
do_get_profile();
var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
var provider = {
getFile: function(prop, persistent) {
persistent.value = true;
if (prop == NS_APP_HISTORY_50_FILE) {
var histFile = dirSvc.get("ProfD", Ci.nsIFile);
histFile.append("history.dat");
return histFile;
}
throw Cr.NS_ERROR_FAILURE;
},
QueryInterface: function(iid) {
if (iid.equals(Ci.nsIDirectoryServiceProvider) ||
iid.equals(Ci.nsISupports)) {
return this;
}
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
dirSvc.QueryInterface(Ci.nsIDirectoryService).registerProvider(provider);
var iosvc = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
function uri(spec) {
return iosvc.newURI(spec, null, null);
}
/*
* Reads the data from the specified nsIFile, and returns an array of bytes.
*/
function readFileData(aFile) {
var inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
// init the stream as RD_ONLY, -1 == default permissions.
inputStream.init(aFile, 0x01, -1, null);
var size = inputStream.available();
// use a binary input stream to grab the bytes.
var bis = Cc["@mozilla.org/binaryinputstream;1"].
createInstance(Ci.nsIBinaryInputStream);
bis.setInputStream(inputStream);
var bytes = bis.readByteArray(size);
if (size != bytes.length)
throw "Didn't read expected number of bytes";
return bytes;
}
/*
* Compares two arrays, and returns true if they are equal.
*/
function compareArrays(aArray1, aArray2) {
if (aArray1.length != aArray2.length) {
print("compareArrays: array lengths differ\n");
return false;
}
for (var i = 0; i < aArray1.length; i++) {
if (aArray1[i] != aArray2[i]) {
print("compareArrays: arrays differ at index " + i + ": " +
"(" + aArray1[i] + ") != (" + aArray2[i] +")\n");
return false;
}
}
return true;
}
// Delete a previously created sqlite file
function clearDB() {
try {
var file = dirSvc.get('ProfD', Ci.nsIFile);
file.append("places.sqlite");
if (file.exists())
file.remove(false);
} catch(ex) { dump("Exception: " + ex); }
}
clearDB();
/**
* Dumps the rows of a table out to the console.
*
* @param aName
* The name of the table or view to output.
*/
function dump_table(aName)
{
let db = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsPIPlacesDatabase).
DBConnection;
let stmt = db.createStatement("SELECT * FROM " + aName);
dump("\n*** Printing data from " + aName + ":\n");
let count = 0;
while (stmt.executeStep()) {
let columns = stmt.numEntries;
if (count == 0) {
// print the column names
for (let i = 0; i < columns; i++)
dump(stmt.getColumnName(i) + "\t");
dump("\n");
}
// print the row
for (let i = 0; i < columns; i++) {
switch (stmt.getTypeOfIndex(i)) {
case Ci.mozIStorageValueArray.VALUE_TYPE_NULL:
dump("NULL\t");
break;
case Ci.mozIStorageValueArray.VALUE_TYPE_INTEGER:
dump(stmt.getInt64(i) + "\t");
break;
case Ci.mozIStorageValueArray.VALUE_TYPE_FLOAT:
dump(stmt.getDouble(i) + "\t");
break;
case Ci.mozIStorageValueArray.VALUE_TYPE_TEXT:
dump(stmt.getString(i) + "\t");
break;
}
}
dump("\n");
count++;
}
dump("*** There were a total of " + count + " rows of data.\n\n");
stmt.reset();
stmt.finalize();
stmt = null;
}
/*
* Removes all bookmarks and checks for correct cleanup
*/
function remove_all_bookmarks() {
var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
getService(Ci.nsINavBookmarksService);
// Clear all bookmarks
bs.removeFolderChildren(bs.bookmarksMenuFolder);
bs.removeFolderChildren(bs.toolbarFolder);
bs.removeFolderChildren(bs.unfiledBookmarksFolder);
// Check for correct cleanup
check_no_bookmarks()
}
/*
* Checks that we don't have any bookmark
*/
function check_no_bookmarks() {
var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
getService(Ci.nsINavBookmarksService);
var query = hs.getNewQuery();
query.setFolders([bs.toolbarFolder, bs.bookmarksMenuFolder, bs.unfiledBookmarksFolder], 3);
var options = hs.getNewQueryOptions();
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
var result = hs.executeQuery(query, options);
var root = result.root;
root.containerOpen = true;
do_check_eq(root.childCount, 0);
root.containerOpen = false;
}
/**
* Function gets current database connection, if the connection has been closed
* it will try to reconnect to the places.sqlite database.
*/
function DBConn()
{
let db = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsPIPlacesDatabase).
DBConnection;
if (db.connectionReady)
return db;
// open a new connection if needed
let file = dirSvc.get('ProfD', Ci.nsIFile);
file.append("places.sqlite");
let storageService = Cc["@mozilla.org/storage/service;1"].
getService(Ci.mozIStorageService);
try {
var dbConn = storageService.openDatabase(file);
} catch (ex) {
return null;
}
return dbConn;
}
/**
* Sets title synchronously for a page in moz_places synchronously.
* History.SetPageTitle uses LAZY_ADD so we can't rely on it.
*
* @param aURI
* An nsIURI to set the title for.
* @param aTitle
* The title to set the page to.
* @throws if the page is not found in the database.
*
* @note this function only exists because we have no API to do this. It should
* be added in bug 421897.
*/
function setPageTitle(aURI, aTitle) {
let dbConn = DBConn();
// Check that the page exists.
let stmt = dbConn.createStatement(
"SELECT id FROM moz_places_view WHERE url = :url");
stmt.params.url = aURI.spec;
try {
if (!stmt.executeStep()) {
do_throw("Unable to find page " + aURIString);
return;
}
}
finally {
stmt.finalize();
}
// Update the title
stmt = dbConn.createStatement(
"UPDATE moz_places_view SET title = :title WHERE url = :url");
stmt.params.title = aTitle;
stmt.params.url = aURI.spec;
try {
stmt.execute();
}
finally {
stmt.finalize();
}
}
/**
* Flushes any events in the event loop of the main thread.
*/
function flush_main_thread_events()
{
let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
while (tm.mainThread.hasPendingEvents())
tm.mainThread.processNextEvent(false);
}
/**
* Clears history invoking callback when done.
*/
function waitForClearHistory(aCallback) {
const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
let os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
let observer = {
observe: function(aSubject, aTopic, aData) {
os.removeObserver(this, TOPIC_EXPIRATION_FINISHED);
aCallback();
}
};
os.addObserver(observer, TOPIC_EXPIRATION_FINISHED, false);
let hs = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
hs.QueryInterface(Ci.nsIBrowserHistory).removeAllPages();
}
// These tests are known to randomly fail due to bug 507790 when database
// flushes are active, so we turn off syncing for them.
let randomFailingSyncTests = [
];
let currentTestFilename = do_get_file(_TEST_FILE[0], true).leafName;
if (randomFailingSyncTests.indexOf(currentTestFilename) != -1) {
print("Test " + currentTestFilename + " is known random due to bug 507790, disabling PlacesDBFlush component.");
let sync = Cc["@mozilla.org/places/sync;1"].getService(Ci.nsIObserver);
sync.observe(null, "places-debug-stop-sync", null);
}

Просмотреть файл

@ -0,0 +1,235 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Places unit test code.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Marco Bonardo <mak77@bonardo.net> (Original Author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* Tests history redirects handling */
let hs = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
let bh = hs.QueryInterface(Ci.nsIBrowserHistory);
let ghist3 = hs.QueryInterface(Ci.nsIGlobalHistory3);
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/NetUtil.jsm");
const PERMA_REDIR_PATH = "/permaredir";
const TEMP_REDIR_PATH = "/tempredir";
const FOUND_PATH = "/found";
const HTTPSVR = new nsHttpServer();
const PORT = 4444;
HTTPSVR.registerPathHandler(PERMA_REDIR_PATH, permaRedirHandler);
HTTPSVR.registerPathHandler(TEMP_REDIR_PATH, tempRedirHandler);
HTTPSVR.registerPathHandler(FOUND_PATH, foundHandler);
const STATUS = {
REDIRECT_PERMANENT: [301, "Moved Permanently"],
REDIRECT_TEMPORARY: [302, "Moved"],
FOUND: [200, "Found"],
}
const PERMA_REDIR_URL = "http://localhost:" + PORT + PERMA_REDIR_PATH;
const TEMP_REDIR_URL = "http://localhost:" + PORT + TEMP_REDIR_PATH;
const FOUND_URL = "http://localhost:" + PORT + FOUND_PATH;
// PERMANENT REDIRECT
function permaRedirHandler(aMeta, aResponse) {
// Redirect permanently to TEMP_REDIR_URL
PathHandler(aMeta, aResponse, "REDIRECT_PERMANENT", TEMP_REDIR_URL);
}
// TEMPORARY REDIRECT
function tempRedirHandler(aMeta, aResponse) {
// Redirect temporarily to FOUND_URL
PathHandler(aMeta, aResponse, "REDIRECT_TEMPORARY", FOUND_URL);
}
// FOUND
function foundHandler(aMeta, aResponse) {
PathHandler(aMeta, aResponse, "FOUND");
}
function PathHandler(aMeta, aResponse, aChannelEvent, aRedirURL) {
aResponse.setStatusLine(aMeta.httpVersion,
STATUS[aChannelEvent][0], // Code
STATUS[aChannelEvent][1]); // Text
if (aRedirURL)
aResponse.setHeader("Location", aRedirURL, false);
//aResponse.setHeader("Content-Type", "text/html", false);
let body = STATUS[aChannelEvent][1] + "\r\n";
aResponse.bodyOutputStream.write(body, body.length);
}
function run_test() {
do_test_pending();
HTTPSVR.start(PORT);
var chan = NetUtil.ioService
.newChannelFromURI(uri("http://localhost:4444/permaredir"));
var listener = new ChannelListener();
chan.notificationCallbacks = listener;
chan.asyncOpen(listener, null);
// The test will continue on onStopRequest.
}
function continue_test() {
let dbConn = DBConn();
let stmt = dbConn.createStatement(
"SELECT v.id, h.url, v.from_visit, v.visit_date, v.visit_type, v.session " +
"FROM moz_historyvisits_view v " +
"JOIN moz_places_view h on h.id = v.place_id " +
"ORDER BY v.id ASC");
const EXPECTED = [
{ id: 1,
url: PERMA_REDIR_URL,
from_visit: 0,
visit_type: Ci.nsINavHistoryService.TRANSITION_LINK,
session: 1 },
{ id: 2,
url: TEMP_REDIR_URL,
from_visit: 1,
visit_type: Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT,
session: 1 },
{ id: 3,
url: FOUND_URL,
from_visit: 2,
visit_type: Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY,
session: 1 },
];
try {
while(stmt.executeStep()) {
let comparator = EXPECTED.shift();
do_check_eq(stmt.row.id, comparator.id);
do_check_eq(stmt.row.url, comparator.url);
do_check_eq(stmt.row.from_visit, comparator.from_visit);
do_check_eq(stmt.row.visit_type, comparator.visit_type);
do_check_eq(stmt.row.session, comparator.session);
}
}
finally {
stmt.finalize();
}
HTTPSVR.stop(do_test_finished);
}
/**
* Read count bytes from stream and return as a String object
*/
function read_stream(stream, count) {
/* assume stream has non-ASCII data */
var wrapper =
Components.classes["@mozilla.org/binaryinputstream;1"]
.createInstance(Components.interfaces.nsIBinaryInputStream);
wrapper.setInputStream(stream);
/* JS methods can be called with a maximum of 65535 arguments, and input
streams don't have to return all the data they make .available() when
asked to .read() that number of bytes. */
var data = [];
while (count > 0) {
var bytes = wrapper.readByteArray(Math.min(65535, count));
data.push(String.fromCharCode.apply(null, bytes));
count -= bytes.length;
if (bytes.length == 0)
do_throw("Nothing read from input stream!");
}
return data.join('');
}
function ChannelListener() {
}
ChannelListener.prototype = {
_buffer: "",
_got_onstartrequest: false,
_got_onchannelredirect: false,
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIStreamListener,
Ci.nsIRequestObserver,
Ci.nsIInterfaceRequestor,
Ci.nsIChannelEventSink,
]),
// nsIInterfaceRequestor
getInterface: function (aIID) {
try {
return this.QueryInterface(aIID);
} catch (e) {
throw Components.results.NS_NOINTERFACE;
}
},
onStartRequest: function(request, context) {
print("onStartRequest");
this._got_onstartrequest = true;
},
onDataAvailable: function(request, context, stream, offset, count) {
this._buffer = this._buffer.concat(read_stream(stream, count));
},
onStopRequest: function(request, context, status) {
print("onStopRequest");
this._got_onstoprequest++;
let success = Components.isSuccessCode(status);
do_check_true(success);
do_check_true(this._got_onstartrequest);
do_check_true(this._got_onchannelredirect);
do_check_true(this._buffer.length > 0);
// The referrer is wrong since it's the first element in the redirects
// chain, but this is good, since it will test a special path.
ghist3.addURI(uri(FOUND_URL), false, true, uri(PERMA_REDIR_URL));
// This forces a CommitLazyMessages, so we don't have to wait for LAZY_ADD.
// Actually trying to delete visits in future.
hs.removeVisitsByTimeframe((Date.now() * 1000) + 1, (Date.now() * 1000) + 2);
continue_test();
},
// nsIChannelEventSink
onChannelRedirect: function (aOldChannel, aNewChannel, aFlags) {
print("onChannelRedirect");
this._got_onchannelredirect = true;
ghist3.addDocumentRedirect(aOldChannel, aNewChannel, aFlags, true);
},
};