Merge f-t to m-c

This commit is contained in:
Phil Ringnalda 2014-03-23 08:27:12 -07:00
commit 93d92170f1
38 changed files with 813 additions and 181 deletions

View File

@ -6875,12 +6875,18 @@ let gRemoteTabsUI = {
* @return True if an existing tab was found, false otherwise
*/
function switchToTabHavingURI(aURI, aOpenNew, aOpenParams) {
// Certain URLs can be switched to irrespective of the source or destination
// window being in private browsing mode:
const kPrivateBrowsingWhitelist = new Set([
"about:customizing",
]);
// This will switch to the tab in aWindow having aURI, if present.
function switchIfURIInWindow(aWindow) {
// Only switch to the tab if neither the source and desination window are
// private and they are not in permanent private borwsing mode
if ((PrivateBrowsingUtils.isWindowPrivate(window) ||
PrivateBrowsingUtils.isWindowPrivate(aWindow)) &&
// Only switch to the tab if neither the source nor the destination window
// are private and they are not in permanent private browsing mode
if (!kPrivateBrowsingWhitelist.has(aURI.spec) &&
(PrivateBrowsingUtils.isWindowPrivate(window) ||
PrivateBrowsingUtils.isWindowPrivate(aWindow)) &&
!PrivateBrowsingUtils.permanentPrivateBrowsing) {
return false;
}

View File

@ -770,7 +770,6 @@ const CustomizableWidgets = [{
elem.section = aSection;
elem.value = item.value;
elem.setAttribute("class", "subviewbutton");
addShortcut(item, doc, elem);
containerElem.appendChild(elem);
}
},

View File

@ -12,7 +12,6 @@ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/SignInToWebsite.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
"resource:///modules/AboutHome.jsm");
@ -88,6 +87,11 @@ XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
"resource://gre/modules/AsyncShutdown.jsm");
#ifdef NIGHTLY_BUILD
XPCOMUtils.defineLazyModuleGetter(this, "SignInToWebsiteUX",
"resource:///modules/SignInToWebsite.jsm");
#endif
const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
const PREF_PLUGINS_UPDATEURL = "plugins.update.url";
@ -472,7 +476,11 @@ BrowserGlue.prototype = {
PageThumbs.init();
NewTabUtils.init();
BrowserNewTabPreloader.init();
SignInToWebsiteUX.init();
#ifdef NIGHTLY_BUILD
if (Services.prefs.getBoolPref("dom.identity.enabled")) {
SignInToWebsiteUX.init();
}
#endif
PdfJs.init();
#ifdef NIGHTLY_BUILD
ShumwayUtils.init();
@ -655,7 +663,11 @@ BrowserGlue.prototype = {
BrowserNewTabPreloader.uninit();
CustomizationTabPreloader.uninit();
WebappManager.uninit();
SignInToWebsiteUX.uninit();
#ifdef NIGHTLY_BUILD
if (Services.prefs.getBoolPref("dom.identity.enabled")) {
SignInToWebsiteUX.uninit();
}
#endif
webrtcUI.uninit();
},

View File

@ -186,7 +186,7 @@
</vbox>
<vbox id="noFxaAccount">
<label value="&welcome.description;"/>
<label>&welcome.description;</label>
<label class="text-link"
onclick="gSyncPane.signUp(); return false;"
value="&welcome.createAccount.label;"/>

View File

@ -9,6 +9,9 @@ function test() {
initNetMonitor(FILTERING_URL).then(([aTab, aDebuggee, aMonitor]) => {
info("Starting test... ");
// It seems that this test may be slow on Ubuntu builds running on ec2.
requestLongerTimeout(2);
let { $, NetMonitorView } = aMonitor.panelWin;
let { RequestsMenu } = NetMonitorView;

View File

@ -7,7 +7,7 @@
<Description>Find photos, movies, music, and text to rip, sample, mash, and share.</Description>
<InputEncoding>utf-8</InputEncoding>
<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAJUSURBVDiNjZO9S1thGMXPvTfJFbnkipNkLLS0ksFg0M0lf4CboNCEgIIg/RiKtEOn0qGWTtbVoBYcIji10I9J0ApWCjp0kRaXdhHjTW4+uGnur4NJ8GOwZ3nf4TnnfZ5z3scAdBGGYdyVdN+yrGHTNNOtVqsVhuG+pO+S3gE/LtV3BIxzPDJNc8FxHGN0dNRKpVIGoJ2dndr+/r5Vr9cl6bmkN0AoSQIEyHXdj5KYnZ3F932uolKpkM/nK5KQ9FmSCZwLOI7zQBLr6+vXiFdRLBaDtsiTTve3YrFYkM/nbyR3MDU1dSKpLumO+vr6Xruui+d5AFSrVVZWVtjY2KDRaABwdHTE4uIie3t7AJTLZaLRaFXSCyUSid1MJgOA53n09/eTTqdJJpPMzc2xurqKbduMj48Tj8fZ3d0FYHBw8FjSezmOU56fnwdgeXkZ27ap1WpUKhWazSZjY2Nks1kASqVSd4zp6eljSX/MtiHdRDpnEATyfb+bkiSVSqXu3TCM8xgHBga+dkY4OzvDdV2GhoZIJBLMzMxQKBSIRqNkMhlisRhbW1sAJJPJn5I+KB6Pv7poou/7rK2tsbm5SRAEXROXlpY4ODgAoFarYdu2J+llN8ZcLvffMeZyud+SGpLuCVBPT89jSRQKhRvJxWKxISmU9JTOT5Rk9Pb2fpHE5OQkJycn14inp6dMTEx4bdM/SbKAy8sk6WEkElmwLCuSSqUYGRmxgHB7e7t+eHgYazabgaRnkt7SeZnr63xbUtYwjGHTNNNhGP4F9iR9a6/zr4v1/wDE1D9XlC4rrAAAAABJRU5ErkJggg==</Image>
<Url type="text/html" method="GET" template="http://search.creativecommons.org/">
<Url type="text/html" method="GET" template="http://search.creativecommons.org/" resultdomain="creativecommons.org">
<Param name="q" value="{searchTerms}"/>
<Param name="sourceid" value="Mozilla-search"/>
</Url>

View File

@ -11,7 +11,7 @@
<Param name="s" value="0"/>
<Param name="q" value="{searchTerms}"/>
</Url>
<Url type="text/html" method="GET" template="http://rover.ebay.com/rover/1/711-47294-18009-3/4">
<Url type="text/html" method="GET" template="http://rover.ebay.com/rover/1/711-47294-18009-3/4" resultdomain="ebay.com">
<Param name="mpre" value="http://shop.ebay.com/?_nkw={searchTerms}"/>
</Url>
<SearchForm>http://search.ebay.com/</SearchForm>

View File

@ -11,7 +11,7 @@
<Param name="action" value="opensearch"/>
<Param name="search" value="{searchTerms}"/>
</Url>
<Url type="text/html" method="GET" template="http://en.wikipedia.org/wiki/Special:Search">
<Url type="text/html" method="GET" template="http://en.wikipedia.org/wiki/Special:Search" resultdomain="wikipedia.org">
<Param name="search" value="{searchTerms}"/>
<Param name="sourceid" value="Mozilla-search"/>
</Url>

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,6 @@ EXTRA_JS_MODULES += [
'NetworkPrioritizer.jsm',
'offlineAppCache.jsm',
'SharedFrame.jsm',
'SignInToWebsite.jsm',
'SitePermissions.jsm',
'Social.jsm',
'TabCrashReporter.jsm',
@ -31,6 +30,11 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
'WindowsPreviewPerTab.jsm',
]
if CONFIG['NIGHTLY_BUILD']:
EXTRA_JS_MODULES += [
'SignInToWebsite.jsm',
]
EXTRA_PP_JS_MODULES += [
'AboutHome.jsm',
'RecentWindow.jsm',

View File

@ -267,12 +267,22 @@ function test_auth() {
function test() {
waitForExplicitFinish();
let sitw = {};
try {
Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
} catch (ex) {
ok(true, "Skip the test since SignInToWebsite.jsm isn't packaged outside outside mozilla-central");
finish();
return;
}
registerCleanupFunction(cleanUp);
let sitw = {};
Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
ok(sitw.SignInToWebsiteUX, "SignInToWebsiteUX object exists");
if (!Services.prefs.getBoolPref("dom.identity.enabled")) {
// If the pref isn't enabled then init wasn't called so do that for the test.
sitw.SignInToWebsiteUX.init();
}
// Replace implementation of ID Service functions for testing
window.selectIdentity = sitw.SignInToWebsiteUX.selectIdentity;
@ -317,6 +327,9 @@ function cleanUp() {
Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
sitw.SignInToWebsiteUX.selectIdentity = window.selectIdentity;
delete window.selectIdentity;
if (!Services.prefs.getBoolPref("dom.identity.enabled")) {
sitw.SignInToWebsiteUX.uninit();
}
Services.prefs.clearUserPref("toolkit.identity.debug");
}

View File

@ -1574,6 +1574,16 @@ nsXPCComponents_utils_Sandbox::CallOrConstruct(nsIXPConnectWrappedNative *wrappe
if (NS_FAILED(AssembleSandboxMemoryReporterName(cx, options.sandboxName)))
return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
if (options.metadata.isNullOrUndefined()) {
// If the caller is running in a sandbox, inherit.
RootedObject callerGlobal(cx, CurrentGlobalOrNull(cx));
if (IsSandbox(callerGlobal)) {
rv = GetSandboxMetadata(cx, callerGlobal, &options.metadata);
if (NS_WARN_IF(NS_FAILED(rv)))
return rv;
}
}
rv = CreateSandboxObject(cx, args.rval(), prinOrSop, options);
if (NS_FAILED(rv))

View File

@ -41,5 +41,16 @@ function run_test()
}
do_check_eq(thrown, true);
sandbox = Components.utils.Sandbox(this, {
metadata: { foopy: { bar: 2 }, baz: "hi" }
});
let inner = Components.utils.evalInSandbox("Components.utils.Sandbox('http://www.blah.com')", sandbox);
metadata = Components.utils.getSandboxMetadata(inner);
do_check_eq(metadata.baz, "hi");
do_check_eq(metadata.foopy.bar, 2);
metadata.baz = "foo";
}

View File

@ -0,0 +1,80 @@
===================
Firefox for Android
===================
UI Telemetry
============
Fennec records UI events using a telemetry framework called UITelemetry.
Some links:
- `Project page <https://wiki.mozilla.org/Mobile/Projects/Telemetry_probes_for_Fennec_UI_elements>`_
- `Wiki page <https://wiki.mozilla.org/Mobile/Fennec/Android/UITelemetry>`_
- `User research notes <https://wiki.mozilla.org/Mobile/User_Experience/Research>`_
Sessions
--------
**Sessions** are essentially scopes. They are meant to provide context to
events; this allows events to be simpler and more reusable. Sessions are
usually bound to some component of the UI, some user action with a duration, or
some transient state.
For example, a session might be begun when a user begins interacting with a
menu, and stopped when the interaction ends. Or a session might encapsulate
period of no network connectivity, the first five seconds after the browser
launched, the time spent with an active download, or a guest mode session.
Sessions implicitly record the duration of the interaction.
A simple use-case for sessions is the bookmarks panel in about:home. We start a
session when the user swipes into the panel, and stop it when they swipe away.
This bookmarks session does two things: firstly, it gives scope to any generic
event that may occur within the panel (*e.g.*, loading a URL). Secondly, it
allows us to figure out how much time users are spending in the bookmarks
panel.
To start a session, call ``Telemetry.startUISession(String sessionName)``.
Session names should be brief, lowercase, and should describe which UI
component the user is interacting with. In certain cases where the UI component
is dynamic, they could include an ID, essential to identifying that component.
An example of this is dynamic home panels: we use session names of the format
``homepanel:<panel_id>`` to identify home panel sessions.
To stop a session call ``Telemetry.stopUISession(String sessionName, String
reason)``. ``sessionName`` is the name of the open session and ``reason`` is a
descriptive cause for the ending of the session. It should be brief, lowercase,
and generic so it can be reused in different places. Examples reasons are:
``switched``
The user transitioned to a UI element of equal level.
``exit``
The user left for an entirely different element.
Events
------
Events capture key occurrences. They should be brief and simple, and should not contain sensitive or excess information. Context for events should come from the session (scope). An event can be created with four fields (via ``Telemetry.sendUIEvent``): ``action``, ``method``, ``extras``, and ``timestamp``.
``action``
The name of the event. Should be brief and lowercase. If needed, you can make use of namespacing with a '``.``' separator. Example event names: ``panel.switch``, ``panel.enable``, ``panel.disable``, ``panel.install``.
``method`` (Optional)
Used for user actions that can be performed in many ways. This field specifies the method by which the action was performed. For example, users can add an item to their reading list either by long-tapping the reader icon in the address bar, or from within reader mode. We would use the same event name for both user actions but specify two methods: ``addressbar`` and ``readermode``.
``extras`` (Optional)
For extra information that may be useful in understanding the event. Make an effort to keep this brief.
``timestamp`` (Optional)
The time at which the event occurred. If not specified, this field defaults to the current value of the realtime clock.
Clock
-----
Times are relative to either elapsed realtime (an arbitrary monotonically increasing clock that continues to tick when the device is asleep), or elapsed uptime (which doesn't tick when the device is in deep sleep). We default to elapsed realtime.
See the documentation in `the source <http://mxr.mozilla.org/mozilla-central/source/mobile/android/base/Telemetry.java>`_ for more details.

View File

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += ['locales']
SPHINX_TREES['fennec'] = 'docs'
include('android-services.mozbuild')

View File

@ -22,7 +22,7 @@ interface nsISearchSubmission : nsISupports
readonly attribute nsIURI uri;
};
[scriptable, uuid(7914c4b8-f05b-40c9-a982-38a058cd1769)]
[scriptable, uuid(77de6680-57ec-4105-a183-cc7cf7e84b09)]
interface nsISearchEngine : nsISupports
{
/**
@ -157,6 +157,18 @@ interface nsISearchEngine : nsISupports
*/
readonly attribute AString identifier;
/**
* Gets a string representing the hostname from which search results for a
* given responseType are returned, minus the leading "www." (if present).
* This can be specified as an url attribute in the engine description file,
* but will default to host from the <Url>'s template otherwise.
*
* @param responseType [optional]
* The MIME type to get resultDomain for. Defaults to "text/html".
*
* @return the resultDomain for the given responseType.
*/
AString getResultDomain([optional] in AString responseType);
};
[scriptable, uuid(9fc39136-f08b-46d3-b232-96f4b7b0e235)]

View File

@ -428,7 +428,7 @@ function BookmarksStore(name, engine) {
// Explicitly nullify our references to our cached services so we don't leak
Svc.Obs.add("places-shutdown", function() {
for each ([query, stmt] in Iterator(this._stmts)) {
for each (let [query, stmt] in Iterator(this._stmts)) {
stmt.finalize();
}
this._stmts = {};

View File

@ -25,6 +25,24 @@ XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => new TextDecoder());
XPCOMUtils.defineLazyGetter(this, "gTextEncoder", () => new TextEncoder());
/**
* Generates an hash for the given string.
*
* @note The generated hash is returned in base64 form. Mind the fact base64
* is case-sensitive if you are going to reuse this code.
*/
function generateHash(aString) {
let cryptoHash = Cc["@mozilla.org/security/hash;1"]
.createInstance(Ci.nsICryptoHash);
cryptoHash.init(Ci.nsICryptoHash.MD5);
let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
stringStream.data = aString;
cryptoHash.updateFromStream(stringStream, -1);
// base64 allows the '/' char, but we can't use it for filenames.
return cryptoHash.finish(true).replace("/", "-", "g");
}
this.BookmarkJSONUtils = Object.freeze({
/**
* Import bookmarks from a url.
@ -99,13 +117,19 @@ this.BookmarkJSONUtils = Object.freeze({
*
* @param aFilePath
* OS.File path string for the "bookmarks.json" file to be created.
*
* @param [optional] aOptions
* Object containing options for the export:
* - failIfHashIs: if the generated file would have the same hash
* defined here, will reject with ex.becauseSameHash
* @return {Promise}
* @resolves To the exported bookmarks count when the file has been created.
* @resolves once the file has been created, to an object with the
* following properties:
* - count: number of exported bookmarks
* - hash: file hash for contents comparison
* @rejects JavaScript exception.
* @deprecated passing an nsIFile is deprecated
*/
exportToFile: function BJU_exportToFile(aFilePath) {
exportToFile: function BJU_exportToFile(aFilePath, aOptions={}) {
if (aFilePath instanceof Ci.nsIFile) {
Deprecated.warning("Passing an nsIFile to BookmarksJSONUtils.exportToFile " +
"is deprecated. Please use an OS.File path string instead.",
@ -125,12 +149,29 @@ this.BookmarkJSONUtils = Object.freeze({
Components.utils.reportError("Unable to report telemetry.");
}
startTime = Date.now();
let hash = generateHash(jsonString);
// Report the time taken to generate the hash.
try {
Services.telemetry
.getHistogramById("PLACES_BACKUPS_HASHING_MS")
.add(Date.now() - startTime);
} catch (ex) {
Components.utils.reportError("Unable to report telemetry.");
}
if (hash === aOptions.failIfHashIs) {
let e = new Error("Hash conflict");
e.becauseSameHash = true;
throw e;
}
// Do not write to the tmp folder, otherwise if it has a different
// filesystem writeAtomic will fail. Eventual dangling .tmp files should
// be cleaned up by the caller.
yield OS.File.writeAtomic(aFilePath, jsonString,
{ tmpPath: OS.Path.join(aFilePath + ".tmp") });
return count;
return { count: count, hash: hash };
});
},

View File

@ -28,12 +28,71 @@ XPCOMUtils.defineLazyGetter(this, "localFileCtor",
() => Components.Constructor("@mozilla.org/file/local;1",
"nsILocalFile", "initWithPath"));
XPCOMUtils.defineLazyGetter(this, "filenamesRegex",
() => new RegExp("^bookmarks-([0-9\-]+)(?:_([0-9]+)){0,1}(?:_([a-z0-9=\+\-]{24})){0,1}\.(json|html)", "i")
);
/**
* Appends meta-data information to a given filename.
*/
function appendMetaDataToFilename(aFilename, aMetaData) {
let matches = aFilename.match(filenamesRegex);
return "bookmarks-" + matches[1] +
"_" + aMetaData.count +
"_" + aMetaData.hash +
"." + matches[4];
}
/**
* Gets the hash from a backup filename.
*
* @return the extracted hash or null.
*/
function getHashFromFilename(aFilename) {
let matches = aFilename.match(filenamesRegex);
if (matches && matches[3])
return matches[3];
return null;
}
/**
* Given two filenames, checks if they contain the same date.
*/
function isFilenameWithSameDate(aSourceName, aTargetName) {
let sourceMatches = aSourceName.match(filenamesRegex);
let targetMatches = aTargetName.match(filenamesRegex);
return sourceMatches && targetMatches &&
sourceMatches[1] == targetMatches[1] &&
sourceMatches[4] == targetMatches[4];
}
/**
* Given a filename, searches for another backup with the same date.
*
* @return OS.File path string or null.
*/
function getBackupFileForSameDate(aFilename) {
return Task.spawn(function* () {
let backupFiles = yield PlacesBackups.getBackupFiles();
for (let backupFile of backupFiles) {
if (isFilenameWithSameDate(OS.Path.basename(backupFile), aFilename))
return backupFile;
}
return null;
});
}
this.PlacesBackups = {
get _filenamesRegex() {
delete this._filenamesRegex;
return this._filenamesRegex =
new RegExp("^(bookmarks)-([0-9-]+)(_[0-9]+)*\.(json|html)");
},
/**
* Matches the backup filename:
* 0: file name
* 1: date in form Y-m-d
* 2: bookmarks count
* 3: contents hash
* 4: file extension
*/
get filenamesRegex() filenamesRegex,
get folder() {
Deprecated.warning(
@ -99,8 +158,7 @@ this.PlacesBackups = {
let entry = files.getNext().QueryInterface(Ci.nsIFile);
// A valid backup is any file that matches either the localized or
// not-localized filename (bug 445704).
let matches = entry.leafName.match(this._filenamesRegex);
if (!entry.isHidden() && matches) {
if (!entry.isHidden() && filenamesRegex.test(entry.leafName)) {
// Remove bogus backups in future dates.
if (this.getDateForFile(entry) > new Date()) {
entry.remove(false);
@ -139,8 +197,7 @@ this.PlacesBackups = {
return;
}
let matches = aEntry.name.match(this._filenamesRegex);
if (matches) {
if (filenamesRegex.test(aEntry.name)) {
// Remove bogus backups in future dates.
let filePath = aEntry.path;
if (this.getDateForFile(filePath) > new Date()) {
@ -188,10 +245,10 @@ this.PlacesBackups = {
getDateForFile: function PB_getDateForFile(aBackupFile) {
let filename = (aBackupFile instanceof Ci.nsIFile) ? aBackupFile.leafName
: OS.Path.basename(aBackupFile);
let matches = filename.match(this._filenamesRegex);
let matches = filename.match(filenamesRegex);
if (!matches)
throw("Invalid backup file name: " + filename);
return new Date(matches[2].replace(/-/g, "/"));
return new Date(matches[1].replace(/-/g, "/"));
},
/**
@ -258,7 +315,8 @@ this.PlacesBackups = {
aFilePath = aFilePath.path;
}
return Task.spawn(function* () {
let nodeCount = yield BookmarkJSONUtils.exportToFile(aFilePath);
let { count: nodeCount, hash: hash } =
yield BookmarkJSONUtils.exportToFile(aFilePath);
let backupFolderPath = yield this.getBackupFolder();
if (OS.Path.dirname(aFilePath) == backupFolderPath) {
@ -274,22 +332,33 @@ this.PlacesBackups = {
// we also want to copy this new backup to it.
// This way we ensure the latest valid backup is the same saved by the
// user. See bug 424389.
let name = this.getFilenameForDate();
let newFilename = this._appendMetaDataToFilename(name,
{ nodeCount: nodeCount });
let newFilePath = OS.Path.join(backupFolderPath, newFilename);
let backupFile = yield this._getBackupFileForSameDate(name);
if (!backupFile) {
// Update internal cache if we are not replacing an existing
// backup file.
this._entries.unshift(new localFileCtor(newFilePath));
if (!this._backupFiles) {
yield this.getBackupFiles();
let mostRecentBackupFile = yield this.getMostRecentBackup("json");
if (!mostRecentBackupFile ||
hash != getHashFromFilename(OS.Path.basename(mostRecentBackupFile))) {
let name = this.getFilenameForDate();
let newFilename = appendMetaDataToFilename(name,
{ count: nodeCount,
hash: hash });
let newFilePath = OS.Path.join(backupFolderPath, newFilename);
let backupFile = yield getBackupFileForSameDate(name);
if (backupFile) {
// There is already a backup for today, replace it.
yield OS.File.remove(backupFile, { ignoreAbsent: true });
if (!this._backupFiles)
yield this.getBackupFiles();
else
this._backupFiles.shift();
this._backupFiles.unshift(newFilePath);
} else {
// There is no backup for today, add the new one.
this._entries.unshift(new localFileCtor(newFilePath));
if (!this._backupFiles)
yield this.getBackupFiles();
this._backupFiles.unshift(newFilePath);
}
this._backupFiles.unshift(newFilePath);
}
yield OS.File.copy(aFilePath, newFilePath);
yield OS.File.copy(aFilePath, newFilePath);
}
}
return nodeCount;
@ -303,83 +372,90 @@ this.PlacesBackups = {
* "places/excludeFromBackup".
*
* @param [optional] int aMaxBackups
* The maximum number of backups to keep.
* The maximum number of backups to keep. If set to 0
* all existing backups are removed and aForceBackup is
* ignored, so a new one won't be created.
* @param [optional] bool aForceBackup
* Forces creating a backup even if one was already
* created that day (overwrites).
* @return {Promise}
*/
create: function PB_create(aMaxBackups, aForceBackup) {
let limitBackups = function* () {
let backupFiles = yield this.getBackupFiles();
if (typeof aMaxBackups == "number" && aMaxBackups > -1 &&
backupFiles.length >= aMaxBackups) {
let numberOfBackupsToDelete = backupFiles.length - aMaxBackups;
while (numberOfBackupsToDelete--) {
this._entries.pop();
let oldestBackup = this._backupFiles.pop();
yield OS.File.remove(oldestBackup);
}
}
}.bind(this);
return Task.spawn(function* () {
// Construct the new leafname.
if (aMaxBackups === 0) {
// Backups are disabled, delete any existing one and bail out.
yield limitBackups(0);
return;
}
// Ensure to initialize _backupFiles
if (!this._backupFiles)
yield this.getBackupFiles();
let newBackupFilename = this.getFilenameForDate();
let mostRecentBackupFile = yield this.getMostRecentBackup();
// If we already have a backup for today we should do nothing, unless we
// were required to enforce a new backup.
let backupFile = yield getBackupFileForSameDate(newBackupFilename);
if (backupFile && !aForceBackup)
return;
if (!aForceBackup) {
let backupFiles = yield this.getBackupFiles();
// If there are backups, limit them to aMaxBackups, if requested.
if (backupFiles.length > 0 && typeof aMaxBackups == "number" &&
aMaxBackups > -1 && backupFiles.length >= aMaxBackups) {
let numberOfBackupsToDelete = backupFiles.length - aMaxBackups;
// If we don't have today's backup, remove one more so that
// the total backups after this operation does not exceed the
// number specified in the pref.
if (!this._isFilenameWithSameDate(OS.Path.basename(mostRecentBackupFile),
newBackupFilename)) {
numberOfBackupsToDelete++;
}
while (numberOfBackupsToDelete--) {
this._entries.pop();
let oldestBackup = this._backupFiles.pop();
yield OS.File.remove(oldestBackup);
}
}
// Do nothing if we already have this backup or we don't want backups.
if (aMaxBackups === 0 ||
(mostRecentBackupFile &&
this._isFilenameWithSameDate(OS.Path.basename(mostRecentBackupFile),
newBackupFilename)))
return;
}
let backupFile = yield this._getBackupFileForSameDate(newBackupFilename);
if (backupFile) {
if (aForceBackup) {
yield OS.File.remove(backupFile, { ignoreAbsent: true });
} else {
return;
}
// In case there is a backup for today we should recreate it.
this._backupFiles.shift();
this._entries.shift();
yield OS.File.remove(backupFile, { ignoreAbsent: true });
}
// Now check the hash of the most recent backup, and try to create a new
// backup, if that fails due to hash conflict, just rename the old backup.
let mostRecentBackupFile = yield this.getMostRecentBackup();
let mostRecentHash = mostRecentBackupFile &&
getHashFromFilename(OS.Path.basename(mostRecentBackupFile));
// Save bookmarks to a backup file.
let backupFolder = yield this.getBackupFolder();
let newBackupFile = OS.Path.join(backupFolder, newBackupFilename);
let nodeCount = yield this.saveBookmarksToJSONFile(newBackupFile);
// Rename the filename with metadata.
let newFilenameWithMetaData = this._appendMetaDataToFilename(
newBackupFilename,
{ nodeCount: nodeCount });
let newFilenameWithMetaData;
try {
let { count: nodeCount, hash: hash } =
yield BookmarkJSONUtils.exportToFile(newBackupFile,
{ failIfHashIs: mostRecentHash });
newFilenameWithMetaData = appendMetaDataToFilename(newBackupFilename,
{ count: nodeCount,
hash: hash });
} catch (ex if ex.becauseSameHash) {
// The last backup already contained up-to-date information, just
// rename it as if it was today's backup.
this._backupFiles.shift();
this._entries.shift();
newBackupFile = mostRecentBackupFile;
newFilenameWithMetaData = appendMetaDataToFilename(
newBackupFilename,
{ count: this.getBookmarkCountForFile(mostRecentBackupFile),
hash: mostRecentHash });
}
// Append metadata to the backup filename.
let newBackupFileWithMetadata = OS.Path.join(backupFolder, newFilenameWithMetaData);
yield OS.File.move(newBackupFile, newBackupFileWithMetadata);
// Update internal cache.
let newFileWithMetaData = new localFileCtor(newBackupFileWithMetadata);
this._entries.pop();
this._entries.unshift(newFileWithMetaData);
this._backupFiles.pop();
this._entries.unshift(new localFileCtor(newBackupFileWithMetadata));
this._backupFiles.unshift(newBackupFileWithMetadata);
}.bind(this));
},
_appendMetaDataToFilename:
function PB__appendMetaDataToFilename(aFilename, aMetaData) {
let matches = aFilename.match(this._filenamesRegex);
let newFilename = matches[1] + "-" + matches[2] + "_" +
aMetaData.nodeCount + "." + matches[4];
return newFilename;
// Limit the number of backups.
yield limitBackups(aMaxBackups);
}.bind(this));
},
/**
@ -393,43 +469,12 @@ this.PlacesBackups = {
getBookmarkCountForFile: function PB_getBookmarkCountForFile(aFilePath) {
let count = null;
let filename = OS.Path.basename(aFilePath);
let matches = filename.match(this._filenamesRegex);
if (matches && matches[3])
count = matches[3].replace(/_/g, "");
let matches = filename.match(filenamesRegex);
if (matches && matches[2])
count = matches[2];
return count;
},
_isFilenameWithSameDate:
function PB__isFilenameWithSameDate(aSourceName, aTargetName) {
let sourceMatches = aSourceName.match(this._filenamesRegex);
let targetMatches = aTargetName.match(this._filenamesRegex);
return (sourceMatches && targetMatches &&
sourceMatches[1] == targetMatches[1] &&
sourceMatches[2] == targetMatches[2] &&
sourceMatches[4] == targetMatches[4]);
},
_getBackupFileForSameDate:
function PB__getBackupFileForSameDate(aFilename) {
return Task.spawn(function* () {
let backupFolderPath = yield this.getBackupFolder();
let iterator = new OS.File.DirectoryIterator(backupFolderPath);
let backupFile;
yield iterator.forEach(function(aEntry) {
if (this._isFilenameWithSameDate(aEntry.name, aFilename)) {
backupFile = aEntry.path;
return iterator.close();
}
}.bind(this));
yield iterator.close();
return backupFile;
}.bind(this));
},
/**
* Gets a bookmarks tree representation usable to create backups in different
* file formats. The root or the tree is PlacesUtils.placesRootId.

View File

@ -0,0 +1,142 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = [ "PriorityUrlProvider" ];
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Task.jsm");
/**
* Provides search engines matches to the PriorityUrlProvider through the
* search engines definitions handled by the Search Service.
*/
const SEARCH_ENGINE_TOPIC = "browser-search-engine-modified";
let SearchEnginesProvider = {
init: function () {
this._engines = new Map();
let deferred = Promise.defer();
Services.search.init(rv => {
if (Components.isSuccessCode(rv)) {
Services.search.getVisibleEngines().forEach(this._addEngine, this);
deferred.resolve();
} else {
deferred.reject(new Error("Unable to initialize search service."));
}
});
Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC, true);
return deferred.promise;
},
observe: function (engine, topic, verb) {
let engine = engine.QueryInterface(Ci.nsISearchEngine);
switch (verb) {
case "engine-added":
this._addEngine(engine);
break;
case "engine-changed":
if (engine.hidden) {
this._removeEngine(engine);
} else {
this._addEngine(engine);
}
break;
case "engine-removed":
this._removeEngine(engine);
break;
}
},
_addEngine: function (engine) {
if (this._engines.has(engine.name)) {
return;
}
let token = engine.getResultDomain();
if (!token) {
return;
}
let match = { token: token,
// TODO (bug 557665): searchForm should provide an usable
// url with affiliate code, if available.
url: engine.searchForm,
title: engine.name,
iconUrl: engine.iconURI ? engine.iconURI.spec : null,
reason: "search" }
this._engines.set(engine.name, match);
PriorityUrlProvider.addMatch(match);
},
_removeEngine: function (engine) {
if (!this._engines.has(engine.name)) {
return;
}
this._engines.delete(engine.name);
PriorityUrlProvider.removeMatchByToken(engine.getResultDomain());
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference])
}
/**
* The PriorityUrlProvider allows to match a given string to a list of
* urls that should have priority in url search components, like autocomplete.
* Each returned match is an object with the following properties:
* - token: string used to match the search term to the url
* - url: url string represented by the match
* - title: title describing the match, or an empty string if not available
* - iconUrl: url of the icon associated to the match, or null if not available
* - reason: a string describing the origin of the match, for example if it
* represents a search engine, it will be "search".
*/
let matches = new Map();
let initialized = false;
function promiseInitialized() {
if (initialized) {
return Promise.resolve();
}
return Task.spawn(function* () {
try {
yield SearchEnginesProvider.init();
} catch (ex) {
Cu.reportError(ex);
}
initialized = true;
});
}
this.PriorityUrlProvider = Object.freeze({
addMatch: function (match) {
matches.set(match.token, match);
},
removeMatchByToken: function (token) {
matches.delete(token);
},
getMatchingSpec: function (searchToken) {
return Task.spawn(function* () {
yield promiseInitialized();
for (let [token, match] of matches.entries()) {
// Match at the beginning for now. In future an aOptions argument may
// allow to control the matching behavior.
if (token.startsWith(searchToken)) {
return match;
}
}
return null;
}.bind(this));
}
});

View File

@ -68,6 +68,7 @@ if CONFIG['MOZ_PLACES']:
'PlacesBackups.jsm',
'PlacesDBUtils.jsm',
'PlacesTransactions.jsm',
'PriorityUrlProvider.jsm'
]
EXTRA_PP_JS_MODULES += [

View File

@ -27,7 +27,6 @@ add_task(function check_max_backups_is_respected() {
// Allow 2 backups, the older one should be removed.
yield PlacesBackups.create(2);
let backupFilename = PlacesBackups.getFilenameForDate();
let re = new RegExp("^" + backupFilename.replace(/\.json/, "") + "(_[0-9]+){0,1}\.json$");
let count = 0;
let lastBackupPath = null;
@ -35,7 +34,7 @@ add_task(function check_max_backups_is_respected() {
try {
yield iterator.forEach(aEntry => {
count++;
if (aEntry.name.match(re))
if (PlacesBackups.filenamesRegex.test(aEntry.name))
lastBackupPath = aEntry.path;
});
} finally {
@ -56,7 +55,6 @@ add_task(function check_max_backups_greater_than_backups() {
// Allow 3 backups, none should be removed.
yield PlacesBackups.create(3);
let backupFilename = PlacesBackups.getFilenameForDate();
let re = new RegExp("^" + backupFilename.replace(/\.json/, "") + "(_[0-9]+){0,1}\.json$");
let count = 0;
let lastBackupPath = null;
@ -64,7 +62,7 @@ add_task(function check_max_backups_greater_than_backups() {
try {
yield iterator.forEach(aEntry => {
count++;
if (aEntry.name.match(re))
if (PlacesBackups.filenamesRegex.test(aEntry.name))
lastBackupPath = aEntry.path;
});
} finally {
@ -83,7 +81,6 @@ add_task(function check_max_backups_null() {
// since one for today already exists.
yield PlacesBackups.create(null);
let backupFilename = PlacesBackups.getFilenameForDate();
let re = new RegExp("^" + backupFilename.replace(/\.json/, "") + "(_[0-9]+){0,1}\.json$");
let count = 0;
let lastBackupPath = null;
@ -91,7 +88,7 @@ add_task(function check_max_backups_null() {
try {
yield iterator.forEach(aEntry => {
count++;
if (aEntry.name.match(re))
if (PlacesBackups.filenamesRegex.test(aEntry.name))
lastBackupPath = aEntry.path;
});
} finally {
@ -110,7 +107,6 @@ add_task(function check_max_backups_undefined() {
// since one for today already exists.
yield PlacesBackups.create();
let backupFilename = PlacesBackups.getFilenameForDate();
let re = new RegExp("^" + backupFilename.replace(/\.json/, "") + "(_[0-9]+){0,1}\.json$");
let count = 0;
let lastBackupPath = null;
@ -118,7 +114,7 @@ add_task(function check_max_backups_undefined() {
try {
yield iterator.forEach(aEntry => {
count++;
if (aEntry.name.match(re))
if (PlacesBackups.filenamesRegex.test(aEntry.name))
lastBackupPath = aEntry.path;
});
} finally {

View File

@ -22,11 +22,10 @@ function run_test() {
dateObj.setYear(dateObj.getFullYear() + 1);
let name = PlacesBackups.getFilenameForDate(dateObj);
do_check_eq(name, "bookmarks-" + dateObj.toLocaleFormat("%Y-%m-%d") + ".json");
let rx = new RegExp("^" + name.replace(/\.json/, "") + "(_[0-9]+){0,1}\.json$");
let files = bookmarksBackupDir.directoryEntries;
while (files.hasMoreElements()) {
let entry = files.getNext().QueryInterface(Ci.nsIFile);
if (entry.leafName.match(rx))
if (PlacesBackups.filenamesRegex.test(entry.leafName))
entry.remove(false);
}
@ -43,8 +42,7 @@ function run_test() {
let mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
do_check_neq(mostRecentBackupFile, null);
let todayFilename = PlacesBackups.getFilenameForDate();
rx = new RegExp("^" + todayFilename.replace(/\.json/, "") + "(_[0-9]+){0,1}\.json$");
do_check_true(OS.Path.basename(mostRecentBackupFile).match(rx).length > 0);
do_check_true(PlacesBackups.filenamesRegex.test(OS.Path.basename(mostRecentBackupFile)));
// Check that future backup has been removed.
do_check_false(futureBackupFile.exists());

View File

@ -0,0 +1,59 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Checks that automatically created bookmark backups are discarded if they are
* duplicate of an existing ones.
*/
function run_test() {
run_next_test();
}
add_task(function() {
// Create a backup for yesterday in the backups folder.
let backupFolder = yield PlacesBackups.getBackupFolder();
let dateObj = new Date();
dateObj.setDate(dateObj.getDate() - 1);
let oldBackupName = PlacesBackups.getFilenameForDate(dateObj);
let oldBackup = OS.Path.join(backupFolder, oldBackupName);
let {count: count, hash: hash} = yield BookmarkJSONUtils.exportToFile(oldBackup);
do_check_true(count > 0);
do_check_eq(hash.length, 24);
oldBackupName = oldBackupName.replace(/\.json/, "_" + count + "_" + hash + ".json");
yield OS.File.move(oldBackup, OS.Path.join(backupFolder, oldBackupName));
// Create a backup.
// This should just rename the existing backup, so in the end there should be
// only one backup with today's date.
yield PlacesBackups.create();
// Get the hash of the generated backup
let backupFiles = yield PlacesBackups.getBackupFiles();
do_check_eq(backupFiles.length, 1);
let matches = OS.Path.basename(backupFiles[0]).match(PlacesBackups.filenamesRegex);
do_check_eq(matches[1], new Date().toLocaleFormat("%Y-%m-%d"));
do_check_eq(matches[2], count);
do_check_eq(matches[3], hash);
// Add a bookmark and create another backup.
let bookmarkId = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarks.bookmarksMenuFolder,
uri("http://foo.com"),
PlacesUtils.bookmarks.DEFAULT_INDEX,
"foo");
// We must enforce a backup since one for today already exists. The forced
// backup will replace the existing one.
yield PlacesBackups.create(undefined, true);
do_check_eq(backupFiles.length, 1);
recentBackup = yield PlacesBackups.getMostRecentBackup();
do_check_neq(recentBackup, OS.Path.join(backupFolder, oldBackupName));
matches = OS.Path.basename(recentBackup).match(PlacesBackups.filenamesRegex);
do_check_eq(matches[1], new Date().toLocaleFormat("%Y-%m-%d"));
do_check_eq(matches[2], count + 1);
do_check_neq(matches[3], hash);
// Clean up
PlacesUtils.bookmarks.removeItem(bookmarkId);
yield PlacesBackups.create(0);
});

View File

@ -31,9 +31,9 @@ add_task(function test_saveBookmarksToJSONFile_and_create()
// Ensure the backup would be copied to our backups folder when the original
// backup is saved somewhere else.
let recentBackup = yield PlacesBackups.getMostRecentBackup();
let todayFilename = PlacesBackups.getFilenameForDate();
do_check_eq(OS.Path.basename(recentBackup),
todayFilename.replace(/\.json/, "_" + nodeCount + ".json"));
let matches = OS.Path.basename(recentBackup).match(PlacesBackups.filenamesRegex);
do_check_eq(matches[2], nodeCount);
do_check_eq(matches[3].length, 24);
// Clear all backups in our backups folder.
yield PlacesBackups.create(0);
@ -45,9 +45,9 @@ add_task(function test_saveBookmarksToJSONFile_and_create()
mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
do_check_neq(mostRecentBackupFile, null);
let rx = new RegExp("^" + todayFilename.replace(/\.json/, "") + "_([0-9]+)\.json$");
let matches = OS.Path.basename(recentBackup).match(rx);
do_check_true(matches.length > 0 && parseInt(matches[1]) == nodeCount);
matches = OS.Path.basename(recentBackup).match(PlacesBackups.filenamesRegex);
do_check_eq(matches[2], nodeCount);
do_check_eq(matches[3].length, 24);
// Cleanup
backupFile.remove(false);

View File

@ -29,3 +29,4 @@ tail =
[test_711914.js]
[test_protectRoots.js]
[test_818593-store-backup-metadata.js]
[test_818584-discard-duplicate-backups.js]

View File

@ -522,10 +522,9 @@ function check_JSON_backup(aIsAutomaticBackup) {
bookmarksBackupDir.append("bookmarkbackups");
let files = bookmarksBackupDir.directoryEntries;
let backup_date = new Date().toLocaleFormat("%Y-%m-%d");
let rx = new RegExp("^bookmarks-" + backup_date + "_[0-9]+\.json$");
while (files.hasMoreElements()) {
let entry = files.getNext().QueryInterface(Ci.nsIFile);
if (entry.leafName.match(rx)) {
if (PlacesBackups.filenamesRegex.test(entry.leafName)) {
profileBookmarksJSONFile = entry;
break;
}

View File

@ -0,0 +1,74 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Cu.import("resource://gre/modules/PriorityUrlProvider.jsm");
function run_test() {
run_next_test();
}
add_task(function* search_engine_match() {
let engine = yield promiseDefaultSearchEngine();
let token = engine.getResultDomain();
let match = yield PriorityUrlProvider.getMatchingSpec(token.substr(0, 1));
do_check_eq(match.url, engine.searchForm);
do_check_eq(match.title, engine.name);
do_check_eq(match.iconUrl, engine.iconURI ? engine.iconURI.spec : null);
do_check_eq(match.reason, "search");
});
add_task(function* no_match() {
do_check_eq(null, yield PriorityUrlProvider.getMatchingSpec("test"));
});
add_task(function* hide_search_engine_nomatch() {
let engine = yield promiseDefaultSearchEngine();
let token = engine.getResultDomain();
let promiseTopic = promiseSearchTopic("engine-changed");
Services.search.removeEngine(engine);
yield promiseTopic;
do_check_true(engine.hidden);
do_check_eq(null, yield PriorityUrlProvider.getMatchingSpec(token.substr(0, 1)));
});
add_task(function* add_search_engine_match() {
let promiseTopic = promiseSearchTopic("engine-added");
do_check_eq(null, yield PriorityUrlProvider.getMatchingSpec("bacon"));
Services.search.addEngineWithDetails("bacon", "", "bacon", "Search Bacon",
"GET", "http://www.bacon.moz/?search={searchTerms}");
yield promiseSearchTopic;
let match = yield PriorityUrlProvider.getMatchingSpec("bacon");
do_check_eq(match.url, "http://www.bacon.moz");
do_check_eq(match.title, "bacon");
do_check_eq(match.iconUrl, null);
do_check_eq(match.reason, "search");
});
add_task(function* remove_search_engine_nomatch() {
let engine = Services.search.getEngineByName("bacon");
let promiseTopic = promiseSearchTopic("engine-removed");
Services.search.removeEngine(engine);
yield promiseTopic;
do_check_eq(null, yield PriorityUrlProvider.getMatchingSpec("bacon"));
});
function promiseDefaultSearchEngine() {
let deferred = Promise.defer();
Services.search.init( () => {
deferred.resolve(Services.search.defaultEngine);
});
return deferred.promise;
}
function promiseSearchTopic(expectedVerb) {
let deferred = Promise.defer();
Services.obs.addObserver( function observe(subject, topic, verb) {
do_log_info("browser-search-engine-modified: " + verb);
if (verb == expectedVerb) {
Services.obs.removeObserver(observe, "browser-search-engine-modified");
deferred.resolve();
}
}, "browser-search-engine-modified", false);
return deferred.promise;
}

View File

@ -8,8 +8,6 @@
* Check for correct functionality of bookmarks backups
*/
const PREFIX = "bookmarks-";
const SUFFIX = ".json";
const NUMBER_OF_BACKUPS = 10;
function run_test() {
@ -40,7 +38,7 @@ add_task(function () {
// creation time.
// Create fake backups for the newest dates.
for (let i = dates.length - 1; i >= 0; i--) {
let backupFilename = PREFIX + dates[i] + SUFFIX;
let backupFilename = PlacesBackups.getFilenameForDate(new Date(dates[i]));
let backupFile = bookmarksBackupDir.clone();
backupFile.append(backupFilename);
backupFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8));
@ -61,10 +59,9 @@ add_task(function () {
let backupFile;
if (i > 0) {
let files = bookmarksBackupDir.directoryEntries;
let rx = new RegExp("^" + PREFIX + dates[i] + "(_[0-9]+){0,1}" + SUFFIX + "$");
while (files.hasMoreElements()) {
let entry = files.getNext().QueryInterface(Ci.nsIFile);
if (entry.leafName.match(rx)) {
if (PlacesBackups.filenamesRegex.test(entry.leafName)) {
backupFilename = entry.leafName;
backupFile = entry;
break;
@ -73,7 +70,7 @@ add_task(function () {
shouldExist = true;
}
else {
backupFilename = PREFIX + dates[i] + SUFFIX;
backupFilename = PlacesBackups.getFilenameForDate(new Date(dates[i]));
backupFile = bookmarksBackupDir.clone();
backupFile.append(backupFilename);
shouldExist = false;

View File

@ -64,6 +64,7 @@ skip-if = os == "android"
# Bug 676989: test hangs consistently on Android
skip-if = os == "android"
[test_async_history_api.js]
[test_async_transactions.js]
[test_autocomplete_stopSearch_no_throw.js]
[test_bookmark_catobs.js]
[test_bookmarks_json.js]
@ -86,6 +87,7 @@ fail-if = os == "android"
# Bug 676989: test hangs consistently on Android
skip-if = os == "android"
[test_getChildIndex.js]
[test_getPlacesInfo.js]
[test_history.js]
[test_history_autocomplete_tags.js]
[test_history_catobs.js]
@ -110,7 +112,11 @@ fail-if = os == "android"
skip-if = true
[test_null_interfaces.js]
[test_onItemChanged_tags.js]
[test_pageGuid_bookmarkGuid.js]
[test_placeURIs.js]
[test_PlacesUtils_asyncGetBookmarkIds.js]
[test_PlacesUtils_lazyobservers.js]
[test_placesTxn.js]
[test_preventive_maintenance.js]
# Bug 676989: test hangs consistently on Android
skip-if = os == "android"
@ -118,6 +124,7 @@ skip-if = os == "android"
# Bug 676989: test hangs consistently on Android
skip-if = os == "android"
[test_preventive_maintenance_runTasks.js]
[test_priorityUrlProvider.js]
[test_removeVisitsByTimeframe.js]
# Bug 676989: test hangs consistently on Android
skip-if = os == "android"
@ -126,16 +133,10 @@ skip-if = os == "android"
[test_sql_guid_functions.js]
[test_tag_autocomplete_search.js]
[test_tagging.js]
[test_telemetry.js]
[test_update_frecency_after_delete.js]
# Bug 676989: test hangs consistently on Android
skip-if = os == "android"
[test_utils_backups_create.js]
[test_utils_getURLsForContainerNode.js]
[test_utils_setAnnotationsFor.js]
[test_PlacesUtils_asyncGetBookmarkIds.js]
[test_PlacesUtils_lazyobservers.js]
[test_placesTxn.js]
[test_telemetry.js]
[test_getPlacesInfo.js]
[test_pageGuid_bookmarkGuid.js]
[test_async_transactions.js]

View File

@ -861,12 +861,14 @@ function ParamSubstitution(aParamValue, aSearchTerms, aEngine) {
* The URL to which search queries should be sent. For GET requests,
* must contain the string "{searchTerms}", to indicate where the user
* entered search terms should be inserted.
* @param aResultDomain
* The root domain for this URL. Defaults to the template's host.
*
* @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag
*
* @throws NS_ERROR_NOT_IMPLEMENTED if aType is unsupported.
*/
function EngineURL(aType, aMethod, aTemplate) {
function EngineURL(aType, aMethod, aTemplate, aResultDomain) {
if (!aType || !aMethod || !aTemplate)
FAIL("missing type, method or template for EngineURL!");
@ -898,6 +900,14 @@ function EngineURL(aType, aMethod, aTemplate) {
default:
FAIL("new EngineURL: template uses invalid scheme!", Cr.NS_ERROR_FAILURE);
}
// If no resultDomain was specified in the engine definition file, use the
// host from the template.
this.resultDomain = aResultDomain || templateURI.host;
// We never want to return a "www." prefix, so eventually strip it.
if (this.resultDomain.startsWith("www.")) {
this.resultDomain = this.resultDomain.substr(4);
}
}
EngineURL.prototype = {
@ -1018,7 +1028,8 @@ EngineURL.prototype = {
_serializeToJSON: function SRCH_EURL__serializeToJSON() {
var json = {
template: this.template,
rels: this.rels
rels: this.rels,
resultDomain: this.resultDomain
};
if (this.type != URLTYPE_SEARCH_HTML)
@ -1049,6 +1060,8 @@ EngineURL.prototype = {
url.setAttribute("template", this.template);
if (this.rels.length)
url.setAttribute("rel", this.rels.join(" "));
if (this.resultDomain)
url.setAttribute("resultDomain", this.resultDomain);
for (var i = 0; i < this.params.length; ++i) {
var param = aDoc.createElementNS(OPENSEARCH_NS_11, "Param");
@ -1770,9 +1783,10 @@ Engine.prototype = {
// specified
var method = aElement.getAttribute("method") || "GET";
var template = aElement.getAttribute("template");
var resultDomain = aElement.getAttribute("resultdomain");
try {
var url = new EngineURL(type, method, template);
var url = new EngineURL(type, method, template, resultDomain);
} catch (ex) {
FAIL("_parseURL: failed to add " + template + " as a URL",
Cr.NS_ERROR_FAILURE);
@ -2271,7 +2285,8 @@ Engine.prototype = {
for (let i = 0; i < aJson._urls.length; ++i) {
let url = aJson._urls[i];
let engineURL = new EngineURL(url.type || URLTYPE_SEARCH_HTML,
url.method || "GET", url.template);
url.method || "GET", url.template,
url.resultDomain);
engineURL._initWithJSON(url, this);
this._urls.push(engineURL);
}
@ -2719,6 +2734,25 @@ Engine.prototype = {
return (this._getURLOfType(type) != null);
},
// from nsISearchEngine
getResultDomain: function SRCH_ENG_getResultDomain(aResponseType) {
#ifdef ANDROID
if (!aResponseType) {
aResponseType = this._defaultMobileResponseType;
}
#endif
if (!aResponseType) {
aResponseType = URLTYPE_SEARCH_HTML;
}
LOG("getResultDomain: responseType: \"" + aResponseType + "\"");
let url = this._getURLOfType(aResponseType);
if (url)
return url.resultDomain;
return "";
},
// nsISupports
QueryInterface: function SRCH_ENG_QI(aIID) {
if (aIID.equals(Ci.nsISearchEngine) ||

View File

@ -5,7 +5,7 @@
<InputEncoding>UTF-8</InputEncoding>
<Image width="16" height="16">data:image/png;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs9Pt8xetPtu9FsfFNtu%2BTzvb2%2B%2Fne4dFJeBw0egA%2FfAJAfAA8ewBBegAAAAD%2B%2FPtft98Mp%2BwWsfAVsvEbs%2FQeqvF8xO7%2F%2F%2F63yqkxdgM7gwE%2FggM%2BfQA%2BegBDeQDe7PIbotgQufcMufEPtfIPsvAbs%2FQvq%2Bfz%2Bf%2F%2B%2B%2FZKhR05hgBBhQI8hgBAgAI9ewD0%2B%2Fg3pswAtO8Cxf4Kw%2FsJvvYAqupKsNv%2B%2Fv7%2F%2FP5VkSU0iQA7jQA9hgBDgQU%2BfQH%2F%2Ff%2FQ6fM4sM4KsN8AteMCruIqqdbZ7PH8%2Fv%2Fg6Nc%2Fhg05kAA8jAM9iQI%2BhQA%2BgQDQu6b97uv%2F%2F%2F7V8Pqw3eiWz97q8%2Ff%2F%2F%2F%2F7%2FPptpkkqjQE4kwA7kAA5iwI8iAA8hQCOSSKdXjiyflbAkG7u2s%2F%2B%2F%2F39%2F%2F7r8utrqEYtjQE8lgA7kwA7kwA9jwA9igA9hACiWSekVRyeSgiYSBHx6N%2F%2B%2Fv7k7OFRmiYtlAA5lwI7lwI4lAA7kgI9jwE9iwI4iQCoVhWcTxCmb0K%2BooT8%2Fv%2F7%2F%2F%2FJ2r8fdwI1mwA3mQA3mgA8lAE8lAE4jwA9iwE%2BhwGfXifWvqz%2B%2Ff%2F58u%2Fev6Dt4tr%2B%2F%2F2ZuIUsggA7mgM6mAM3lgA5lgA6kQE%2FkwBChwHt4dv%2F%2F%2F728ei1bCi7VAC5XQ7kz7n%2F%2F%2F6bsZkgcB03lQA9lgM7kwA2iQktZToPK4r9%2F%2F%2F9%2F%2F%2FSqYK5UwDKZAS9WALIkFn%2B%2F%2F3%2F%2BP8oKccGGcIRJrERILYFEMwAAuEAAdX%2F%2Ff7%2F%2FP%2B%2BfDvGXQLIZgLEWgLOjlf7%2F%2F%2F%2F%2F%2F9QU90EAPQAAf8DAP0AAfMAAOUDAtr%2F%2F%2F%2F7%2B%2Fu2bCTIYwDPZgDBWQDSr4P%2F%2Fv%2F%2F%2FP5GRuABAPkAA%2FwBAfkDAPAAAesAAN%2F%2F%2B%2Fz%2F%2F%2F64g1C5VwDMYwK8Yg7y5tz8%2Fv%2FV1PYKDOcAAP0DAf4AAf0AAfYEAOwAAuAAAAD%2F%2FPvi28ymXyChTATRrIb8%2F%2F3v8fk6P8MAAdUCAvoAAP0CAP0AAfYAAO4AAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA</Image>
<Url type="application/x-suggestions+json" method="GET" template="http://suggestqueries.google.com/complete/search?output=firefox&amp;client=firefox&amp;hl={moz:locale}&amp;q={searchTerms}"/>
<Url type="text/html" method="GET" template="http://www.google.com/search">
<Url type="text/html" method="GET" template="http://www.google.com/search" resultdomain="google.com">
<Param name="q" value="{searchTerms}"/>
<Param name="ie" value="utf-8"/>
<Param name="oe" value="utf-8"/>
@ -15,7 +15,7 @@
<MozParam name="channel" condition="purpose" purpose="contextmenu" value="rcs"/>
<MozParam name="channel" condition="purpose" purpose="keyword" value="fflb"/>
</Url>
<Url type="application/x-moz-default-purpose" method="GET" template="http://www.google.com/search">
<Url type="application/x-moz-default-purpose" method="GET" template="http://www.google.com/search" resultdomain="purpose.google.com">
<Param name="q" value="{searchTerms}"/>
<MozParam name="client" condition="defaultEngine" trueValue="firefox-a" falseValue="firefox"/>
<!-- MozParam with a default value if purpose is not specified -->

View File

@ -24,6 +24,7 @@
},
{
"template": "http://www.google.com/search",
"resultDomain": "google.com",
"rels": [
],
"params": [
@ -64,6 +65,7 @@
},
{
"template": "http://www.google.com/search",
"resultDomain": "purpose.google.com",
"rels": [
],
"type": "application/x-moz-default-purpose",

View File

@ -5,7 +5,7 @@ Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm");
Components.utils.import("resource://gre/modules/Promise.jsm");
Components.utils.import("resource://testing-common/AppInfo.jsm");
const BROWSER_SEARCH_PREF = "browser.search.";

View File

@ -199,6 +199,7 @@ let EXPECTED_ENGINE = {
type: "text/html",
method: "GET",
template: "http://www.google.com/search",
resultDomain: "google.com",
params: [
{
"name": "q",
@ -250,6 +251,7 @@ let EXPECTED_ENGINE = {
type: "application/x-moz-default-purpose",
method: "GET",
template: "http://www.google.com/search",
resultDomain: "purpose.google.com",
params: [
{
"name": "q",

View File

@ -0,0 +1,79 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* Tests getResultDomain API.
*/
"use strict";
const Ci = Components.interfaces;
Components.utils.import("resource://testing-common/httpd.js");
let waitForEngines = new Set([ "Test search engine",
"A second test engine",
"bacon" ]);
function promiseEnginesAdded() {
let deferred = Promise.defer();
let observe = function observe(aSubject, aTopic, aData) {
let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
do_print("Observer: " + aData + " for " + engine.name);
if (aData != "engine-added") {
return;
}
waitForEngines.delete(engine.name);
if (waitForEngines.size > 0) {
return;
}
let engine1 = Services.search.getEngineByName("Test search engine");
do_check_eq(engine1.getResultDomain(), "google.com");
do_check_eq(engine1.getResultDomain("text/html"), "google.com");
do_check_eq(engine1.getResultDomain("application/x-moz-default-purpose"),
"purpose.google.com");
do_check_eq(engine1.getResultDomain("fake-response-type"), "");
let engine2 = Services.search.getEngineByName("A second test engine");
do_check_eq(engine2.getResultDomain(), "duckduckgo.com");
let engine3 = Services.search.getEngineByName("bacon");
do_check_eq(engine3.getResultDomain(), "bacon.moz");
deferred.resolve();
};
Services.obs.addObserver(observe, "browser-search-engine-modified", false);
do_register_cleanup(function cleanup() {
Services.obs.removeObserver(observe, "browser-search-engine-modified");
});
return deferred.promise;
}
function run_test() {
removeMetadata();
updateAppInfo();
run_next_test();
}
add_task(function* check_resultDomain() {
let httpServer = new HttpServer();
httpServer.start(-1);
httpServer.registerDirectory("/", do_get_cwd());
let baseUrl = "http://localhost:" + httpServer.identity.primaryPort;
do_register_cleanup(function cleanup() {
httpServer.stop(function() {});
});
let promise = promiseEnginesAdded();
Services.search.addEngine(baseUrl + "/data/engine.xml",
Ci.nsISearchEngine.DATA_XML,
null, false);
Services.search.addEngine(baseUrl + "/data/engine2.xml",
Ci.nsISearchEngine.DATA_XML,
null, false);
Services.search.addEngineWithDetails("bacon", "", "bacon", "Search Bacon",
"GET", "http://www.bacon.moz/?search={searchTerms}");
yield promise;
});

View File

@ -31,6 +31,7 @@ support-files =
[test_notifications.js]
[test_addEngine_callback.js]
[test_multipleIcons.js]
[test_resultDomain.js]
[test_serialize_file.js]
[test_async.js]
[test_sync.js]

View File

@ -2845,6 +2845,15 @@
"extended_statistics_ok": true,
"description": "PLACES: Time to convert and write bookmarks.html"
},
"PLACES_BACKUPS_HASHING_MS": {
"expires_in_version": "never",
"kind": "exponential",
"low": 50,
"high": 2000,
"n_buckets": 10,
"extended_statistics_ok": true,
"description": "PLACES: Time to calculate the md5 hash for a backup"
},
"FENNEC_FAVICONS_COUNT": {
"expires_in_version": "never",
"kind": "exponential",