mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge s-c to m-c
This commit is contained in:
commit
daffa2e519
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 \
|
||||
|
@ -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);
|
||||
|
||||
|
@ -35,7 +35,7 @@
|
||||
# [testSettingsMenuItems] # see bug 843947
|
||||
[testSystemPages]
|
||||
# [testPermissions] # see bug 757475
|
||||
# [testJarReader] # see bug 738890
|
||||
[testJarReader]
|
||||
[testDistribution]
|
||||
[testFindInPage]
|
||||
[testInputAwesomeBar]
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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.");
|
||||
|
@ -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");
|
||||
},
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 = {};
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
@ -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!");
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user