Bug 1054303 - Use open search plugins for search engines. r=bnicholson
authorMargaret Leibovic <margaret.leibovic@gmail.com>
Mon, 25 Aug 2014 15:28:11 -0700
changeset 201556 36bc6b13ac2c8344f7f8d97513d4fcb8460aa004
parent 201555 a1a03dfa771a98ae73074f08b7f7cfed30f6777e
child 201557 b1cfad9bc2dbc22eef183bdf51c890e56b7fdda9
push id27374
push userryanvm@gmail.com
push dateTue, 26 Aug 2014 18:16:27 +0000
treeherdermozilla-central@c850f3af9f2c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbnicholson
bugs1054303
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1054303 - Use open search plugins for search engines. r=bnicholson
mobile/android/search/java/org/mozilla/search/providers/BingSearchEngine.java
mobile/android/search/java/org/mozilla/search/providers/GoogleSearchEngine.java
mobile/android/search/java/org/mozilla/search/providers/SearchEngine.java
mobile/android/search/java/org/mozilla/search/providers/SearchEngineManager.java
mobile/android/search/java/org/mozilla/search/providers/YahooSearchEngine.java
mobile/android/search/search_activity_sources.mozbuild
deleted file mode 100644
--- a/mobile/android/search/java/org/mozilla/search/providers/BingSearchEngine.java
+++ /dev/null
@@ -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;
-    }
-}
deleted file mode 100644
--- a/mobile/android/search/java/org/mozilla/search/providers/GoogleSearchEngine.java
+++ /dev/null
@@ -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;
-    }
-
-}
--- a/mobile/android/search/java/org/mozilla/search/providers/SearchEngine.java
+++ b/mobile/android/search/java/org/mozilla/search/providers/SearchEngine.java
@@ -1,102 +1,227 @@
 /* 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 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`.
     private static final String STYLE_INJECTION_SCRIPT =
             "javascript:(function(){" +
                     "var tag=document.createElement('style');" +
                     "tag.type='text/css';" +
                     "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());
-    }
-
-    /**
-     * Create a uri string that can be used to fetch suggestions.
-     *
-     * @param query The user's partial query. This method will handle url escaping.
-     */
-    final public String suggestUriForQuery(String query) {
-        return getSuggestionUri().buildUpon().appendQueryParameter(getSuggestionQueryParam(), query).build().toString();
+    public boolean isSearchResultsPage(String url) {
+        return resultsUri.getAuthority().equalsIgnoreCase(Uri.parse(url).getAuthority());
     }
 
     /**
-     * Create a uri strung that can be used to fetch the results page.
+     * Create a uri string that can be used to fetch the results page.
      *
-     * @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 resultsUriForQuery(String query) {
+        final String template = Uri.decode(resultsUri.toString());
+        return paramSubstitution(template, Uri.encode(query));
     }
 
     /**
-     * Create a suggestion uri that can be used by SuggestClient
+     * Create a uri string to fetch autocomplete suggestions.
+     *
+     * @param query The user's query. This method will escape and encode the query.
      */
-    final public String getSuggestionTemplate(String placeholder) {
-        if (suggestionTemplate == null) {
-            suggestionTemplate = suggestUriForQuery(placeholder);
-        }
-        return suggestionTemplate;
+    public String getSuggestionTemplate(String query) {
+        final String template = Uri.decode(suggestUri.toString());
+        return paramSubstitution(template, Uri.encode(query));
     }
 
     /**
-     * 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.
+     * Formats template string with proper parameters. Modeled after
+     * ParamSubstitution in nsSearchService.js
+     *
+     * @param template
+     * @param query
+     * @return
      */
-    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();
+    private String paramSubstitution(String template, String query) {
+        final String locale = Locale.getDefault().toString();
 
-    /**
-     * 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();
+        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");
 
-    /**
-     * Retrieve the uri query param for the user's partial query.
-     * Used for suggestions.
-     */
-    protected abstract String getSuggestionQueryParam();
+        template = template.replaceAll(OS_PARAM_LANGUAGE, locale);
+        template = template.replaceAll(OS_PARAM_OUTPUT_ENCODING, "UTF-8");
 
-    /**
-     * Retrieve the uri query param that holds the user's final query.
-     * Used for results.
-     */
-    protected abstract String getResultsQueryParam();
+        // Replace any optional parameters
+        template = template.replaceAll(OS_PARAM_OPTIONAL, "");
+
+        return template;
+    }
 }
--- a/mobile/android/search/java/org/mozilla/search/providers/SearchEngineManager.java
+++ b/mobile/android/search/java/org/mozilla/search/providers/SearchEngineManager.java
@@ -5,34 +5,33 @@
 package org.mozilla.search.providers;
 
 import android.content.Context;
 import android.content.SharedPreferences;
 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";
 
     private Context context;
     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);
     }
 
     public SearchEngineManager(Context context) {
         this.context = context;
         GeckoSharedPrefs.forApp(context).registerOnSharedPreferenceChangeListener(this);
     }
@@ -58,78 +57,112 @@ public class SearchEngineManager impleme
 
     public void destroy() {
         GeckoSharedPrefs.forApp(context).unregisterOnSharedPreferenceChangeListener(this);
         context = null;
         changeCallback = null;
         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);
+
+        // 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);
+        }
 
-            if (changeCallback != null) {
-                changeCallback.execute(engine);
+        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) {
-
-        if (TextUtils.isEmpty(engineName)) {
-            engineName = Constants.DEFAULT_SEARCH_ENGINE;
-        }
+    private InputStream getEngineFromJar(String identifier) {
+        // TODO: Get the real value for this
+        final String locale = "en-US";
 
-        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);
-        }
-    }
+        final String path = "!/chrome/" + locale + "/locale/" + locale + "/browser/searchplugins/" + identifier + ".xml";
+        final String url = "jar:jar:file://" + context.getPackageResourcePath() + "!/" + AppConstants.OMNIJAR_NAME + path;
 
-    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);
     }
 }
deleted file mode 100644
--- a/mobile/android/search/java/org/mozilla/search/providers/YahooSearchEngine.java
+++ /dev/null
@@ -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;
-    }
-
-}
--- a/mobile/android/search/search_activity_sources.mozbuild
+++ b/mobile/android/search/search_activity_sources.mozbuild
@@ -8,16 +8,13 @@ search_activity_sources = [
     'java/org/mozilla/search/AcceptsSearchQuery.java',
     'java/org/mozilla/search/autocomplete/AutoCompleteAdapter.java',
     'java/org/mozilla/search/autocomplete/ClearableEditText.java',
     'java/org/mozilla/search/autocomplete/SuggestionsFragment.java',
     'java/org/mozilla/search/Constants.java',
     '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',
 ]