Merge m-c to s-c.

This commit is contained in:
Richard Newman 2011-12-13 12:10:12 -08:00
commit fb25194f74
16 changed files with 622 additions and 206 deletions

View File

@ -52,10 +52,10 @@ TEST_FILES = \
exceptions_in_success_events_iframe.html \
helpers.js \
leaving_page_iframe.html \
test_add_put.html \
test_add_twice_failure.html \
test_advance.html \
test_autoIncrement_indexes.html \
test_bad_keypath.html \
test_bfcache.html \
test_clear.html \
test_cmp.html \

View File

@ -0,0 +1,180 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html>
<head>
<title>Indexed Database Property Test</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="text/javascript;version=1.7">
function testSteps()
{
const name = window.location.pathname;
let openRequest = mozIndexedDB.open(name, 1);
openRequest.onerror = errorHandler;
openRequest.onupgradeneeded = grabEventAndContinueHandler;
openRequest.onsuccess = unexpectedSuccessHandler;
let event = yield;
let db = event.target.result;
let trans = event.target.transaction;
for each (let autoincrement in [true, false]) {
for each (let keypath in [false, true, "missing", "invalid"]) {
for each (let method in ["put", "add"]) {
for each (let explicit in [true, false, undefined, "invalid"]) {
for each (let existing in [true, false]) {
let speccedNoKey = (keypath == false || keypath == "missing") &&
!explicit;
// We can't do 'existing' checks if we use autogenerated key
if (speccedNoKey && autoincrement && existing) {
continue;
}
// Create store
if (db.objectStoreNames.contains("mystore"))
db.deleteObjectStore("mystore");
let store = db.createObjectStore("mystore",
{ autoIncrement: autoincrement,
keyPath: (keypath ? "id" : null) });
test = " for test " + JSON.stringify({ autoincrement: autoincrement,
keypath: keypath,
method: method,
explicit: explicit === undefined ? "undefined" : explicit,
existing: existing });
// Insert "existing" data if needed
if (existing) {
if (keypath)
store.add({ existing: "data", id: 5 }).onsuccess = grabEventAndContinueHandler;
else
store.add({ existing: "data" }, 5).onsuccess = grabEventAndContinueHandler;
let e = yield;
is(e.type, "success", "success inserting existing" + test);
is(e.target.result, 5, "inserted correct key" + test);
}
// Set up value to be inserted
let value = { theObj: true };
if (keypath === true) {
value.id = 5;
}
else if (keypath === "invalid") {
value.id = /x/;
}
// Which arguments are passed to function
args = [value];
if (explicit === true) {
args.push(5);
}
else if (explicit === undefined) {
args.push(undefined);
}
else if (explicit === "invalid") {
args.push(/x/);
}
let expected = expectedResult(method, keypath, explicit, autoincrement, existing);
ok(true, "making call" + test);
// Make function call for throwing functions
if (expected === "throw") {
try {
store[method].apply(store, args);
ok(false, "should have thrown" + test);
}
catch (ex) {
ok(true, "did throw" + test);
ok(ex instanceof IDBDatabaseException, "Got a IDBDatabaseException" + test);
is(ex.code, IDBDatabaseException.DATA_ERR, "expect a DATA_ERR" + test);
}
continue;
}
// Make non-throwing function call
let req = store[method].apply(store, args);
req.onsuccess = req.onerror = grabEventAndContinueHandler
let e = yield;
// Figure out what key we used
let key = 5;
if (autoincrement && speccedNoKey) {
key = 1;
}
// Adjust value if expected
if (autoincrement && keypath && speccedNoKey) {
value.id = key;
}
// Check result
if (expected === "error") {
is(e.type, "error", "write should fail" + test);
e.preventDefault();
e.stopPropagation();
continue;
}
is(e.type, "success", "write should succeed" + test);
if (autoincrement && speccedNoKey) {
todo_is(e.target.result, key, "(fix ai) write should return correct key" + test);
key = e.target.result;
if (keypath) {
value.id = key;
}
}
else {
is(e.target.result, key, "write should return correct key" + test);
}
store.get(key).onsuccess = grabEventAndContinueHandler;
e = yield;
is(e.type, "success", "read back should succeed" + test);
is(JSON.stringify(e.target.result),
JSON.stringify(value),
"read back should return correct value" + test);
}
}
}
}
}
function expectedResult(method, keypath, explicit, autoincrement, existing) {
if (keypath && explicit)
return "throw";
if (!keypath && !explicit && !autoincrement)
return "throw";
if (keypath == "invalid")
return "throw";
if (keypath == "missing" && !autoincrement)
return "throw";
if (explicit == "invalid")
return "throw";
if (method == "add" && existing)
return "error";
return "success";
}
openRequest.onsuccess = grabEventAndContinueHandler;
yield;
finishTest();
yield;
}
</script>
<script type="text/javascript;version=1.7" src="helpers.js"></script>
</head>
<body onload="runTest();"></body>
</html>

View File

@ -1,49 +0,0 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html>
<head>
<title>Indexed Database Property Test</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="text/javascript;version=1.7">
function testSteps()
{
const name = window.location.pathname;
const description = "My Test Database";
let request = mozIndexedDB.open(name, 1, description);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
let event = yield;
let db = request.result;
let objectStore = db.createObjectStore("foo", { keyPath: "keyPath" });
request = objectStore.add({keyPath:"foo"});
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
event = yield;
try {
request = objectStore.add({});
ok(false, "Shouldn't get here!");
}
catch (e) {
is(e.code, IDBDatabaseException.DATA_ERR, "Good error");
}
finishTest();
yield;
}
</script>
<script type="text/javascript;version=1.7" src="helpers.js"></script>
</head>
<body onload="runTest();"></body>
</html>

View File

@ -93,6 +93,13 @@ public class BrowserContract {
public static final class Bookmarks implements CommonColumns, URLColumns, ImageColumns, SyncColumns {
private Bookmarks() {}
public static final String MOBILE_FOLDER_GUID = "mobile";
public static final String PLACES_FOLDER_GUID = "places";
public static final String MENU_FOLDER_GUID = "menu";
public static final String TAGS_FOLDER_GUID = "tags";
public static final String TOOLBAR_FOLDER_GUID = "toolbar";
public static final String UNFILED_FOLDER_GUID = "unfiled";
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "bookmarks");
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/bookmark";

View File

@ -83,6 +83,9 @@ public class BrowserProvider extends ContentProvider {
static final String TABLE_HISTORY = "history";
static final String TABLE_IMAGES = "images";
static final String VIEW_BOOKMARKS_WITH_IMAGES = "bookmarks_with_images";
static final String VIEW_HISTORY_WITH_IMAGES = "history_with_images";
// Bookmark matches
static final int BOOKMARKS = 100;
static final int BOOKMARKS_ID = 101;
@ -106,12 +109,20 @@ public class BrowserProvider extends ContentProvider {
static final String DEFAULT_HISTORY_SORT_ORDER = History.DATE_LAST_VISITED + " DESC";
static final String TABLE_BOOKMARKS_JOIN_IMAGES = TABLE_BOOKMARKS + " LEFT OUTER JOIN " +
TABLE_IMAGES + " ON " + qualifyColumnValue(TABLE_BOOKMARKS, Bookmarks.URL) +
" = " + qualifyColumnValue(TABLE_IMAGES, Images.URL);
"(SELECT " + Images.URL + ", " + Images.FAVICON + ", " + Images.THUMBNAIL + " FROM " +
TABLE_IMAGES + ", " + TABLE_BOOKMARKS + " WHERE " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " +
qualifyColumn(TABLE_IMAGES, Images.URL) + ") AS bookmark_images ON " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " +
qualifyColumn("bookmark_images", Images.URL);
static final String TABLE_HISTORY_JOIN_IMAGES = TABLE_HISTORY + " LEFT OUTER JOIN " +
TABLE_IMAGES + " ON " + qualifyColumnValue(TABLE_HISTORY, History.URL) +
" = " + qualifyColumnValue(TABLE_IMAGES, Images.URL);
"(SELECT " + Images.URL + ", " + Images.FAVICON + ", " + Images.THUMBNAIL + " FROM " +
TABLE_IMAGES + ", " + TABLE_HISTORY + " WHERE " +
qualifyColumn(TABLE_HISTORY, History.URL) + " = " +
qualifyColumn(TABLE_IMAGES, Images.URL) + ") AS history_images ON " +
qualifyColumn(TABLE_HISTORY, History.URL) + " = " +
qualifyColumn("history_images", Images.URL);
static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
@ -131,7 +142,7 @@ public class BrowserProvider extends ContentProvider {
URI_MATCHER.addURI(BrowserContract.AUTHORITY, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID);
map = BOOKMARKS_PROJECTION_MAP;
map.put(Bookmarks._ID, qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID));
map.put(Bookmarks._ID, Bookmarks._ID);
map.put(Bookmarks.TITLE, Bookmarks.TITLE);
map.put(Bookmarks.URL, Bookmarks.URL);
map.put(Bookmarks.FAVICON, Bookmarks.FAVICON);
@ -142,41 +153,41 @@ public class BrowserProvider extends ContentProvider {
map.put(Bookmarks.TAGS, Bookmarks.TAGS);
map.put(Bookmarks.DESCRIPTION, Bookmarks.DESCRIPTION);
map.put(Bookmarks.KEYWORD, Bookmarks.KEYWORD);
map.put(Bookmarks.DATE_CREATED, qualifyColumn(TABLE_BOOKMARKS, Bookmarks.DATE_CREATED));
map.put(Bookmarks.DATE_MODIFIED, qualifyColumn(TABLE_BOOKMARKS, Bookmarks.DATE_MODIFIED));
map.put(Bookmarks.GUID, qualifyColumn(TABLE_BOOKMARKS, Bookmarks.GUID));
map.put(Bookmarks.IS_DELETED, qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED));
map.put(Bookmarks.DATE_CREATED, Bookmarks.DATE_CREATED);
map.put(Bookmarks.DATE_MODIFIED, Bookmarks.DATE_MODIFIED);
map.put(Bookmarks.GUID, Bookmarks.GUID);
map.put(Bookmarks.IS_DELETED, Bookmarks.IS_DELETED);
// History
URI_MATCHER.addURI(BrowserContract.AUTHORITY, "history", HISTORY);
URI_MATCHER.addURI(BrowserContract.AUTHORITY, "history/#", HISTORY_ID);
map = HISTORY_PROJECTION_MAP;
map.put(History._ID, qualifyColumn(TABLE_HISTORY, History._ID));
map.put(History._ID, History._ID);
map.put(History.TITLE, History.TITLE);
map.put(History.URL, History.URL);
map.put(History.FAVICON, History.FAVICON);
map.put(History.THUMBNAIL, History.THUMBNAIL);
map.put(History.VISITS, History.VISITS);
map.put(History.DATE_LAST_VISITED, History.DATE_LAST_VISITED);
map.put(History.DATE_CREATED, qualifyColumn(TABLE_HISTORY, History.DATE_CREATED));
map.put(History.DATE_MODIFIED, qualifyColumn(TABLE_HISTORY, History.DATE_MODIFIED));
map.put(History.GUID, qualifyColumn(TABLE_HISTORY, History.GUID));
map.put(History.IS_DELETED, qualifyColumn(TABLE_HISTORY, History.IS_DELETED));
map.put(History.DATE_CREATED, History.DATE_CREATED);
map.put(History.DATE_MODIFIED, History.DATE_MODIFIED);
map.put(History.GUID, History.GUID);
map.put(History.IS_DELETED, History.IS_DELETED);
// Images
URI_MATCHER.addURI(BrowserContract.AUTHORITY, "images", IMAGES);
map = IMAGES_PROJECTION_MAP;
map.put(Images._ID, qualifyColumn(TABLE_IMAGES, Images._ID));
map.put(Images._ID, Images._ID);
map.put(Images.URL, Images.URL);
map.put(Images.FAVICON, Images.FAVICON);
map.put(Images.FAVICON_URL, Images.FAVICON_URL);
map.put(Images.THUMBNAIL, Images.THUMBNAIL);
map.put(Images.DATE_CREATED, qualifyColumn(TABLE_IMAGES, Images.DATE_CREATED));
map.put(Images.DATE_MODIFIED, qualifyColumn(TABLE_IMAGES, Images.DATE_MODIFIED));
map.put(Images.GUID, qualifyColumn(TABLE_IMAGES, Images.GUID));
map.put(Images.IS_DELETED, qualifyColumn(TABLE_IMAGES, Images.IS_DELETED));
map.put(Images.DATE_CREATED, Images.DATE_CREATED);
map.put(Images.DATE_MODIFIED, Images.DATE_MODIFIED);
map.put(Images.GUID, Images.GUID);
map.put(Images.IS_DELETED, Images.IS_DELETED);
// Schema
URI_MATCHER.addURI(BrowserContract.AUTHORITY, "schema", SCHEMA);
@ -186,10 +197,6 @@ public class BrowserProvider extends ContentProvider {
}
static final String qualifyColumn(String table, String column) {
return table + "." + column + " AS " + column;
}
static final String qualifyColumnValue(String table, String column) {
return table + "." + column;
}
@ -239,6 +246,16 @@ public class BrowserProvider extends ContentProvider {
return result;
}
private static boolean hasImagesInProjection(String[] projection) {
for (int i = 0; i < projection.length; ++i) {
if (projection[i].equals(Images.FAVICON) ||
projection[i].equals(Images.THUMBNAIL))
return true;
}
return false;
}
final class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context, String databasePath) {
super(context, databasePath, null, DATABASE_VERSION);
@ -314,9 +331,34 @@ public class BrowserProvider extends ContentProvider {
db.execSQL("CREATE INDEX images_modified_index ON " + TABLE_IMAGES + "("
+ Images.DATE_MODIFIED + ")");
db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_BOOKMARKS_WITH_IMAGES + " AS " +
"SELECT " + qualifyColumn(TABLE_BOOKMARKS, "*") +
", " + Images.FAVICON + ", " + Images.THUMBNAIL + " FROM " +
TABLE_BOOKMARKS_JOIN_IMAGES);
db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_HISTORY_WITH_IMAGES + " AS " +
"SELECT " + qualifyColumn(TABLE_HISTORY, "*") +
", " + Images.FAVICON + ", " + Images.THUMBNAIL + " FROM " +
TABLE_HISTORY_JOIN_IMAGES);
createMobileBookmarksFolder(db);
// FIXME: Create default bookmarks here
}
private void createMobileBookmarksFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues();
values.put(Bookmarks.GUID, Bookmarks.MOBILE_FOLDER_GUID);
values.put(Bookmarks.IS_FOLDER, 1);
values.put(Bookmarks.POSITION, 0);
long now = System.currentTimeMillis();
values.put(Bookmarks.DATE_CREATED, now);
values.put(Bookmarks.DATE_MODIFIED, now);
db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.GUID, values);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.d(LOGTAG, "Upgrading browser.db: " + db.getPath() + " from " +
@ -428,11 +470,8 @@ public class BrowserProvider extends ContentProvider {
try {
long now = System.currentTimeMillis();
String isDeletedColumn = qualifyColumnValue(tableName, SyncColumns.IS_DELETED);
String dateModifiedColumn = qualifyColumnValue(tableName, SyncColumns.DATE_MODIFIED);
String selection = isDeletedColumn + " = 1 AND " +
dateModifiedColumn + " <= " + (now - MAX_AGE_OF_DELETED_RECORDS);
String selection = SyncColumns.IS_DELETED + " = 1 AND " +
SyncColumns.DATE_MODIFIED + " <= " + (now - MAX_AGE_OF_DELETED_RECORDS);
cursor = query(uriWithArgs,
new String[] { CommonColumns._ID },
@ -801,22 +840,18 @@ public class BrowserProvider extends ContentProvider {
if (match == BOOKMARKS_ID) {
Log.d(LOGTAG, "Query is BOOKMARKS_ID: " + uri);
selection = concatenateWhere(selection,
qualifyColumnValue(TABLE_BOOKMARKS, Bookmarks._ID) + " = ?");
selection = concatenateWhere(selection, Bookmarks._ID + " = ?");
selectionArgs = appendSelectionArgs(selectionArgs,
new String[] { Long.toString(ContentUris.parseId(uri)) });
} else if (match == BOOKMARKS_FOLDER_ID) {
Log.d(LOGTAG, "Query is BOOKMARKS_FOLDER_ID: " + uri);
selection = concatenateWhere(selection,
qualifyColumnValue(TABLE_BOOKMARKS, Bookmarks.PARENT) + " = ?");
selection = concatenateWhere(selection, Bookmarks.PARENT + " = ?");
selectionArgs = appendSelectionArgs(selectionArgs,
new String[] { Long.toString(ContentUris.parseId(uri)) });
}
if (!shouldShowDeleted(uri)) {
String isDeletedColumn = qualifyColumnValue(TABLE_BOOKMARKS, Bookmarks.IS_DELETED);
selection = concatenateWhere(isDeletedColumn + " = 0", selection);
}
if (!shouldShowDeleted(uri))
selection = concatenateWhere(Bookmarks.IS_DELETED + " = 0", selection);
if (TextUtils.isEmpty(sortOrder)) {
Log.d(LOGTAG, "Using default sort order on query: " + uri);
@ -824,7 +859,11 @@ public class BrowserProvider extends ContentProvider {
}
qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
if (hasImagesInProjection(projection))
qb.setTables(VIEW_BOOKMARKS_WITH_IMAGES);
else
qb.setTables(TABLE_BOOKMARKS);
break;
}
@ -835,22 +874,23 @@ public class BrowserProvider extends ContentProvider {
if (match == HISTORY_ID) {
Log.d(LOGTAG, "Query is HISTORY_ID: " + uri);
selection = concatenateWhere(selection,
qualifyColumnValue(TABLE_HISTORY, History._ID) + " = ?");
selection = concatenateWhere(selection, History._ID + " = ?");
selectionArgs = appendSelectionArgs(selectionArgs,
new String[] { Long.toString(ContentUris.parseId(uri)) });
}
if (!shouldShowDeleted(uri)) {
String isDeletedColumn = qualifyColumnValue(TABLE_HISTORY, History.IS_DELETED);
selection = concatenateWhere(isDeletedColumn + " = 0", selection);
}
if (!shouldShowDeleted(uri))
selection = concatenateWhere(History.IS_DELETED + " = 0", selection);
if (TextUtils.isEmpty(sortOrder))
sortOrder = DEFAULT_HISTORY_SORT_ORDER;
qb.setProjectionMap(HISTORY_PROJECTION_MAP);
qb.setTables(TABLE_HISTORY_JOIN_IMAGES);
if (hasImagesInProjection(projection))
qb.setTables(VIEW_HISTORY_WITH_IMAGES);
else
qb.setTables(TABLE_HISTORY);
break;
}
@ -861,16 +901,13 @@ public class BrowserProvider extends ContentProvider {
if (match == IMAGES_ID) {
Log.d(LOGTAG, "Query is IMAGES_ID: " + uri);
selection = concatenateWhere(selection,
qualifyColumnValue(TABLE_IMAGES, Images._ID) + " = ?");
selection = concatenateWhere(selection, Images._ID + " = ?");
selectionArgs = appendSelectionArgs(selectionArgs,
new String[] { Long.toString(ContentUris.parseId(uri)) });
}
if (!shouldShowDeleted(uri)) {
String isDeletedColumn = qualifyColumnValue(TABLE_IMAGES, Images.IS_DELETED);
selection = concatenateWhere(isDeletedColumn + " = 0", selection);
}
if (!shouldShowDeleted(uri))
selection = concatenateWhere(Images.IS_DELETED + " = 0", selection);
qb.setProjectionMap(IMAGES_PROJECTION_MAP);
qb.setTables(TABLE_IMAGES);
@ -1173,10 +1210,10 @@ public class BrowserProvider extends ContentProvider {
String selection = Images.URL + " NOT IN (SELECT " + Bookmarks.URL +
" FROM " + TABLE_BOOKMARKS + " WHERE " + Bookmarks.URL + " IS NOT NULL AND " +
qualifyColumnValue(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " = 0) AND " +
qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " = 0) AND " +
Images.URL + " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY +
" WHERE " + History.URL + " IS NOT NULL AND " +
qualifyColumnValue(TABLE_HISTORY, History.IS_DELETED) + " = 0)";
qualifyColumn(TABLE_HISTORY, History.IS_DELETED) + " = 0)";
return deleteImages(uri, selection, null);
}

View File

@ -65,9 +65,11 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
public static final int TRUNCATE_N_OLDEST = 5;
private final String mProfile;
private long mMobileFolderId;
public LocalBrowserDB(String profile) {
mProfile = profile;
mMobileFolderId = -1;
}
private Uri appendProfileAndLimit(Uri uri, int limit) {
@ -233,10 +235,38 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return (count == 1);
}
private long getMobileBookmarksFolderId(ContentResolver cr) {
if (mMobileFolderId >= 0)
return mMobileFolderId;
Cursor c = null;
try {
c = cr.query(appendProfile(Bookmarks.CONTENT_URI),
new String[] { Bookmarks._ID },
Bookmarks.GUID + " = ?",
new String[] { Bookmarks.MOBILE_FOLDER_GUID },
null);
if (c.moveToFirst())
mMobileFolderId = c.getLong(c.getColumnIndexOrThrow(Bookmarks._ID));
} finally {
if (c != null)
c.close();
}
return mMobileFolderId;
}
public void addBookmark(ContentResolver cr, String title, String uri) {
long folderId = getMobileBookmarksFolderId(cr);
if (folderId < 0)
return;
ContentValues values = new ContentValues();
values.put(Browser.BookmarkColumns.TITLE, title);
values.put(Bookmarks.URL, uri);
values.put(Bookmarks.PARENT, folderId);
// Restore deleted record if possible
values.put(Bookmarks.IS_DELETED, 0);

View File

@ -236,17 +236,17 @@ public class ViewportMetrics {
public String toJSON() {
try {
return new JSONStringer().object()
.key("x").value(mViewportRect.left)
.key("y").value(mViewportRect.top)
.key("width").value(mViewportRect.width())
.key("height").value(mViewportRect.height())
.key("pageWidth").value(mPageSize.width)
.key("pageHeight").value(mPageSize.height)
.key("offsetX").value(mViewportOffset.x)
.key("offsetY").value(mViewportOffset.y)
.key("zoom").value(mZoomFactor)
.endObject().toString();
JSONStringer object = new JSONStringer().object();
object.key("zoom").value(mZoomFactor);
object.key("offsetY").value(mViewportOffset.y);
object.key("offsetX").value(mViewportOffset.x);
object.key("pageHeight").value(mPageSize.height);
object.key("pageWidth").value(mPageSize.width);
object.key("height").value(mViewportRect.height());
object.key("width").value(mViewportRect.width());
object.key("y").value(mViewportRect.top);
object.key("x").value(mViewportRect.left);
return object.endObject().toString();
} catch (JSONException je) {
Log.e(LOGTAG, "Error serializing viewportmetrics", je);
return "";

View File

@ -1160,10 +1160,10 @@ Tab.prototype = {
get viewport() {
// Update the viewport to current dimensions
this._viewport.x = this.browser.contentWindow.scrollX +
this.viewportExcess.x;
this._viewport.y = this.browser.contentWindow.scrollY +
this.viewportExcess.y;
this._viewport.x = (this.browser.contentWindow.scrollX +
this.viewportExcess.x) || 0;
this._viewport.y = (this.browser.contentWindow.scrollY +
this.viewportExcess.y) || 0;
// Transform coordinates based on zoom
this._viewport.x = Math.round(this._viewport.x * this._viewport.zoom);
@ -1192,8 +1192,8 @@ Tab.prototype = {
updateViewport: function(aReset) {
let win = this.browser.contentWindow;
let zoom = (aReset ? this.getDefaultZoomLevel() : this._viewport.zoom);
let xpos = (aReset ? win.scrollX * zoom : this._viewport.x);
let ypos = (aReset ? win.scrollY * zoom : this._viewport.y);
let xpos = ((aReset && win) ? win.scrollX * zoom : this._viewport.x);
let ypos = ((aReset && win) ? win.scrollY * zoom : this._viewport.y);
this.viewportExcess = { x: 0, y: 0 };
this.viewport = { x: xpos, y: ypos,

View File

@ -4,7 +4,7 @@ Universal manifests for Mozilla test harnesses
What ManifestDestiny gives you:
* manifests are (ordered) lists of tests
* manifests are ordered lists of tests
* tests may have an arbitrary number of key, value pairs
* the parser returns an ordered list of test data structures, which
are just dicts with some keys. For example, a test with no
@ -23,6 +23,14 @@ additional key, value metadata to each test.
# Why have test manifests?
It is desirable to have a unified format for test manifests for testing
[mozilla-central](http://hg.mozilla.org/mozilla-central), etc.
* It is desirable to be able to selectively enable or disable tests based on platform or other conditions. This should be easy to do. Currently, since many of the harnesses just crawl directories, there is no effective way of disabling a test except for removal from mozilla-central
* It is desriable to do this in a universal way so that enabling and disabling tests as well as other tasks are easily accessible to a wider audience than just those intimately familiar with the specific test framework.
* It is desirable to have other metadata on top of the test. For instance, let's say a test is marked as skipped. It would be nice to give the reason why.
Most Mozilla test harnesses work by crawling a directory structure.
While this is straight-forward, manifests offer several practical
advantages::
@ -227,6 +235,80 @@ directory:
manifestparser [options] update manifest from_directory -tag1 -tag2 --key1=value1 --key2=value2 ...
# Usage example
Here is an example of how to create manifests for a directory tree and
update the tests listed in the manifests from an external source.
## Creating Manifests
Let's say you want to make a series of manifests for a given directory structure containing `.js` test files:
testing/mozmill/tests/firefox/
testing/mozmill/tests/firefox/testAwesomeBar/
testing/mozmill/tests/firefox/testPreferences/
testing/mozmill/tests/firefox/testPrivateBrowsing/
testing/mozmill/tests/firefox/testSessionStore/
testing/mozmill/tests/firefox/testTechnicalTools/
testing/mozmill/tests/firefox/testToolbar/
testing/mozmill/tests/firefox/restartTests
You can use `manifestparser create` to do this:
$ manifestparser help create
Usage: manifestparser.py [options] create directory <directory> <...>
create a manifest from a list of directories
Options:
-p PATTERN, --pattern=PATTERN
glob pattern for files
-i IGNORE, --ignore=IGNORE
directories to ignore
-w IN_PLACE, --in-place=IN_PLACE
Write .ini files in place; filename to write to
We only want `.js` files and we want to skip the `restartTests` directory.
We also want to write a manifest per directory, so I use the `--in-place`
option to write the manifests:
manifestparser create . -i restartTests -p '*.js' -w manifest.ini
This creates a manifest.ini per directory that we care about with the JS test files:
testing/mozmill/tests/firefox/manifest.ini
testing/mozmill/tests/firefox/testAwesomeBar/manifest.ini
testing/mozmill/tests/firefox/testPreferences/manifest.ini
testing/mozmill/tests/firefox/testPrivateBrowsing/manifest.ini
testing/mozmill/tests/firefox/testSessionStore/manifest.ini
testing/mozmill/tests/firefox/testTechnicalTools/manifest.ini
testing/mozmill/tests/firefox/testToolbar/manifest.ini
The top-level `manifest.ini` merely has `[include:]` references to the sub manifests:
[include:testAwesomeBar/manifest.ini]
[include:testPreferences/manifest.ini]
[include:testPrivateBrowsing/manifest.ini]
[include:testSessionStore/manifest.ini]
[include:testTechnicalTools/manifest.ini]
[include:testToolbar/manifest.ini]
Each sub-level manifest contains the (`.js`) test files relative to it.
## Updating the tests from manifests
You may need to update tests as given in manifests from a different source directory.
`manifestparser update` was made for just this purpose:
Usage: manifestparser [options] update manifest directory -tag1 -tag2 --key1=value1 --key2=value2 ...
update the tests as listed in a manifest from a directory
To update from a directory of tests in `~/mozmill/src/mozmill-tests/firefox/` run:
manifestparser update manifest.ini ~/mozmill/src/mozmill-tests/firefox/
# Tests
ManifestDestiny includes a suite of tests:
@ -309,6 +391,14 @@ through several design considerations.
installation.
# Developing ManifestDestiny
ManifestDestiny is developed and maintained by Mozilla's
[Automation and Testing Team](https://wiki.mozilla.org/Auto-tools).
The project page is located at
https://wiki.mozilla.org/Auto-tools/Projects/ManifestDestiny .
# Historical Reference
Date-ordered list of links about how manifests came to be where they are today::

View File

@ -3,6 +3,7 @@ from devicemanager import DeviceManager, DMError
import re
import os
import sys
import tempfile
class DeviceManagerADB(DeviceManager):
@ -13,6 +14,7 @@ class DeviceManagerADB(DeviceManager):
self.retries = 0
self._sock = None
self.useRunAs = False
self.useZip = False
self.packageName = None
if packageName == None:
if os.getenv('USER'):
@ -30,6 +32,10 @@ class DeviceManagerADB(DeviceManager):
except:
self.useRunAs = False
self.packageName = None
try:
self.verifyZip()
except:
self.useZip = False
try:
# a test to see if we have root privs
files = self.listFiles("/data/data")
@ -103,8 +109,18 @@ class DeviceManagerADB(DeviceManager):
def pushDir(self, localDir, remoteDir):
# adb "push" accepts a directory as an argument, but if the directory
# contains symbolic links, the links are pushed, rather than the linked
# files; we push file-by-file to get around this limitation
# files; we either zip/unzip or push file-by-file to get around this
# limitation
try:
if (self.useZip):
localZip = tempfile.mktemp()+".zip"
remoteZip = remoteDir + "/adbdmtmp.zip"
subprocess.check_output(["zip", "-r", localZip, '.'], cwd=localDir)
self.pushFile(localZip, remoteZip)
os.remove(localZip)
self.checkCmdAs(["shell", "unzip", "-o", remoteZip, "-d", remoteDir])
self.checkCmdAs(["shell", "rm", remoteZip])
else:
if (not self.dirExists(remoteDir)):
self.mkDirs(remoteDir+"/x")
for root, dirs, files in os.walk(localDir, followlinks='true'):
@ -124,10 +140,10 @@ class DeviceManagerADB(DeviceManager):
if (not self.dirExists(targetDir)):
self.mkDir(targetDir)
self.checkCmdAs(["shell", "chmod", "777", remoteDir])
return True
return remoteDir
except:
print "pushing " + localDir + " to " + remoteDir + " failed"
return False
return None
# external function
# returns:
@ -241,11 +257,25 @@ class DeviceManagerADB(DeviceManager):
acmd = ["shell", "am","start"]
cmd = ' '.join(cmd).strip()
i = cmd.find(" ")
# SUT identifies the URL by looking for :\\ -- another strategy to consider
re_url = re.compile('^[http|file|chrome|about].*')
last = cmd.rfind(" ")
uri = ""
args = ""
if re_url.match(cmd[last:].strip()):
args = cmd[i:last].strip()
uri = cmd[last:].strip()
else:
args = cmd[i:].strip()
acmd.append("-n")
acmd.append(cmd[0:i] + "/.App")
acmd.append("--es")
if args != "":
acmd.append("args")
acmd.append(cmd[i:])
acmd.append(args)
if uri != "":
acmd.append("-d")
acmd.append(''.join(['\'',uri, '\'']));
print acmd
self.checkCmd(acmd)
return outputFile;
@ -578,3 +608,25 @@ class DeviceManagerADB(DeviceManager):
self.checkCmd(["shell", "rm", devroot + "/tmp/tmpfile"])
self.checkCmd(["shell", "run-as", packageName, "rm", "-r", devroot + "/sanity"])
def isUnzipAvailable(self):
data = self.runCmd(["shell", "unzip"]).stdout.read()
if (re.search('Usage', data)):
return True
else:
return False
def isLocalZipAvailable(self):
try:
subprocess.check_call(["zip", "-?"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except:
return False
return True
def verifyZip(self):
# If "zip" can be run locally, and "unzip" can be run remotely, then pushDir
# can use these to push just one file per directory -- a significant
# optimization for large directories.
self.useZip = False
if (self.isUnzipAvailable() and self.isLocalZipAvailable()):
print "will use zip to push directories"
self.useZip = True

View File

@ -110,29 +110,49 @@ def _extract(path, extdir=None, delete=False):
If delete is set to True, deletes the bundle at path
Returns the list of top level files that were extracted
"""
assert not os.path.isfile(extdir), "extdir cannot be a file"
if extdir is None:
extdir = os.path.dirname(path)
elif not os.path.isdir(extdir):
os.makedirs(extdir)
if zipfile.is_zipfile(path):
bundle = zipfile.ZipFile(path)
namelist = bundle.namelist()
if hasattr(bundle, 'extractall'):
bundle.extractall(path=extdir)
# zipfile.extractall doesn't exist in Python 2.5
else:
for name in namelist:
filename = os.path.realpath(os.path.join(extdir, name))
if name.endswith("/"):
os.makedirs(filename)
else:
path = os.path.dirname(filename)
if not os.path.isdir(path):
os.makedirs(path)
dest = open(filename, "wb")
dest.write(bundle.read(name))
dest.close()
elif tarfile.is_tarfile(path):
bundle = tarfile.open(path)
namelist = bundle.getnames()
if hasattr(bundle, 'extractall'):
bundle.extractall(path=extdir)
# tarfile.extractall doesn't exist in Python 2.4
else:
for name in namelist:
bundle.extract(name, path=extdir)
else:
return
if extdir is None:
extdir = os.path.dirname(path)
elif not os.path.exists(extdir):
os.makedirs(extdir)
bundle.extractall(path=extdir)
bundle.close()
if delete:
os.remove(path)
# namelist returns paths with forward slashes even in windows
top_level_files = [os.path.join(extdir, name) for name in namelist
if len(name.rstrip('/').split('/')) == 1]
# namelist doesn't include folders in windows, append these to the list
if mozinfo.isWin:
# namelist doesn't include folders, append these to the list
for name in namelist:
root = name[:name.find('/')]
root = os.path.join(extdir, name[:name.find('/')])
if root not in top_level_files:
top_level_files.append(root)
return top_level_files

View File

@ -100,10 +100,12 @@ TestSuite.prototype.loadTest = function(test) {
log.log('TEST-END', test.name + ' ' + runTime + fThreshold);
} catch (e) {
log.error(test.name + ' | ' + e);
if (e['stack'] !== undefined) {
log.debug(test.name + ' | Traceback:');
lines = e.stack.split('\n');
for (let i = 0; i < lines.length - 1; ++i) {
log.debug('\t' + lines[i]);
}
}
}
};

View File

@ -36,6 +36,8 @@
from mozprocess import ProcessHandler
from pepresults import Results
from time import sleep
from threading import Thread
import mozlog
import os
@ -57,6 +59,13 @@ class PepProcess(ProcessHandler):
self.logger = mozlog.getLogger('PEP')
results.fails[str(None)] = []
def waitForQuit(self, timeout=5):
for i in range(1, timeout):
if self.proc.returncode != None:
return
sleep(1)
self.proc.kill()
def processOutputLine(self, line):
"""
Callback called on each line of output
@ -68,6 +77,11 @@ class PepProcess(ProcessHandler):
# The output is generated from the Peptest extension
# Format is 'PEP <LEVEL> <MSG>' where <MSG> can have multiple tokens
# The content of <MSG> depends on the <LEVEL>
if line.find('Test Suite Finished') != -1:
thread = Thread(target=self.waitForQuit)
thread.setDaemon(True) # don't hang on quit
thread.start()
level = tokens[1]
if level == 'TEST-START':
results.currentTest = tokens[2].rstrip()
@ -81,11 +95,12 @@ class PepProcess(ProcessHandler):
threshold = 0.0
msg = results.currentTest \
+ ' | fail threshold: ' + str(threshold) \
+ ' | metric: ' + str(metric)
+ ' | fail threshold: ' + str(threshold)
if metric > threshold:
msg += ' < metric: ' + str(metric)
self.logger.testFail(msg)
else:
msg += ' >= metric: ' + str(metric)
self.logger.testPass(msg)
self.logger.testEnd(

View File

@ -68,25 +68,53 @@ def isURL(path):
def extract(path, extdir=None, delete=False):
"""
Takes in a tar or zip file and extracts it to extdir
If extdir is not specified, extracts to path
If extdir is not specified, extracts to os.path.dirname(path)
If delete is set to True, deletes the bundle at path
Returns the list of top level files that were extracted
"""
assert not os.path.isfile(extdir), "extdir cannot be a file"
if extdir is None:
extdir = os.path.dirname(path)
elif not os.path.isdir(extdir):
os.makedirs(extdir)
if zipfile.is_zipfile(path):
bundle = zipfile.ZipFile(path)
namelist = bundle.namelist()
if hasattr(bundle, 'extractall'):
bundle.extractall(path=extdir)
# zipfile.extractall doesn't exist in Python 2.5
else:
for name in namelist:
filename = os.path.realpath(os.path.join(extdir, name))
if name.endswith("/"):
os.makedirs(filename)
else:
path = os.path.dirname(filename)
if not os.path.isdir(path):
os.makedirs(path)
dest = open(filename, "wb")
dest.write(bundle.read(name))
dest.close()
elif tarfile.is_tarfile(path):
bundle = tarfile.open(path)
namelist = bundle.getnames()
if hasattr(bundle, 'extractall'):
bundle.extractall(path=extdir)
# tarfile.extractall doesn't exist in Python 2.4
else:
for name in namelist:
bundle.extract(name, path=extdir)
else:
return
if extdir is None:
extdir = os.path.dirname(path)
elif not os.path.exists(extdir):
os.makedirs(extdir)
bundle.extractall(path=extdir)
bundle.close()
if delete:
os.remove(path)
return [os.path.join(extdir, name) for name in namelist
if len(name.rstrip(os.sep).split(os.sep)) == 1]
# namelist returns paths with forward slashes even in windows
top_level_files = [os.path.join(extdir, name) for name in namelist
if len(name.rstrip('/').split('/')) == 1]
# namelist doesn't include folders, append these to the list
for name in namelist:
root = os.path.join(extdir, name[:name.find('/')])
if root not in top_level_files:
top_level_files.append(root)
return top_level_files

View File

@ -82,6 +82,7 @@ class Peptest():
testObj = {}
testObj['path'] = os.path.realpath(self.options.testPath)
testObj['name'] = os.path.basename(self.options.testPath)
testObj['here'] = os.path.dirname(testObj['path'])
tests.append(testObj)
else:
# a test manifest was passed in

View File

@ -44,12 +44,15 @@ try:
except IOError:
description = ''
version = "0.0"
version = "0.1"
dependencies = ['mozprofile',
dependencies = ['ManifestDestiny',
'mozhttpd',
'mozlog',
'mozprofile >= 0.1',
'mozprocess',
'mozrunner >= 3.0b3',
'mozlog']
]
setup(name='peptest',
version=version,