Merge m-c to elm.

This commit is contained in:
Richard Newman 2013-10-30 17:19:21 -07:00
commit 8ba90c1d80
15 changed files with 242 additions and 344 deletions

View File

@ -3015,11 +3015,11 @@ const BrowserSearch = {
* allows the search service to provide a different nsISearchSubmission
* depending on e.g. where the search is triggered in the UI.
*
* @return string Name of the search engine used to perform a search or null
* if a search was not performed.
* @return engine The search engine used to perform a search, or null if no
* search was performed.
*/
loadSearch: function BrowserSearch_search(searchText, useNewTab, purpose) {
var engine;
_loadSearch: function (searchText, useNewTab, purpose) {
let engine;
// If the search bar is visible, use the current engine, otherwise, fall
// back to the default engine.
@ -3028,7 +3028,7 @@ const BrowserSearch = {
else
engine = Services.search.defaultEngine;
var submission = engine.getSubmission(searchText, null, purpose); // HTML response
let submission = engine.getSubmission(searchText, null, purpose); // HTML response
// getSubmission can return null if the engine doesn't have a URL
// with a text/html response type. This is unlikely (since
@ -3045,6 +3045,20 @@ const BrowserSearch = {
inBackground: inBackground,
relatedToCurrent: true });
return engine;
},
/**
* Just like _loadSearch, but preserving an old API.
*
* @return string Name of the search engine used to perform a search or null
* if a search was not performed.
*/
loadSearch: function BrowserSearch_search(searchText, useNewTab, purpose) {
let engine = BrowserSearch._loadSearch(searchText, useNewTab, purpose);
if (!engine) {
return null;
}
return engine.name;
},
@ -3055,7 +3069,7 @@ const BrowserSearch = {
* BrowserSearch.loadSearch for the preferred API.
*/
loadSearchFromContext: function (terms) {
let engine = BrowserSearch.loadSearch(terms, true, "contextmenu");
let engine = BrowserSearch._loadSearch(terms, true, "contextmenu");
if (engine) {
BrowserSearch.recordSearchInHealthReport(engine, "contextmenu");
}
@ -3081,8 +3095,7 @@ const BrowserSearch = {
* FHR records only search counts and nothing pertaining to the search itself.
*
* @param engine
* (string) The name of the engine used to perform the search. This
* is typically nsISearchEngine.name.
* (nsISearchEngine) The engine handling the search.
* @param source
* (string) Where the search originated from. See the FHR
* SearchesProvider for allowed values.

View File

@ -486,7 +486,7 @@ function getNumberOfSearches(aEngineName) {
let provider = reporter.getProvider("org.mozilla.searches");
ok(provider, "Searches provider is available.");
let m = provider.getMeasurement("counts", 2);
let m = provider.getMeasurement("counts", 3);
return m.getValues().then(data => {
let now = new Date();
let yday = new Date(now);

View File

@ -47,7 +47,7 @@ function test() {
let provider = reporter.getProvider("org.mozilla.searches");
ok(provider, "Searches provider is available.");
let m = provider.getMeasurement("counts", 2);
let m = provider.getMeasurement("counts", 3);
m.getValues().then(function onValues(data) {
let now = new Date();
ok(data.days.hasDay(now), "Have data for today.");

View File

@ -23,7 +23,7 @@ function test() {
reporter.onInit().then(function onInit() {
let provider = reporter.getProvider("org.mozilla.searches");
ok(provider, "Searches provider is available.");
let m = provider.getMeasurement("counts", 2);
let m = provider.getMeasurement("counts", 3);
m.getValues().then(function onData(data) {
let now = new Date();

View File

@ -320,9 +320,8 @@ BrowserGlue.prototype = {
reporter.onInit().then(function record() {
try {
let name = subject.QueryInterface(Ci.nsISearchEngine).name;
reporter.getProvider("org.mozilla.searches").recordSearch(name,
"urlbar");
let engine = subject.QueryInterface(Ci.nsISearchEngine);
reporter.getProvider("org.mozilla.searches").recordSearch(engine, "urlbar");
} catch (ex) {
Cu.reportError(ex);
}

View File

@ -483,7 +483,7 @@
// null parameter below specifies HTML response for search
var submission = this.currentEngine.getSubmission(aData, null, "searchbar");
BrowserSearch.recordSearchInHealthReport(this.currentEngine.name, "searchbar");
BrowserSearch.recordSearchInHealthReport(this.currentEngine, "searchbar");
openUILinkIn(submission.uri.spec, aWhere, null, submission.postData);
]]></body>
</method>

View File

@ -27,14 +27,15 @@ function test() {
ok(reporter, "Health Reporter available.");
reporter.onInit().then(function onInit() {
let provider = reporter.getProvider("org.mozilla.searches");
let m = provider.getMeasurement("counts", 2);
let m = provider.getMeasurement("counts", 3);
m.getValues().then(function onData(data) {
let now = new Date();
let oldCount = 0;
// Foo engine goes into "other" bucket.
let field = "other.searchbar";
// Find the right bucket for the "Foo" engine.
let engine = Services.search.getEngineByName("Foo");
let field = (engine.identifier || "other-Foo") + ".searchbar";
if (data.days.hasDay(now)) {
let day = data.days.getDay(now);

View File

@ -165,11 +165,12 @@ let AboutHome = {
Cu.reportError(ex);
break;
}
let engine = Services.search.currentEngine;
#ifdef MOZ_SERVICES_HEALTHREPORT
window.BrowserSearch.recordSearchInHealthReport(data.engineName, "abouthome");
window.BrowserSearch.recordSearchInHealthReport(engine, "abouthome");
#endif
// Trigger a search through nsISearchEngine.getSubmission()
let submission = Services.search.currentEngine.getSubmission(data.searchTerms, null, "homepage");
let submission = engine.getSubmission(data.searchTerms, null, "homepage");
window.loadURI(submission.uri.spec, null, submission.postData);
break;
}

View File

@ -19,6 +19,7 @@ import org.mozilla.gecko.health.BrowserHealthReporter;
import org.mozilla.gecko.home.BrowserSearch;
import org.mozilla.gecko.home.HomePager;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.SearchEngine;
import org.mozilla.gecko.menu.GeckoMenu;
import org.mozilla.gecko.prompts.Prompt;
import org.mozilla.gecko.util.Clipboard;
@ -1485,15 +1486,18 @@ abstract public class BrowserApp extends GeckoApp
/**
* Record in Health Report that a search has occurred.
*
* @param identifier
* a search identifier, such as "partnername". Can be null.
* @param engine
* a search engine instance. Can be null.
* @param where
* where the search was initialized; one of the values in
* {@link BrowserHealthRecorder#SEARCH_LOCATIONS}.
*/
private static void recordSearch(String identifier, String where) {
Log.i(LOGTAG, "Recording search: " + identifier + ", " + where);
private static void recordSearch(SearchEngine engine, String where) {
Log.i(LOGTAG, "Recording search: " +
((engine == null) ? "null" : engine.name) +
", " + where);
try {
String identifier = (engine == null) ? "other" : engine.getEngineIdentifier();
JSONObject message = new JSONObject();
message.put("type", BrowserHealthRecorder.EVENT_SEARCH);
message.put("location", where);
@ -2308,9 +2312,9 @@ abstract public class BrowserApp extends GeckoApp
// BrowserSearch.OnSearchListener
@Override
public void onSearch(String engineId, String text) {
recordSearch(engineId, "barsuggest");
openUrl(text, engineId);
public void onSearch(SearchEngine engine, String text) {
recordSearch(engine, "barsuggest");
openUrl(text, engine.name);
}
// BrowserSearch.OnEditSuggestionListener

View File

@ -741,7 +741,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
*/
public static final String MEASUREMENT_NAME_SEARCH_COUNTS = "org.mozilla.searches.counts";
public static final int MEASUREMENT_VERSION_SEARCH_COUNTS = 4;
public static final int MEASUREMENT_VERSION_SEARCH_COUNTS = 5;
public static final String[] SEARCH_LOCATIONS = {
"barkeyword",
@ -749,84 +749,6 @@ public class BrowserHealthRecorder implements GeckoEventListener {
"bartext",
};
// See services/healthreport/providers.jsm. Sorry for the duplication.
// THIS LIST MUST BE SORTED per java.lang.Comparable<String>.
private static final String[] SEARCH_PROVIDERS = {
"amazon-co-uk",
"amazon-de",
"amazon-en-GB",
"amazon-france",
"amazon-it",
"amazon-jp",
"amazondotcn",
"amazondotcom",
"amazondotcom-de",
"aol-en-GB",
"aol-web-search",
"bing",
"eBay",
"eBay-de",
"eBay-en-GB",
"eBay-es",
"eBay-fi",
"eBay-france",
"eBay-hu",
"eBay-in",
"eBay-it",
"google",
"google-jp",
"google-ku",
"google-maps-zh-TW",
"mailru",
"mercadolibre-ar",
"mercadolibre-cl",
"mercadolibre-mx",
"seznam-cz",
"twitter",
"twitter-de",
"twitter-ja",
"wikipedia", // Manually added.
"yahoo",
"yahoo-NO",
"yahoo-answer-zh-TW",
"yahoo-ar",
"yahoo-bid-zh-TW",
"yahoo-br",
"yahoo-ch",
"yahoo-cl",
"yahoo-de",
"yahoo-en-GB",
"yahoo-es",
"yahoo-fi",
"yahoo-france",
"yahoo-fy-NL",
"yahoo-id",
"yahoo-in",
"yahoo-it",
"yahoo-jp",
"yahoo-jp-auctions",
"yahoo-mx",
"yahoo-sv-SE",
"yahoo-zh-TW",
"yandex",
"yandex-ru",
"yandex-slovari",
"yandex-tr",
"yandex.by",
"yandex.ru-be",
};
private void initializeSearchProvider() {
this.storage.ensureMeasurementInitialized(
MEASUREMENT_NAME_SEARCH_COUNTS,
@ -854,30 +776,13 @@ public class BrowserHealthRecorder implements GeckoEventListener {
this.dispatcher.registerEventListener(EVENT_SEARCH, this);
}
/**
* Return the field key for the search provider. This turns null and
* non-partner providers into "other".
*
* @param engine an engine identifier, such as "yandex"
* @return the key to use, such as "other" or "yandex".
*/
protected String getEngineKey(final String engine) {
if (engine == null) {
return "other";
}
// This is inefficient. Optimize if necessary.
boolean found = (0 <= java.util.Arrays.binarySearch(SEARCH_PROVIDERS, engine));
return found ? engine : "other";
}
/**
* Record a search.
*
* @param engine the string identifier for the engine, or null if it's not a partner.
* @param engineID the string identifier for the engine. Can be <code>null</code>.
* @param location one of a fixed set of locations: see {@link #SEARCH_LOCATIONS}.
*/
public void recordSearch(final String engine, final String location) {
public void recordSearch(final String engineID, final String location) {
if (this.state != State.INITIALIZED) {
Log.d(LOG_TAG, "Not initialized: not recording search. (" + this.state + ")");
return;
@ -889,7 +794,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
final int day = storage.getDay();
final int env = this.env;
final String key = getEngineKey(engine);
final String key = (engineID == null) ? "other" : engineID;
final BrowserHealthRecorder self = this;
ThreadUtils.postToBackgroundThread(new Runnable() {

View File

@ -13,8 +13,8 @@ import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.db.BrowserDB.URLColumns;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.SearchEngine;
import org.mozilla.gecko.home.SearchLoader.SearchCursorLoader;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.StringUtils;
@ -132,7 +132,7 @@ public class BrowserSearch extends HomeFragment
private View mSuggestionsOptInPrompt;
public interface OnSearchListener {
public void onSearch(String engineId, String text);
public void onSearch(SearchEngine engine, String text);
}
public interface OnEditSuggestionListener {
@ -393,7 +393,7 @@ public class BrowserSearch extends HomeFragment
}
private void setSuggestions(ArrayList<String> suggestions) {
mSearchEngines.get(0).suggestions = suggestions;
mSearchEngines.get(0).setSuggestions(suggestions);
mAdapter.notifyDataSetChanged();
}
@ -417,14 +417,11 @@ public class BrowserSearch extends HomeFragment
ArrayList<SearchEngine> searchEngines = new ArrayList<SearchEngine>();
for (int i = 0; i < engines.length(); i++) {
final JSONObject engineJSON = engines.getJSONObject(i);
final String name = engineJSON.getString("name");
final String identifier = engineJSON.getString("identifier");
final String iconURI = engineJSON.getString("iconURI");
final Bitmap icon = BitmapUtils.getBitmapFromDataURI(iconURI);
final SearchEngine engine = new SearchEngine(engineJSON);
if (name.equals(suggestEngine) && suggestTemplate != null) {
// Suggest engine should be at the front of the list
searchEngines.add(0, new SearchEngine(name, identifier, icon));
if (engine.name.equals(suggestEngine) && suggestTemplate != null) {
// Suggest engine should be at the front of the list.
searchEngines.add(0, engine);
// The only time Tabs.getInstance().getSelectedTab() should
// be null is when we're restoring after a crash. We should
@ -441,7 +438,7 @@ public class BrowserSearch extends HomeFragment
SUGGESTION_TIMEOUT, SUGGESTION_MAX);
}
} else {
searchEngines.add(new SearchEngine(name, identifier, icon));
searchEngines.add(engine);
}
}
@ -713,7 +710,7 @@ public class BrowserSearch extends HomeFragment
// row contains multiple items, clicking the row will do nothing.
final int index = getEngineIndex(position);
if (index != -1) {
return mSearchEngines.get(index).suggestions.isEmpty();
return !mSearchEngines.get(index).hasSuggestions();
}
return true;
@ -744,7 +741,7 @@ public class BrowserSearch extends HomeFragment
row.setSearchTerm(mSearchTerm);
final SearchEngine engine = mSearchEngines.get(getEngineIndex(position));
final boolean animate = (mAnimateSuggestions && engine.suggestions.size() > 0);
final boolean animate = (mAnimateSuggestions && engine.hasSuggestions());
row.updateFromSearchEngine(engine, animate);
if (animate) {
// Only animate suggestions the first time they are shown

View File

@ -5,25 +5,89 @@
package org.mozilla.gecko.home;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.json.JSONException;
import org.json.JSONObject;
import android.graphics.Bitmap;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
class SearchEngine {
public String name;
public String identifier;
public Bitmap icon;
public ArrayList<String> suggestions;
public class SearchEngine {
public static final String LOG_TAG = "GeckoSearchEngine";
public SearchEngine(String name, String identifier) {
this(name, identifier, null);
public final String name; // Never null.
public final String identifier; // Can be null.
private final Bitmap icon;
private volatile List<String> suggestions = new ArrayList<String>(); // Never null.
public SearchEngine(JSONObject engineJSON) throws JSONException {
if (engineJSON == null) {
throw new IllegalArgumentException("Can't instantiate SearchEngine from null JSON.");
}
this.name = getString(engineJSON, "name");
if (this.name == null) {
throw new IllegalArgumentException("Cannot have an unnamed search engine.");
}
this.identifier = getString(engineJSON, "identifier");
final String iconURI = getString(engineJSON, "iconURI");
if (iconURI == null) {
Log.w(LOG_TAG, "iconURI is null for search engine " + this.name);
this.icon = null;
return;
}
this.icon = BitmapUtils.getBitmapFromDataURI(iconURI);
}
public SearchEngine(String name, String identifier, Bitmap icon) {
this.name = name;
this.identifier = identifier;
this.icon = icon;
this.suggestions = new ArrayList<String>();
private static String getString(JSONObject data, String key) throws JSONException {
if (data.isNull(key)) {
return null;
}
return data.getString(key);
}
/**
* @return a non-null string suitable for use by FHR.
*/
public String getEngineIdentifier() {
if (this.identifier != null) {
return this.identifier;
}
if (this.name != null) {
return "other-" + this.name;
}
return "other";
}
public boolean hasSuggestions() {
return !this.suggestions.isEmpty();
}
public int getSuggestionsCount() {
return this.suggestions.size();
}
public Iterable<String> getSuggestions() {
return this.suggestions;
}
public void setSuggestions(List<String> suggestions) {
if (suggestions == null) {
this.suggestions = new ArrayList<String>();
return;
}
this.suggestions = suggestions;
}
public Bitmap getIcon() {
return this.icon;
}
}

View File

@ -82,7 +82,7 @@ class SearchEngineRow extends AnimatedHeightLayout {
mUrlOpenListener.onUrlOpen(suggestion, EnumSet.noneOf(OnUrlOpenListener.Flags.class));
}
} else if (mSearchListener != null) {
mSearchListener.onSearch(mSearchEngine.name, suggestion);
mSearchListener.onSearch(mSearchEngine, suggestion);
}
}
};
@ -135,7 +135,7 @@ class SearchEngineRow extends AnimatedHeightLayout {
public void performUserEnteredSearch() {
String searchTerm = getSuggestionTextFromView(mUserEnteredView);
if (mSearchListener != null) {
mSearchListener.onSearch(mSearchEngine.name, searchTerm);
mSearchListener.onSearch(mSearchEngine, searchTerm);
}
}
@ -162,25 +162,25 @@ class SearchEngineRow extends AnimatedHeightLayout {
}
public void updateFromSearchEngine(SearchEngine searchEngine, boolean animate) {
// Update search engine reference
// Update search engine reference.
mSearchEngine = searchEngine;
// Set the search engine icon (e.g., Google) for the row
mIconView.updateAndScaleImage(mSearchEngine.icon, mSearchEngine.name);
// Set the search engine icon (e.g., Google) for the row.
mIconView.updateAndScaleImage(mSearchEngine.getIcon(), mSearchEngine.getEngineIdentifier());
// Set the initial content description
// Set the initial content description.
setDescriptionOnSuggestion(mUserEnteredTextView, mUserEnteredTextView.getText().toString());
// Add additional suggestions given by this engine
// Add additional suggestions given by this engine.
final int recycledSuggestionCount = mSuggestionView.getChildCount();
final int suggestionCount = mSearchEngine.suggestions.size();
for (int i = 0; i < suggestionCount; i++) {
int suggestionCounter = 0;
for (String suggestion : mSearchEngine.getSuggestions()) {
final View suggestionItem;
// Reuse suggestion views from recycled view, if possible
if (i + 1 < recycledSuggestionCount) {
suggestionItem = mSuggestionView.getChildAt(i + 1);
// Reuse suggestion views from recycled view, if possible.
if (suggestionCounter + 1 < recycledSuggestionCount) {
suggestionItem = mSuggestionView.getChildAt(suggestionCounter + 1);
suggestionItem.setVisibility(View.VISIBLE);
} else {
suggestionItem = mInflater.inflate(R.layout.suggestion_item, null);
@ -195,23 +195,24 @@ class SearchEngineRow extends AnimatedHeightLayout {
mSuggestionView.addView(suggestionItem);
}
final String suggestion = mSearchEngine.suggestions.get(i);
setSuggestionOnView(suggestionItem, suggestion);
if (animate) {
AlphaAnimation anim = new AlphaAnimation(0, 1);
anim.setDuration(ANIMATION_DURATION);
anim.setStartOffset(i * ANIMATION_DURATION);
anim.setStartOffset(suggestionCounter * ANIMATION_DURATION);
suggestionItem.startAnimation(anim);
}
++suggestionCounter;
}
// Hide extra suggestions that have been recycled
for (int i = suggestionCount + 1; i < recycledSuggestionCount; i++) {
// Hide extra suggestions that have been recycled.
for (int i = suggestionCounter + 1; i < recycledSuggestionCount; ++i) {
mSuggestionView.getChildAt(i).setVisibility(View.GONE);
}
// Make sure mSelectedView is still valid
// Make sure mSelectedView is still valid.
if (mSelectedView >= mSuggestionView.getChildCount()) {
mSelectedView = mSuggestionView.getChildCount() - 1;
}

View File

@ -1186,71 +1186,20 @@ SearchCountMeasurement1.prototype = Object.freeze({
* We don't use the search engine name directly, because it is shared across
* locales; e.g., eBay-de and eBay both share the name "eBay".
*/
function SearchCountMeasurement2() {
this._fieldSpecs = null;
this._interestingEngines = null; // Name -> ID. ("Amazon.com" -> "amazondotcom")
function SearchCountMeasurementBase() {
this._fieldSpecs = {};
Metrics.Measurement.call(this);
}
SearchCountMeasurement2.prototype = Object.freeze({
SearchCountMeasurementBase.prototype = Object.freeze({
__proto__: Metrics.Measurement.prototype,
name: "counts",
version: 2,
/**
* Default implementation; can be overridden by test helpers.
*/
getDefaultEngines: function () {
return Services.search.getDefaultEngines();
},
_initialize: function () {
// Don't create all of these for every profile.
// There are 61 partner engines, translating to 244 fields.
// Instead, compute only those that are possible -- those for whom the
// provider is one of the default search engines.
// This set can grow over time, and change as users run different localized
// Firefox instances.
this._fieldSpecs = {};
this._interestingEngines = {};
for (let source of this.SOURCES) {
this._fieldSpecs["other." + source] = DAILY_COUNTER_FIELD;
}
let engines = this.getDefaultEngines();
for (let engine of engines) {
let id = engine.identifier;
if (!id || (this.PROVIDERS.indexOf(id) == -1)) {
continue;
}
this._interestingEngines[engine.name] = id;
let fieldPrefix = id + ".";
for (let source of this.SOURCES) {
this._fieldSpecs[fieldPrefix + source] = DAILY_COUNTER_FIELD;
}
}
},
// Our fields are dynamic, so we compute them into _fieldSpecs by looking at
// the current set of interesting engines.
// Our fields are dynamic.
get fields() {
if (!this._fieldSpecs) {
this._initialize();
}
return this._fieldSpecs;
},
get interestingEngines() {
if (!this._fieldSpecs) {
this._initialize();
}
return this._interestingEngines;
},
/**
* Override the default behavior: serializers should include every counter
* field from the DB, even if we don't currently have it registered.
@ -1280,101 +1229,6 @@ SearchCountMeasurement2.prototype = Object.freeze({
return Metrics.Storage.FIELD_DAILY_COUNTER;
},
// You can compute the total list of fields by unifying the entire l10n repo
// set with the list of partners:
//
// sort -u */*/searchplugins/list.txt | tr -d '^M' | uniq | grep -f partners.txt
//
// where partners.txt contains
//
// amazon
// aol
// bing
// eBay
// google
// mailru
// mercadolibre
// seznam
// twitter
// yahoo
// yandex
//
// Please update this list as the set of partners changes.
//
PROVIDERS: [
"amazon-co-uk",
"amazon-de",
"amazon-en-GB",
"amazon-france",
"amazon-it",
"amazon-jp",
"amazondotcn",
"amazondotcom",
"amazondotcom-de",
"aol-en-GB",
"aol-web-search",
"bing",
"eBay",
"eBay-de",
"eBay-en-GB",
"eBay-es",
"eBay-fi",
"eBay-france",
"eBay-hu",
"eBay-in",
"eBay-it",
"google",
"google-jp",
"google-ku",
"google-maps-zh-TW",
"mailru",
"mercadolibre-ar",
"mercadolibre-cl",
"mercadolibre-mx",
"seznam-cz",
"twitter",
"twitter-de",
"twitter-ja",
"yahoo",
"yahoo-NO",
"yahoo-answer-zh-TW",
"yahoo-ar",
"yahoo-bid-zh-TW",
"yahoo-br",
"yahoo-ch",
"yahoo-cl",
"yahoo-de",
"yahoo-en-GB",
"yahoo-es",
"yahoo-fi",
"yahoo-france",
"yahoo-fy-NL",
"yahoo-id",
"yahoo-in",
"yahoo-it",
"yahoo-jp",
"yahoo-jp-auctions",
"yahoo-mx",
"yahoo-sv-SE",
"yahoo-zh-TW",
"yandex",
"yandex-ru",
"yandex-slovari",
"yandex-tr",
"yandex.by",
"yandex.ru-be",
],
SOURCES: [
"abouthome",
"contextmenu",
@ -1383,6 +1237,40 @@ SearchCountMeasurement2.prototype = Object.freeze({
],
});
function SearchCountMeasurement2() {
SearchCountMeasurementBase.call(this);
}
SearchCountMeasurement2.prototype = Object.freeze({
__proto__: SearchCountMeasurementBase.prototype,
name: "counts",
version: 2,
});
function SearchCountMeasurement3() {
SearchCountMeasurementBase.call(this);
}
SearchCountMeasurement3.prototype = Object.freeze({
__proto__: SearchCountMeasurementBase.prototype,
name: "counts",
version: 3,
getEngines: function () {
return Services.search.getEngines();
},
getEngineID: function (engine) {
if (!engine) {
return "other";
}
if (engine.identifier) {
return engine.identifier;
}
return "other-" + engine.name;
},
});
this.SearchesProvider = function () {
Metrics.Provider.call(this);
};
@ -1394,6 +1282,7 @@ this.SearchesProvider.prototype = Object.freeze({
measurementTypes: [
SearchCountMeasurement1,
SearchCountMeasurement2,
SearchCountMeasurement3,
],
/**
@ -1412,8 +1301,7 @@ this.SearchesProvider.prototype = Object.freeze({
* Record that a search occurred.
*
* @param engine
* (string) The search engine used. If the search engine is unknown,
* the search will be attributed to "other".
* (nsISearchEngine) The search engine used.
* @param source
* (string) Where the search was initiated from. Must be one of the
* SearchCountMeasurement2.SOURCES values.
@ -1422,17 +1310,30 @@ this.SearchesProvider.prototype = Object.freeze({
* The promise is resolved when the storage operation completes.
*/
recordSearch: function (engine, source) {
let m = this.getMeasurement("counts", 2);
let m = this.getMeasurement("counts", 3);
if (m.SOURCES.indexOf(source) == -1) {
throw new Error("Unknown source for search: " + source);
}
let id = m.interestingEngines[engine] || "other";
let field = id + "." + source;
return this.enqueueStorageOperation(function recordSearch() {
return m.incrementDailyCounter(field);
});
let field = m.getEngineID(engine) + "." + source;
if (this.storage.hasFieldFromMeasurement(m.id, field,
this.storage.FIELD_DAILY_COUNTER)) {
let fieldID = this.storage.fieldIDFromMeasurement(m.id, field);
return this.enqueueStorageOperation(function recordSearchKnownField() {
return this.storage.incrementDailyCounterFromFieldID(fieldID);
}.bind(this));
}
// Otherwise, we first need to create the field.
return this.enqueueStorageOperation(function recordFieldAndSearch() {
// This function has to return a promise.
return Task.spawn(function () {
let fieldID = yield this.storage.registerField(m.id, field,
this.storage.FIELD_DAILY_COUNTER);
yield this.storage.incrementDailyCounterFromFieldID(fieldID);
}.bind(this));
}.bind(this));
},
});

View File

@ -17,13 +17,10 @@ const DEFAULT_ENGINES = [
];
function MockSearchCountMeasurement() {
bsp.SearchCountMeasurement2.call(this);
bsp.SearchCountMeasurement3.call(this);
}
MockSearchCountMeasurement.prototype = {
__proto__: bsp.SearchCountMeasurement2.prototype,
getDefaultEngines: function () {
return DEFAULT_ENGINES;
},
__proto__: bsp.SearchCountMeasurement3.prototype,
};
function MockSearchesProvider() {
@ -52,24 +49,29 @@ add_task(function test_record() {
let now = new Date();
for (let engine of DEFAULT_ENGINES) {
yield provider.recordSearch(engine.name, "abouthome");
yield provider.recordSearch(engine.name, "contextmenu");
yield provider.recordSearch(engine.name, "searchbar");
yield provider.recordSearch(engine.name, "urlbar");
// Record searches for all but one of our defaults, and one engine that's
// not a default.
for (let engine of DEFAULT_ENGINES.concat([{name: "Not Default", identifier: "notdef"}])) {
if (engine.identifier == "yahoo") {
continue;
}
yield provider.recordSearch(engine, "abouthome");
yield provider.recordSearch(engine, "contextmenu");
yield provider.recordSearch(engine, "searchbar");
yield provider.recordSearch(engine, "urlbar");
}
// Invalid sources should throw.
let errored = false;
try {
yield provider.recordSearch(DEFAULT_ENGINES[0].name, "bad source");
yield provider.recordSearch(DEFAULT_ENGINES[0], "bad source");
} catch (ex) {
errored = true;
} finally {
do_check_true(errored);
}
let m = provider.getMeasurement("counts", 2);
let m = provider.getMeasurement("counts", 3);
let data = yield m.getValues();
do_check_eq(data.days.size, 1);
do_check_true(data.days.hasDay(now));
@ -77,17 +79,27 @@ add_task(function test_record() {
let day = data.days.getDay(now);
for (let engine of DEFAULT_ENGINES) {
let identifier = engine.identifier;
if (identifier == "foobar") {
identifier = "other";
}
let expected = identifier != "yahoo";
for (let source of ["abouthome", "contextmenu", "searchbar", "urlbar"]) {
let field = identifier + "." + source;
do_check_true(day.has(field));
do_check_eq(day.get(field), 1);
if (expected) {
do_check_true(day.has(field));
do_check_eq(day.get(field), 1);
} else {
do_check_false(day.has(field));
}
}
}
// Also, check that our non-default engine contributed, with a computed
// identifier.
let identifier = "notdef";
for (let source of ["abouthome", "contextmenu", "searchbar", "urlbar"]) {
let field = identifier + "." + source;
do_check_true(day.has(field));
}
yield storage.close();
});
@ -96,7 +108,7 @@ add_task(function test_includes_other_fields() {
let provider = new MockSearchesProvider();
yield provider.init(storage);
let m = provider.getMeasurement("counts", 2);
let m = provider.getMeasurement("counts", 3);
// Register a search against a provider that isn't live in this session.
let id = yield m.storage.registerField(m.id, "test.searchbar",