Merge s-c to m-c

This commit is contained in:
Phil Ringnalda 2013-06-22 15:50:51 -07:00
commit daffa2e519
20 changed files with 351 additions and 209 deletions

View File

@ -452,6 +452,8 @@ abstract public class BrowserApp extends GeckoApp
registerEventListener("Feedback:OpenPlayStore");
registerEventListener("Feedback:MaybeLater");
registerEventListener("Telemetry:Gather");
registerEventListener("Settings:Show");
registerEventListener("Updater:Launch");
Distribution.init(this, getPackageResourcePath());
JavaAddonManager.getInstance().init(getApplicationContext());
@ -724,6 +726,8 @@ abstract public class BrowserApp extends GeckoApp
unregisterEventListener("Feedback:OpenPlayStore");
unregisterEventListener("Feedback:MaybeLater");
unregisterEventListener("Telemetry:Gather");
unregisterEventListener("Settings:Show");
unregisterEventListener("Updater:Launch");
if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
@ -1108,6 +1112,17 @@ abstract public class BrowserApp extends GeckoApp
final String url = message.getString("url");
GeckoAppShell.openUriExternal(url, "text/plain", "", "",
Intent.ACTION_SEND, title);
} else if (event.equals("Settings:Show")) {
// null strings return "null" (http://code.google.com/p/android/issues/detail?id=13830)
String resource = null;
if (!message.isNull(GeckoPreferences.INTENT_EXTRA_RESOURCES)) {
resource = message.getString(GeckoPreferences.INTENT_EXTRA_RESOURCES);
}
Intent settingsIntent = new Intent(this, GeckoPreferences.class);
GeckoPreferences.setResourceToOpen(settingsIntent, resource);
startActivity(settingsIntent);
} else if (event.equals("Updater:Launch")) {
handleUpdaterLaunch();
} else {
super.handleMessage(event, message);
}
@ -1857,4 +1872,33 @@ abstract public class BrowserApp extends GeckoApp
String profile = GeckoProfile.findDefaultProfile(this);
return (profile != null ? profile : "default");
}
/**
* Launch UI that lets the user update Firefox.
*
* This depends on the current channel: Release and Beta both direct to the
* Google Play Store. If updating is enabled, Aurora, Nightly, and custom
* builds open about:, which provides an update interface.
*
* If updating is not enabled, this simply logs an error.
*
* @return true if update UI was launched.
*/
protected boolean handleUpdaterLaunch() {
if ("release".equals(AppConstants.MOZ_UPDATE_CHANNEL) ||
"beta".equals(AppConstants.MOZ_UPDATE_CHANNEL)) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("market://details?id=" + getPackageName()));
startActivity(intent);
return true;
}
if (AppConstants.MOZ_UPDATER) {
Tabs.getInstance().loadUrlInTab("about:");
return true;
}
Log.w(LOGTAG, "No candidate updater found; ignoring launch request.");
return false;
}
}

View File

@ -48,16 +48,7 @@ public class DataReportingNotification {
Intent prefIntent = new Intent(GeckoApp.ACTION_LAUNCH_SETTINGS);
prefIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.BROWSER_INTENT_CLASS);
// Build launch intent based on Android version.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
prefIntent.putExtra("resource", "preferences_datareporting");
} else {
prefIntent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, GeckoPreferenceFragment.class.getName());
Bundle fragmentArgs = new Bundle();
fragmentArgs.putString("resource", "preferences_datareporting");
prefIntent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
}
GeckoPreferences.setResourceToOpen(prefIntent, "preferences_datareporting");
prefIntent.putExtra(ALERT_NAME_DATAREPORTING_NOTIFICATION, true);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, prefIntent, PendingIntent.FLAG_UPDATE_CURRENT);

View File

@ -82,8 +82,8 @@ public class GeckoPreferences
super.onCreate(savedInstanceState);
// Use setResourceToOpen to specify these extras.
Bundle intentExtras = getIntent().getExtras();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
if (intentExtras != null && intentExtras.containsKey(INTENT_EXTRA_RESOURCES)) {
String resourceName = intentExtras.getString(INTENT_EXTRA_RESOURCES);
@ -707,4 +707,31 @@ public class GeckoPreferences
public boolean isGeckoActivityOpened() {
return false;
}
/**
* Given an Intent instance, add extras to specify which settings section to
* open.
*
* resource should be a valid Android XML resource identifier.
*
* The mechanism to open a section differs based on Android version.
*/
public static void setResourceToOpen(final Intent intent, final String resource) {
if (intent == null) {
throw new IllegalArgumentException("intent must not be null");
}
if (resource == null) {
return;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
intent.putExtra("resource", resource);
} else {
intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, GeckoPreferenceFragment.class.getName());
Bundle fragmentArgs = new Bundle();
fragmentArgs.putString("resource", resource);
intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
}
}
}

View File

@ -667,7 +667,6 @@ RES_DRAWABLE_MDPI = \
res/drawable-mdpi/tabs_count_foreground.png \
res/drawable-mdpi/toast.9.png \
res/drawable-mdpi/toast_button_focused.9.png \
res/drawable-mdpi/toast_button_focused.9.png \
res/drawable-mdpi/toast_button_pressed.9.png \
res/drawable-mdpi/toast_divider.9.png \
res/drawable-mdpi/address_bar_url_default.9.png \

View File

@ -82,7 +82,7 @@ public class UpdateServiceHelper {
String locale = null;
try {
ApplicationInfo info = pm.getApplicationInfo(AppConstants.ANDROID_PACKAGE_NAME, 0);
String updateLocaleUrl = "jar:jar:file://" + info.sourceDir + "!/omni.ja!/update.locale";
String updateLocaleUrl = "jar:jar:file://" + info.sourceDir + "!/" + AppConstants.OMNIJAR_NAME + "!/update.locale";
locale = GeckoJarReader.getText(updateLocaleUrl);

View File

@ -35,7 +35,7 @@
# [testSettingsMenuItems] # see bug 843947
[testSystemPages]
# [testPermissions] # see bug 757475
# [testJarReader] # see bug 738890
[testJarReader]
[testDistribution]
[testFindInPage]
[testInputAwesomeBar]

View File

@ -3,6 +3,7 @@ package @ANDROID_PACKAGE_NAME@.tests;
import java.lang.ClassLoader;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
@ -18,29 +19,45 @@ public class testJarReader extends BaseTest {
public void testJarReader() {
try {
ClassLoader classLoader = getActivity().getClassLoader();
Class appConstantsClass = classLoader.loadClass("org.mozilla.gecko.AppConstants");
String omniJarName = (String) appConstantsClass.getField("OMNIJAR_NAME").get(null);
Class gjrClass = classLoader.loadClass("org.mozilla.gecko.util.GeckoJarReader");
Method getStreamMethod = gjrClass.getMethod("getStream", String.class);
String appPath = getActivity().getApplication().getPackageResourcePath();
mAsserter.isnot(appPath, null, "getPackageResourcePath is non-null");
// Test reading a file from a jar url
String url = "jar:file://" + getActivity().getApplication().getPackageResourcePath() + "!/omni.ja";
InputStream stream = (InputStream)getStreamMethod.invoke(null, "jar:" + url + "!/chrome/chrome/content/branding/favicon32.png");
mAsserter.isnot(stream, null, "JarReader returned null for invalid jar file");
// Test reading a file from a jar url that looks correct.
String url = "jar:file://" + appPath + "!/" + omniJarName;
InputStream stream = (InputStream) getStreamMethod.invoke(null, "jar:" + url + "!/chrome/chrome/content/branding/favicon32.png");
mAsserter.isnot(stream, null, "JarReader returned non-null for valid file in valid jar");
// Test looking for a jar that doesn't exist
url = "jar:file://" + getActivity().getApplication().getPackageResourcePath() + "2!/omni.ja";
stream = (InputStream)getStreamMethod.invoke(null, "jar:" + url + "!/chrome/chrome/content/branding/favicon32.png");
mAsserter.is(stream, null, "JarReader returned null for invalid jar file");
// Test looking for an non-existent file in a jar.
url = "jar:file://" + appPath + "!/" + omniJarName;
stream = (InputStream) getStreamMethod.invoke(null, "jar:" + url + "!/chrome/chrome/content/branding/nonexistent_file.png");
mAsserter.is(stream, null, "JarReader returned null for non-existent file in valid jar");
// Test looking for an non-existant file in a jar
url = "jar:file://" + getActivity().getApplication().getPackageResourcePath() + "2!/omni.ja";
stream = (InputStream)getStreamMethod.invoke(null, "jar:" + url + "!/chrome/chrome/content/branding/nonexistant_file.png");
mAsserter.is(stream, null, "JarReader returned null for non-existant file in jar");
// Test looking for a file that doesn't exist in the APK.
url = "jar:file://" + appPath + "!/" + "BAD" + omniJarName;
stream = (InputStream) getStreamMethod.invoke(null, "jar:" + url + "!/chrome/chrome/content/branding/favicon32.png");
mAsserter.is(stream, null, "JarReader returned null for valid file in invalid jar file");
// Test looking for an jar with an invalid url
url = "jar:file://" + getActivity().getApplication().getPackageResourcePath() + "2!!/omni.ja";
stream = (InputStream)getStreamMethod.invoke(null, "jar:" + url + "!/chrome/chrome/content/branding/nonexistant_file.png");
mAsserter.is(stream, null, "JarReader returned null for bar jar url");
// Test looking for an jar with an invalid url.
url = "jar:file://" + appPath + "!" + "!/" + omniJarName;
stream = (InputStream) getStreamMethod.invoke(null, "jar:" + url + "!/chrome/chrome/content/branding/nonexistent_file.png");
mAsserter.is(stream, null, "JarReader returned null for bad jar url");
// Test looking for a file that doesn't exist on disk.
url = "jar:file://" + appPath + "BAD" + "!/" + omniJarName;
stream = (InputStream) getStreamMethod.invoke(null, "jar:" + url + "!/chrome/chrome/content/branding/favicon32.png");
mAsserter.is(stream, null, "JarReader returned null for a non-existent APK");
} catch (java.lang.ClassCastException ex) {
mAsserter.is(false, true, "Error getting OMNIJAR_NAME");
return;
} catch (java.lang.NoSuchFieldException ex) {
mAsserter.is(false, true, "Error getting field");
return;
} catch (java.lang.ClassNotFoundException ex) {
mAsserter.is(false, true, "Error getting class");
return;

View File

@ -98,6 +98,20 @@ public final class GeckoJarReader {
return new NativeZip(fileUrl.getPath());
}
// Public for testing only.
public static InputStream getStream(String url) {
Stack<String> jarUrls = parseUrl(url);
try {
NativeZip zip = getZipFile(jarUrls.pop());
return getStream(zip, jarUrls, url);
} catch (Exception ex) {
// Some JNI code throws IllegalArgumentException on a bad file name;
// swallow the error and return null. We could also see legitimate
// IOExceptions here.
return null;
}
}
private static InputStream getStream(NativeZip zip, Stack<String> jarUrls, String origUrl) {
InputStream inputStream = null;

View File

@ -18,6 +18,9 @@ const PREF_UPLOAD_ENABLED = "android.not_a_preference.healthreport.uploadEnabled
// Name of Gecko Pref specifying report content location.
const PREF_REPORTURL = "datareporting.healthreport.about.reportUrl";
// Monotonically increasing wrapper API version number.
const WRAPPER_VERSION = 1;
const EVENT_HEALTH_REQUEST = "HealthReport:Request";
const EVENT_HEALTH_RESPONSE = "HealthReport:Response";
@ -37,6 +40,7 @@ let healthReportWrapper = {
iframe.addEventListener("load", healthReportWrapper.initRemotePage, false);
let report = this._getReportURI();
iframe.src = report.spec;
console.log("AboutHealthReport: loading content from " + report.spec);
sharedPrefs.addObserver(PREF_UPLOAD_ENABLED, this, false);
Services.obs.addObserver(this, EVENT_HEALTH_RESPONSE, false);
@ -57,7 +61,10 @@ let healthReportWrapper = {
_getReportURI: function () {
let url = Services.urlFormatter.formatURLPref(PREF_REPORTURL);
return Services.io.newURI(url, null, null);
// This handles URLs that already have query parameters.
let uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
uri.query += ((uri.query != "") ? "&v=" : "v=") + WRAPPER_VERSION;
return uri;
},
onOptIn: function () {
@ -116,6 +123,21 @@ let healthReportWrapper = {
iframe.contentWindow.postMessage(data, reportUrl);
},
showSettings: function () {
console.log("AboutHealthReport: showing settings.");
sendMessageToJava({
type: "Settings:Show",
resource: "preferences_datareporting",
});
},
launchUpdater: function () {
console.log("AboutHealthReport: launching updater.");
sendMessageToJava({
type: "Updater:Launch",
});
},
handleRemoteCommand: function (evt) {
switch (evt.detail.command) {
case "DisableDataSubmission":
@ -130,6 +152,12 @@ let healthReportWrapper = {
case "RequestCurrentPayload":
this.refreshPayload();
break;
case "ShowSettings":
this.showSettings();
break;
case "LaunchUpdater":
this.launchUpdater();
break;
default:
Cu.reportError("Unexpected remote command received: " + evt.detail.command +
". Ignoring command.");

View File

@ -124,7 +124,7 @@ JNI.prototype = {
callStaticVoidMethod: function(aClass, aMethod) {
let args = Array.prototype.slice.apply(arguments, [2]);
this._callStaticVoidMethod(aClass, aMethodId.methodId, this.getArgs(aMethod, args));
this._callStaticVoidMethod(aClass, aMethod.methodId, this.getArgs(aMethod, args));
if (this.exceptionCheck())
throw("Error calling static void method");
},

View File

@ -64,8 +64,7 @@ this.EXPORTED_SYMBOLS = ["AddonsReconciler", "CHANGE_INSTALLED",
* When you are finished with the instance, please call:
*
* reconciler.stopListening();
* reconciler.saveStateFile(...);
*
* reconciler.saveState(...);
*
* There are 2 classes of listeners in the AddonManager: AddonListener and
* InstallListener. This class is a listener for both (member functions just
@ -124,12 +123,23 @@ AddonsReconciler.prototype = {
/** Flag indicating whether we are listening to AddonManager events. */
_listening: false,
/** Whether state has been loaded from a file.
/**
* Whether state has been loaded from a file.
*
* State is loaded on demand if an operation requires it.
*/
_stateLoaded: false,
/**
* Define this as false if the reconciler should not persist state
* to disk when handling events.
*
* This allows test code to avoid spinning to write during observer
* notifications and xpcom shutdown, which appears to cause hangs on WinXP
* (Bug 873861).
*/
_shouldPersist: true,
/** log4moz logger instance */
_log: null,
@ -384,7 +394,12 @@ AddonsReconciler.prototype = {
}
}
this.saveState(null, callback);
// See note for _shouldPersist.
if (this._shouldPersist) {
this.saveState(null, callback);
} else {
callback();
}
}.bind(this));
},
@ -610,9 +625,12 @@ AddonsReconciler.prototype = {
}
}
let cb = Async.makeSpinningCallback();
this.saveState(null, cb);
cb.wait();
// See note for _shouldPersist.
if (this._shouldPersist) {
let cb = Async.makeSpinningCallback();
this.saveState(null, cb);
cb.wait();
}
}
catch (ex) {
this._log.warn("Exception: " + Utils.exceptionStr(ex));

View File

@ -33,120 +33,63 @@ Utils.deferGetSet(FormRec, "cleartext", ["name", "value"]);
let FormWrapper = {
_log: Log4Moz.repository.getLogger("Sync.Engine.Forms"),
_getEntryCols: ["name", "value"],
_getEntryCols: ["fieldname", "value"],
_guidCols: ["guid"],
_stmts: {},
_getStmt: function _getStmt(query) {
if (query in this._stmts) {
return this._stmts[query];
}
this._log.trace("Creating SQL statement: " + query);
let db = Svc.Form.DBConnection;
return this._stmts[query] = db.createAsyncStatement(query);
// Do a "sync" search by spinning the event loop until it completes.
_searchSpinningly: function(terms, searchData) {
let results = [];
let cb = Async.makeSpinningCallback();
let callbacks = {
handleResult: function(result) {
results.push(result);
},
handleCompletion: function(reason) {
cb(null, results);
}
};
Svc.FormHistory.search(terms, searchData, callbacks);
return cb.wait();
},
_finalize : function () {
for each (let stmt in FormWrapper._stmts) {
stmt.finalize();
}
FormWrapper._stmts = {};
_updateSpinningly: function(changes) {
let cb = Async.makeSpinningCallback();
let callbacks = {
handleCompletion: function(reason) {
cb();
}
};
Svc.FormHistory.update(changes, callbacks);
return cb.wait();
},
get _getAllEntriesStmt() {
const query =
"SELECT fieldname name, value FROM moz_formhistory " +
"ORDER BY 1.0 * (lastUsed - (SELECT lastUsed FROM moz_formhistory ORDER BY lastUsed ASC LIMIT 1)) / " +
"((SELECT lastUsed FROM moz_formhistory ORDER BY lastUsed DESC LIMIT 1) - (SELECT lastUsed FROM moz_formhistory ORDER BY lastUsed ASC LIMIT 1)) * " +
"timesUsed / (SELECT timesUsed FROM moz_formhistory ORDER BY timesUsed DESC LIMIT 1) DESC " +
"LIMIT 500";
return this._getStmt(query);
},
get _getEntryStmt() {
const query = "SELECT fieldname name, value FROM moz_formhistory " +
"WHERE guid = :guid";
return this._getStmt(query);
},
get _getGUIDStmt() {
const query = "SELECT guid FROM moz_formhistory " +
"WHERE fieldname = :name AND value = :value";
return this._getStmt(query);
},
get _setGUIDStmt() {
const query = "UPDATE moz_formhistory SET guid = :guid " +
"WHERE fieldname = :name AND value = :value";
return this._getStmt(query);
},
get _hasGUIDStmt() {
const query = "SELECT guid FROM moz_formhistory WHERE guid = :guid LIMIT 1";
return this._getStmt(query);
},
get _replaceGUIDStmt() {
const query = "UPDATE moz_formhistory SET guid = :newGUID " +
"WHERE guid = :oldGUID";
return this._getStmt(query);
},
getAllEntries: function getAllEntries() {
return Async.querySpinningly(this._getAllEntriesStmt, this._getEntryCols);
},
getEntry: function getEntry(guid) {
let stmt = this._getEntryStmt;
stmt.params.guid = guid;
return Async.querySpinningly(stmt, this._getEntryCols)[0];
},
getGUID: function getGUID(name, value) {
// Query for the provided entry.
let getStmt = this._getGUIDStmt;
getStmt.params.name = name;
getStmt.params.value = value;
// Give the GUID if we found one.
let item = Async.querySpinningly(getStmt, this._guidCols)[0];
if (!item) {
// Shouldn't happen, but Bug 597400...
// Might as well just return.
this._log.warn("GUID query returned " + item + "; turn on Trace logging for details.");
this._log.trace("getGUID(" + JSON.stringify(name) + ", " +
JSON.stringify(value) + ") => " + item);
getEntry: function (guid) {
let results = this._searchSpinningly(this._getEntryCols, {guid: guid});
if (!results.length) {
return null;
}
return {name: results[0].fieldname, value: results[0].value};
},
if (item.guid != null) {
return item.guid;
getGUID: function (name, value) {
// Query for the provided entry.
let query = { fieldname: name, value: value };
let results = this._searchSpinningly(this._guidCols, query);
return results.length ? results[0].guid : null;
},
hasGUID: function (guid) {
// We could probably use a count function here, but searchSpinningly exists...
return this._searchSpinningly(this._guidCols, {guid: guid}).length != 0;
},
replaceGUID: function (oldGUID, newGUID) {
let changes = {
op: "update",
guid: oldGUID,
newGuid: newGUID,
}
// We need to create a GUID for this entry.
let setStmt = this._setGUIDStmt;
let guid = Utils.makeGUID();
setStmt.params.guid = guid;
setStmt.params.name = name;
setStmt.params.value = value;
Async.querySpinningly(setStmt);
return guid;
},
hasGUID: function hasGUID(guid) {
let stmt = this._hasGUIDStmt;
stmt.params.guid = guid;
return Async.querySpinningly(stmt, this._guidCols).length == 1;
},
replaceGUID: function replaceGUID(oldGUID, newGUID) {
let stmt = this._replaceGUIDStmt;
stmt.params.oldGUID = oldGUID;
stmt.params.newGUID = newGUID;
Async.querySpinningly(stmt);
this._updateSpinningly(changes);
}
};
@ -164,9 +107,7 @@ FormEngine.prototype = {
get prefName() "history",
_findDupe: function _findDupe(item) {
if (Svc.Form.entryExists(item.name, item.value)) {
return FormWrapper.getGUID(item.name, item.value);
}
return FormWrapper.getGUID(item.name, item.value);
}
};
@ -176,34 +117,47 @@ function FormStore(name, engine) {
FormStore.prototype = {
__proto__: Store.prototype,
applyIncomingBatch: function applyIncomingBatch(records) {
return Utils.runInTransaction(Svc.Form.DBConnection, function() {
return Store.prototype.applyIncomingBatch.call(this, records);
}, this);
_processChange: function (change) {
// If this._changes is defined, then we are applying a batch, so we
// can defer it.
if (this._changes) {
this._changes.push(change);
return;
}
// Otherwise we must handle the change synchronously, right now.
FormWrapper._updateSpinningly(change);
},
applyIncoming: function applyIncoming(record) {
Store.prototype.applyIncoming.call(this, record);
this._sleep(0); // Yield back to main thread after synchronous operation.
applyIncomingBatch: function (records) {
// We collect all the changes to be made then apply them all at once.
this._changes = [];
let failures = Store.prototype.applyIncomingBatch.call(this, records);
if (this._changes.length) {
FormWrapper._updateSpinningly(this._changes);
}
delete this._changes;
return failures;
},
getAllIDs: function FormStore_getAllIDs() {
getAllIDs: function () {
let results = FormWrapper._searchSpinningly(["guid"], [])
let guids = {};
for each (let {name, value} in FormWrapper.getAllEntries()) {
guids[FormWrapper.getGUID(name, value)] = true;
for (let result of results) {
guids[result.guid] = true;
}
return guids;
},
changeItemID: function FormStore_changeItemID(oldID, newID) {
changeItemID: function (oldID, newID) {
FormWrapper.replaceGUID(oldID, newID);
},
itemExists: function FormStore_itemExists(id) {
itemExists: function (id) {
return FormWrapper.hasGUID(id);
},
createRecord: function createRecord(id, collection) {
createRecord: function (id, collection) {
let record = new FormRec(collection, id);
let entry = FormWrapper.getEntry(id);
if (entry != null) {
@ -215,29 +169,34 @@ FormStore.prototype = {
return record;
},
create: function FormStore_create(record) {
create: function (record) {
this._log.trace("Adding form record for " + record.name);
Svc.Form.addEntry(record.name, record.value);
let change = {
op: "add",
fieldname: record.name,
value: record.value
};
this._processChange(change);
},
remove: function FormStore_remove(record) {
remove: function (record) {
this._log.trace("Removing form record: " + record.id);
// Just skip remove requests for things already gone
let entry = FormWrapper.getEntry(record.id);
if (entry == null) {
return;
}
Svc.Form.removeEntry(entry.name, entry.value);
let change = {
op: "remove",
guid: record.id
};
this._processChange(change);
},
update: function FormStore_update(record) {
update: function (record) {
this._log.trace("Ignoring form record update request!");
},
wipe: function FormStore_wipe() {
Svc.Form.removeAllEntries();
wipe: function () {
let change = {
op: "remove"
};
FormWrapper._updateSpinningly(change);
}
};
@ -255,13 +214,8 @@ FormTracker.prototype = {
Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
trackEntry: function trackEntry(name, value) {
this.addChangedID(FormWrapper.getGUID(name, value));
this.score += SCORE_INCREMENT_MEDIUM;
},
_enabled: false,
observe: function observe(subject, topic, data) {
observe: function (subject, topic, data) {
switch (topic) {
case "weave:engine:start-tracking":
if (!this._enabled) {
@ -287,13 +241,10 @@ FormTracker.prototype = {
}
break;
case "satchel-storage-changed":
if (data == "addEntry" || data == "before-removeEntry") {
subject = subject.QueryInterface(Ci.nsIArray);
let name = subject.queryElementAt(0, Ci.nsISupportsString)
.toString();
let value = subject.queryElementAt(1, Ci.nsISupportsString)
.toString();
this.trackEntry(name, value);
if (data == "formhistory-add" || data == "formhistory-remove") {
let guid = subject.QueryInterface(Ci.nsISupportsString).toString();
this.addChangedID(guid);
this.score += SCORE_INCREMENT_MEDIUM;
}
break;
case "profile-change-teardown":
@ -302,7 +253,7 @@ FormTracker.prototype = {
}
},
notify: function FormTracker_notify(formElement, aWindow, actionURI) {
notify: function (formElement, aWindow, actionURI) {
if (this.ignoreAll) {
return;
}

View File

@ -621,13 +621,15 @@ let _sessionCID = Services.appinfo.ID == SEAMONKEY_ID ?
"@mozilla.org/suite/sessionstore;1" :
"@mozilla.org/browser/sessionstore;1";
[["Form", "@mozilla.org/satchel/form-history;1", "nsIFormHistory2"],
[
["Idle", "@mozilla.org/widget/idleservice;1", "nsIIdleService"],
["Session", _sessionCID, "nsISessionStore"]
].forEach(function([name, contract, iface]) {
XPCOMUtils.defineLazyServiceGetter(Svc, name, contract, iface);
});
XPCOMUtils.defineLazyModuleGetter(Svc, "FormHistory", "resource://gre/modules/FormHistory.jsm");
Svc.__defineGetter__("Crypto", function() {
let cryptoSvc;
let ns = {};

View File

@ -19,7 +19,7 @@ pref("services.sync.scheduler.activeInterval", 600); // 10 minutes
pref("services.sync.scheduler.immediateInterval", 90); // 1.5 minutes
pref("services.sync.scheduler.idleTime", 300); // 5 minutes
pref("services.sync.errorhandler.networkFailureReportTimeout", 604800); // 1 week
pref("services.sync.errorhandler.networkFailureReportTimeout", 1209600); // 2 weeks
pref("services.sync.engine.addons", true);
pref("services.sync.engine.bookmarks", true);

View File

@ -7,6 +7,12 @@ let gSyncProfile;
gSyncProfile = do_get_profile();
// Init FormHistoryStartup and pretend we opened a profile.
let fhs = Cc["@mozilla.org/satchel/form-history-startup;1"]
.getService(Ci.nsIObserver);
fhs.observe(null, "profile-after-change", null);
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
// Make sure to provide the right OS so crypto loads the right binaries

View File

@ -231,6 +231,12 @@ add_test(function test_disabled_install_semantics() {
server.stop(advance_test);
});
add_test(function cleanup() {
// There's an xpcom-shutdown hook for this, but let's give this a shot.
reconciler.stopListening();
run_next_test();
});
function run_test() {
initTestLogging("Trace");
Log4Moz.repository.getLogger("Sync.Engine.Addons").level =
@ -245,5 +251,9 @@ function run_test() {
reconciler.startListening();
// Don't flush to disk in the middle of an event listener!
// This causes test hangs on WinXP.
reconciler._shouldPersist = false;
advance_test();
}

View File

@ -73,6 +73,10 @@ function run_test() {
reconciler.startListening();
// Don't flush to disk in the middle of an event listener!
// This causes test hangs on WinXP.
reconciler._shouldPersist = false;
run_next_test();
}
@ -461,3 +465,10 @@ add_test(function test_wipe_and_install() {
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
server.stop(run_next_test);
});
add_test(function cleanup() {
// There's an xpcom-shutdown hook for this, but let's give this a shot.
reconciler.stopListening();
run_next_test();
});

View File

@ -8,7 +8,8 @@ Cu.import("resource://services-sync/util.js");
function run_test() {
let baseuri = "http://fake/uri/";
let store = new FormEngine(Service)._store;
let engine = new FormEngine(Service);
let store = engine._store;
function applyEnsureNoFailures(records) {
do_check_eq(store.applyIncomingBatch(records).length, 0);
@ -37,6 +38,9 @@ function run_test() {
}
do_check_true(store.itemExists(id));
_("Should be able to find this entry as a dupe");
do_check_eq(engine._findDupe({name: "name!!", value: "value??"}), id);
let rec = store.createRecord(id);
_("Got record for id", id, rec);
do_check_eq(rec.name, "name!!");
@ -120,9 +124,7 @@ function run_test() {
value: "entry"
}]);
Utils.runInTransaction(Svc.Form.DBConnection, function() {
store.wipe();
});
store.wipe();
for (let id in store.getAllIDs()) {
do_throw("Shouldn't get any ids!");

View File

@ -8,46 +8,51 @@ Cu.import("resource://services-sync/util.js");
function run_test() {
_("Verify we've got an empty tracker to work with.");
let tracker = new FormEngine(Service)._tracker;
let engine = new FormEngine(Service);
let tracker = engine._tracker;
// Don't do asynchronous writes.
tracker.persistChangedIDs = false;
do_check_empty(tracker.changedIDs);
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
function addEntry(name, value) {
engine._store.create({name: name, value: value});
}
function removeEntry(name, value) {
guid = engine._findDupe({name: name, value: value});
engine._store.remove({id: guid});
}
try {
_("Create an entry. Won't show because we haven't started tracking yet");
Svc.Form.addEntry("name", "John Doe");
addEntry("name", "John Doe");
do_check_empty(tracker.changedIDs);
_("Tell the tracker to start tracking changes.");
Svc.Obs.notify("weave:engine:start-tracking");
Svc.Form.removeEntry("name", "John Doe");
Svc.Form.addEntry("email", "john@doe.com");
removeEntry("name", "John Doe");
addEntry("email", "john@doe.com");
do_check_attribute_count(tracker.changedIDs, 2);
_("Notifying twice won't do any harm.");
Svc.Obs.notify("weave:engine:start-tracking");
Svc.Form.addEntry("address", "Memory Lane");
addEntry("address", "Memory Lane");
do_check_attribute_count(tracker.changedIDs, 3);
_("Let's stop tracking again.");
tracker.clearChangedIDs();
Svc.Obs.notify("weave:engine:stop-tracking");
Svc.Form.removeEntry("address", "Memory Lane");
removeEntry("address", "Memory Lane");
do_check_empty(tracker.changedIDs);
_("Notifying twice won't do any harm.");
Svc.Obs.notify("weave:engine:stop-tracking");
Svc.Form.removeEntry("email", "john@doe.com");
removeEntry("email", "john@doe.com");
do_check_empty(tracker.changedIDs);
_("Test error detection.");
// This throws an exception without the fix for Bug 597400.
tracker.trackEntry("foo", "bar");
} finally {
_("Clean up.");
Svc.Form.removeAllEntries();
engine._store.wipe();
}
}

View File

@ -69,6 +69,11 @@
* firstUsedEnd - search for entries created before or at this time
* lastUsedStart - search for entries last accessed after or at this time
* lastUsedEnd - search for entries last accessed before or at this time
* newGuid - a special case valid only for 'update' and allows the guid for
* an existing record to be updated. The 'guid' term is the only
* other term which can be used (ie, you can not also specify a
* fieldname, value etc) and indicates the guid of the existing
* record that should be updated.
*
* In all of the above methods, the callback argument should be an object with
* handleResult(result), handleFailure(error) and handleCompletion(reason) functions.
@ -206,8 +211,14 @@ const searchFilters = [
];
function validateOpData(aData, aDataType) {
let thisValidFields = validFields;
// A special case to update the GUID - in this case there can be a 'newGuid'
// field and of the normally valid fields, only 'guid' is accepted.
if (aDataType == "Update" && "newGuid" in aData) {
thisValidFields = ["guid", "newGuid"];
}
for (let field in aData) {
if (field != "op" && validFields.indexOf(field) == -1) {
if (field != "op" && thisValidFields.indexOf(field) == -1) {
throw Components.Exception(
aDataType + " query contains an unrecognized field: " + field,
Cr.NS_ERROR_ILLEGAL_VALUE);
@ -309,8 +320,8 @@ function makeUpdateStatement(aGuid, aNewData, aBindingArrays) {
Cr.NS_ERROR_ILLEGAL_VALUE);
}
query += queryTerms + " WHERE guid = :guid";
aNewData["guid"] = aGuid;
query += queryTerms + " WHERE guid = :existing_guid";
aNewData["existing_guid"] = aGuid;
return dbCreateAsyncStatement(query, aNewData, aBindingArrays);
}
@ -632,12 +643,18 @@ function updateFormHistoryWrite(aChanges, aCallbacks) {
if ("timeDeleted" in change)
delete change.timeDeleted;
stmt = makeRemoveStatement(change, bindingArrays);
notifications.push([ "formhistory-remove", null ]);
notifications.push([ "formhistory-remove", change.guid ]);
break;
case "update":
log("Update form history " + change);
let guid = change.guid;
delete change.guid;
// a special case for updating the GUID - the new value can be
// specified in newGuid.
if (change.newGuid) {
change.guid = change.newGuid
delete change.newGuid;
}
stmt = makeUpdateStatement(guid, change, bindingArrays);
notifications.push([ "formhistory-update", guid ]);
break;