Bug 1054303 - Use open search plugins for search engines. r=bnicholson

This commit is contained in:
Margaret Leibovic 2014-08-25 15:28:11 -07:00
Родитель 30931aeb6a
Коммит dbabc09768
6 изменённых файлов: 260 добавлений и 239 удалений

Просмотреть файл

@ -1,43 +0,0 @@
/* 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/. */
package org.mozilla.search.providers;
import android.net.Uri;
public class BingSearchEngine extends SearchEngine {
private static final String HIDE_BANNER_CSS = "#mHeader{display:none}#contentWrapper{margin-top:0}";
private static final Uri RESULTS_URI = Uri.parse("https://www.bing.com/search");
private static final String RESULTS_URI_QUERY_PARAM = "q";
private static final Uri SUGGEST_URI = Uri.parse("http://api.bing.com/osjson.aspx");
private static final String SUGGEST_URI_QUERY_PARAM = "query";
@Override
public String getInjectableCss() {
return HIDE_BANNER_CSS;
}
@Override
protected Uri getResultsUri() {
return RESULTS_URI;
}
@Override
protected Uri getSuggestionUri() {
return SUGGEST_URI;
}
@Override
protected String getSuggestionQueryParam() {
return SUGGEST_URI_QUERY_PARAM;
}
@Override
protected String getResultsQueryParam() {
return RESULTS_URI_QUERY_PARAM;
}
}

Просмотреть файл

@ -1,44 +0,0 @@
/* 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/. */
package org.mozilla.search.providers;
import android.net.Uri;
public class GoogleSearchEngine extends SearchEngine {
private static final String HIDE_BANNER_CSS = "#sfcnt,#top_nav{display:none}";
private static final Uri RESULTS_URI = Uri.parse("https://www.google.com/search");
private static final String RESULTS_URI_QUERY_PARAM = "q";
private static final Uri SUGGEST_URI = Uri.parse("https://www.google.com/complete/search?client=firefox");
private static final String SUGGEST_URI_QUERY_PARAM = "q";
@Override
public String getInjectableCss() {
return HIDE_BANNER_CSS;
}
@Override
protected Uri getResultsUri() {
return RESULTS_URI;
}
@Override
protected Uri getSuggestionUri() {
return SUGGEST_URI;
}
@Override
protected String getSuggestionQueryParam() {
return SUGGEST_URI_QUERY_PARAM;
}
@Override
protected String getResultsQueryParam() {
return RESULTS_URI_QUERY_PARAM;
}
}

Просмотреть файл

@ -5,12 +5,36 @@
package org.mozilla.search.providers;
import android.net.Uri;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
/**
* Extend this class to add a new search engine to
* the search activity.
*/
public abstract class SearchEngine {
public class SearchEngine {
private static final String URLTYPE_SUGGEST_JSON = "application/x-suggestions+json";
private static final String URLTYPE_SEARCH_HTML = "text/html";
// Parameters copied from nsSearchService.js
private static final String MOZ_PARAM_LOCALE = "\\{moz:locale\\}";
private static final String MOZ_PARAM_DIST_ID = "\\{moz:distributionID\\}";
private static final String MOZ_PARAM_OFFICIAL = "\\{moz:official\\}";
// Supported OpenSearch parameters
// See http://opensearch.a9.com/spec/1.1/querysyntax/#core
private static final String OS_PARAM_USER_DEFINED = "\\{searchTerms\\??\\}";
private static final String OS_PARAM_INPUT_ENCODING = "\\{inputEncoding\\??\\}";
private static final String OS_PARAM_LANGUAGE = "\\{language\\??\\}";
private static final String OS_PARAM_OUTPUT_ENCODING = "\\{outputEncoding\\??\\}";
private static final String OS_PARAM_OPTIONAL = "\\{(?:\\w+:)?\\w+\\?\\}";
// Boilerplate bookmarklet-style JS for injecting CSS into the
// head of a web page. The actual CSS is inserted at `%s`.
@ -21,82 +45,183 @@ public abstract class SearchEngine {
"document.getElementsByTagName('head')[0].appendChild(tag);" +
"tag.innerText='%s'})();";
private String suggestionTemplate;
private String identifier;
private String shortName;
// TODO: Make something more robust (like EngineURL in nsSearchService.js)
private Uri resultsUri;
private Uri suggestUri;
/**
*
* @param in InputStream of open search plugin XML
*/
public SearchEngine(String identifier, InputStream in) throws IOException, XmlPullParserException {
this.identifier = identifier;
final XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, null);
parser.nextTag();
readSearchPlugin(parser);
}
private void readSearchPlugin(XmlPullParser parser) throws XmlPullParserException, IOException {
parser.require(XmlPullParser.START_TAG, null, "SearchPlugin");
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
final String tag = parser.getName();
if (tag.equals("ShortName")) {
readShortName(parser);
} else if (tag.equals("Url")) {
readUrl(parser);
// TODO: Support for other tags
//} else if (tag.equals("Image")) {
} else {
skip(parser);
}
}
}
private void readShortName(XmlPullParser parser) throws IOException, XmlPullParserException {
parser.require(XmlPullParser.START_TAG, null, "ShortName");
if (parser.next() == XmlPullParser.TEXT) {
shortName = parser.getText();
parser.nextTag();
}
}
private void readUrl(XmlPullParser parser) throws XmlPullParserException, IOException {
parser.require(XmlPullParser.START_TAG, null, "Url");
final String type = parser.getAttributeValue(null, "type");
final String template = parser.getAttributeValue(null, "template");
Uri uri = Uri.parse(template);
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
final String tag = parser.getName();
if (tag.equals("Param")) {
final String name = parser.getAttributeValue(null, "name");
final String value = parser.getAttributeValue(null, "value");
uri = uri.buildUpon().appendQueryParameter(name, value).build();
parser.nextTag();
// TODO: Support for other tags
//} else if (tag.equals("MozParam")) {
} else {
skip(parser);
}
}
if (type.equals(URLTYPE_SEARCH_HTML)) {
resultsUri = uri;
} else if (type.equals(URLTYPE_SUGGEST_JSON)) {
suggestUri = uri;
}
}
private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
if (parser.getEventType() != XmlPullParser.START_TAG) {
throw new IllegalStateException();
}
int depth = 1;
while (depth != 0) {
switch (parser.next()) {
case XmlPullParser.END_TAG:
depth--;
break;
case XmlPullParser.START_TAG:
depth++;
break;
}
}
}
/**
* HACKS! We'll need to replace this with endpoints that return the correct content.
*
* Retrieve a JS snippet, in bookmarklet style, that can be used
* to modify the results page.
*/
public String getInjectableJs() {
return String.format(STYLE_INJECTION_SCRIPT, getInjectableCss());
final String css;
if (identifier.equals("bing")) {
css = "#mHeader{display:none}#contentWrapper{margin-top:0}";
} else if (identifier.equals("google")) {
css = "#sfcnt,#top_nav{display:none}";
} else if (identifier.equals("yahoo")) {
css = "#nav,#header{display:none}";
} else {
css = "";
}
return String.format(STYLE_INJECTION_SCRIPT, css);
}
public String getName() {
return shortName;
}
/**
* Determine whether a particular url belongs to this search engine. If not,
* the url will be sent to Fennec.
*/
final public boolean isSearchResultsPage(String url) {
return getResultsUri().getAuthority().equalsIgnoreCase(Uri.parse(url).getAuthority());
public boolean isSearchResultsPage(String url) {
return resultsUri.getAuthority().equalsIgnoreCase(Uri.parse(url).getAuthority());
}
/**
* Create a uri string that can be used to fetch suggestions.
* Create a uri string that can be used to fetch the results page.
*
* @param query The user's partial query. This method will handle url escaping.
* @param query The user's query. This method will escape and encode the query.
*/
final public String suggestUriForQuery(String query) {
return getSuggestionUri().buildUpon().appendQueryParameter(getSuggestionQueryParam(), query).build().toString();
public String resultsUriForQuery(String query) {
final String template = Uri.decode(resultsUri.toString());
return paramSubstitution(template, Uri.encode(query));
}
/**
* Create a uri strung that can be used to fetch the results page.
* Create a uri string to fetch autocomplete suggestions.
*
* @param query The user's query. This method will handle url escaping.
* @param query The user's query. This method will escape and encode the query.
*/
final public String resultsUriForQuery(String query) {
return getResultsUri().buildUpon().appendQueryParameter(getResultsQueryParam(), query).build().toString();
public String getSuggestionTemplate(String query) {
final String template = Uri.decode(suggestUri.toString());
return paramSubstitution(template, Uri.encode(query));
}
/**
* Create a suggestion uri that can be used by SuggestClient
* Formats template string with proper parameters. Modeled after
* ParamSubstitution in nsSearchService.js
*
* @param template
* @param query
* @return
*/
final public String getSuggestionTemplate(String placeholder) {
if (suggestionTemplate == null) {
suggestionTemplate = suggestUriForQuery(placeholder);
}
return suggestionTemplate;
private String paramSubstitution(String template, String query) {
final String locale = Locale.getDefault().toString();
template = template.replaceAll(MOZ_PARAM_LOCALE, locale);
template = template.replaceAll(MOZ_PARAM_DIST_ID, "");
template = template.replaceAll(MOZ_PARAM_OFFICIAL, "unofficial");
template = template.replaceAll(OS_PARAM_USER_DEFINED, query);
template = template.replaceAll(OS_PARAM_INPUT_ENCODING, "UTF-8");
template = template.replaceAll(OS_PARAM_LANGUAGE, locale);
template = template.replaceAll(OS_PARAM_OUTPUT_ENCODING, "UTF-8");
// Replace any optional parameters
template = template.replaceAll(OS_PARAM_OPTIONAL, "");
return template;
}
/**
* Retrieve a snippet of CSS that can be used to modify the appearance
* of the search results page. Currently this is used to hide
* the web site's search bar and facet bar.
*/
protected abstract String getInjectableCss();
/**
* Retrieve the base Uri that should be used when retrieving
* the results page. This may include params that do not vary --
* for example, the user's locale.
*/
protected abstract Uri getResultsUri();
/**
* Retrieve the base Uri that should be used when fetching
* suggestions. This may include params that do not vary --
* for example, the user's locale.
*/
protected abstract Uri getSuggestionUri();
/**
* Retrieve the uri query param for the user's partial query.
* Used for suggestions.
*/
protected abstract String getSuggestionQueryParam();
/**
* Retrieve the uri query param that holds the user's final query.
* Used for results.
*/
protected abstract String getResultsQueryParam();
}

Просмотреть файл

@ -10,9 +10,15 @@ import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.util.GeckoJarReader;
import org.mozilla.search.Constants;
import org.mozilla.search.SearchPreferenceActivity;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
public class SearchEngineManager implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String LOG_TAG = "SearchEngineManager";
@ -21,13 +27,6 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
private SearchEngineCallback changeCallback;
private SearchEngine engine;
// Add new engines to this enum. Also update createInstance, the factory method below.
public static enum Engine {
BING,
GOOGLE,
YAHOO
}
public static interface SearchEngineCallback {
public void execute(SearchEngine engine);
}
@ -63,73 +62,107 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
engine = null;
}
@Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
if (!TextUtils.equals(SearchPreferenceActivity.PREF_SEARCH_ENGINE_KEY, key)) {
return;
}
getEngineFromPrefs(changeCallback);
}
/**
* Manually lookup the current search engine.
* Look up the current search engine in shared preferences.
* Creates a SearchEngine instance and caches it for use on the main thread.
*
* @param callback a SearchEngineCallback to be called after successfully looking
* up the search engine. This will run on the UI thread.
*/
private void getEngineFromPrefs(final SearchEngineCallback callback) {
final AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {
final AsyncTask<Void, Void, SearchEngine> task = new AsyncTask<Void, Void, SearchEngine>() {
@Override
protected String doInBackground(Void... params) {
return GeckoSharedPrefs.forApp(context).getString(SearchPreferenceActivity.PREF_SEARCH_ENGINE_KEY, null);
protected SearchEngine doInBackground(Void... params) {
final String identifier = GeckoSharedPrefs.forApp(context)
.getString(SearchPreferenceActivity.PREF_SEARCH_ENGINE_KEY, Constants.DEFAULT_SEARCH_ENGINE)
.toLowerCase();
return createEngine(identifier);
}
@Override
protected void onPostExecute(String engineName) {
updateEngine(engineName);
callback.execute(engine);
protected void onPostExecute(SearchEngine engine) {
// Only touch engine on the main thread.
SearchEngineManager.this.engine = engine;
if (callback != null) {
callback.execute(engine);
}
}
};
task.execute();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (TextUtils.equals(SearchPreferenceActivity.PREF_SEARCH_ENGINE_KEY, key)) {
updateEngine(sharedPreferences.getString(key, null));
/**
* Creates a SearchEngine instance from an open search plugin.
* This method does disk I/O, call it from a background thread.
*
* @param identifier search engine identifier (e.g. "google")
* @return SearchEngine instance for identifier
*/
private SearchEngine createEngine(String identifier) {
InputStream in = getEngineFromJar(identifier);
if (changeCallback != null) {
changeCallback.execute(engine);
// Fallback for standalone search activity.
if (in == null) {
in = getEngineFromAssets(identifier);
}
if (in == null) {
throw new IllegalArgumentException("Couldn't find search engine for identifier: " + identifier);
}
try {
try {
return new SearchEngine(identifier, in);
} finally {
in.close();
}
} catch (IOException e) {
Log.e(LOG_TAG, "Exception creating search engine", e);
} catch (XmlPullParserException e) {
Log.e(LOG_TAG, "Exception creating search engine", e);
}
return null;
}
/**
* Fallback for standalone search activity. These assets are not included
* in mozilla-central.
*
* @param identifier search engine identifier (e.g. "google")
* @return InputStream for open search plugin XML
*/
private InputStream getEngineFromAssets(String identifier) {
try {
return context.getResources().getAssets().open(identifier + ".xml");
} catch (IOException e) {
Log.e(LOG_TAG, "Exception getting search engine from assets", e);
return null;
}
}
/**
* Notify the searchEngineChangeListener that the default search engine has changed.
* Reads open search plugin XML file from the gecko jar. This will only work
* if the search activity is built as part of mozilla-central.
*
* @param engineName The name of the new search engine. This should be a member
* of SearchEngineFactory.Engine. If null, then it will use the
* default search engine.
* @return true if this caused the engine to be changed.
* @param identifier search engine identifier (e.g. "google")
* @return InputStream for open search plugin XML
*/
private void updateEngine(String engineName) {
private InputStream getEngineFromJar(String identifier) {
// TODO: Get the real value for this
final String locale = "en-US";
if (TextUtils.isEmpty(engineName)) {
engineName = Constants.DEFAULT_SEARCH_ENGINE;
}
final String path = "!/chrome/" + locale + "/locale/" + locale + "/browser/searchplugins/" + identifier + ".xml";
final String url = "jar:jar:file://" + context.getPackageResourcePath() + "!/" + AppConstants.OMNIJAR_NAME + path;
try {
engine = createEngine(engineName);
} catch (IllegalArgumentException e) {
Log.e(LOG_TAG, "Search engine not found for " + engineName + " reverting to default engine.", e);
engine = createEngine(Constants.DEFAULT_SEARCH_ENGINE);
}
}
private static SearchEngine createEngine(String engineName) {
switch (Engine.valueOf(engineName)) {
case BING:
return new BingSearchEngine();
case GOOGLE:
return new GoogleSearchEngine();
case YAHOO:
return new YahooSearchEngine();
}
// The return statement is unreachable since Engine.valueOf will throw
// IllegalArgumentException if engineName cannot be resolved.
return null;
return GeckoJarReader.getStream(url);
}
}

Просмотреть файл

@ -1,47 +0,0 @@
/* 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/. */
package org.mozilla.search.providers;
import android.net.Uri;
import org.mozilla.search.Constants;
public class YahooSearchEngine extends SearchEngine {
private static final String HIDE_BANNER_CSS = "#nav,#header{display:none}";
private static final Uri RESULTS_URI = Uri.parse("https://search.yahoo.com/search");
private static final String RESULTS_URI_QUERY_PARAM = "p";
private static final Uri SUGGEST_URI = Uri.parse(
"https://search.yahoo.com/sugg/ff?output=fxjson&appid=ffm&nresults=" + Constants.SUGGESTION_MAX);
private static final String SUGGEST_URI_QUERY_PARAM = "command";
@Override
public String getInjectableCss() {
return HIDE_BANNER_CSS;
}
@Override
protected Uri getResultsUri() {
return RESULTS_URI;
}
@Override
protected Uri getSuggestionUri() {
return SUGGEST_URI;
}
@Override
protected String getSuggestionQueryParam() {
return SUGGEST_URI_QUERY_PARAM;
}
@Override
protected String getResultsQueryParam() {
return RESULTS_URI_QUERY_PARAM;
}
}

Просмотреть файл

@ -13,11 +13,8 @@ search_activity_sources = [
'java/org/mozilla/search/MainActivity.java',
'java/org/mozilla/search/PostSearchFragment.java',
'java/org/mozilla/search/PreSearchFragment.java',
'java/org/mozilla/search/providers/BingSearchEngine.java',
'java/org/mozilla/search/providers/GoogleSearchEngine.java',
'java/org/mozilla/search/providers/SearchEngine.java',
'java/org/mozilla/search/providers/SearchEngineManager.java',
'java/org/mozilla/search/providers/YahooSearchEngine.java',
'java/org/mozilla/search/SearchPreferenceActivity.java',
'java/org/mozilla/search/SearchWidget.java',
]