Bug 769145 - Part 4: Add search suggestions opt-in prompt. r=mfinkle
☠☠ backed out by d5a3c148545c ☠ ☠
authorBrian Nicholson <bnicholson@mozilla.com>
Tue, 02 Oct 2012 10:51:48 -0700
changeset 115214 580f710a84e8f99229aca9f4e11e1c8f2df47253
parent 115213 ecaff73e5a633b344e6960a7d6b21f672fb174de
child 115215 8b5a786ee47b5d103a16031119098d1b99900e39
push id1708
push userakeybl@mozilla.com
push dateMon, 19 Nov 2012 21:10:21 +0000
treeherdermozilla-beta@27b14fe50103 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs769145
milestone18.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 769145 - Part 4: Add search suggestions opt-in prompt. r=mfinkle
mobile/android/app/mobile.js
mobile/android/base/Makefile.in
mobile/android/base/awesomebar/AllPagesTab.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/resources/layout/awesomebar_allpages_list.xml
mobile/android/base/resources/layout/awesomebar_suggestion_prompt.xml
mobile/android/base/strings.xml.in
mobile/android/chrome/content/browser.js
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -245,16 +245,17 @@ pref("browser.search.order.2", "chrome:/
 
 // disable updating
 pref("browser.search.update", false);
 pref("browser.search.update.log", false);
 pref("browser.search.updateinterval", 6);
 
 // disable search suggestions by default
 pref("browser.search.suggest.enabled", false);
+pref("browser.search.suggest.prompted", false);
 
 // Tell the search service to load search plugins from the locale JAR
 pref("browser.search.loadFromJars", true);
 pref("browser.search.jarURIs", "chrome://browser/locale/searchplugins/");
 
 // tell the search service that we don't really expose the "current engine"
 pref("browser.search.noCurrentEngine", true);
 
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -317,19 +317,21 @@ RES_LAYOUT = \
   $(SYNC_RES_LAYOUT) \
   res/layout/autocomplete_list.xml \
   res/layout/autocomplete_list_item.xml \
   res/layout/awesomebar.xml \
   res/layout/awesomebar_actionbar.xml \
   res/layout/awesomebar_expandable_list.xml \
   res/layout/awesomebar_folder_row.xml \
   res/layout/awesomebar_header_row.xml \
+  res/layout/awesomebar_allpages_list.xml \
   res/layout/awesomebar_list.xml \
   res/layout/awesomebar_row.xml \
   res/layout/awesomebar_suggestion_item.xml \
+  res/layout/awesomebar_suggestion_prompt.xml \
   res/layout/awesomebar_suggestion_row.xml \
   res/layout/awesomebar_search.xml \
   res/layout/awesomebar_tab_indicator.xml \
   res/layout/awesomebar_tabs.xml \
   res/layout/bookmark_edit.xml \
   res/layout/datetime_picker.xml \
   res/layout/doorhangerpopup.xml \
   res/layout/doorhanger.xml \
--- a/mobile/android/base/awesomebar/AllPagesTab.java
+++ b/mobile/android/base/awesomebar/AllPagesTab.java
@@ -53,16 +53,18 @@ public class AllPagesTab extends Awesome
 
     private String mSearchTerm;
     private ArrayList<SearchEngine> mSearchEngines;
     private SuggestClient mSuggestClient;
     private boolean mSuggestionsEnabled;
     private AsyncTask<String, Void, ArrayList<String>> mSuggestTask;
     private AwesomeBarCursorAdapter mCursorAdapter = null;
     private boolean mTelemetrySent = false;
+    private LinearLayout mAllPagesView;
+    private View mSuggestionsOptInPrompt;
 
     private class SearchEntryViewHolder {
         public FlowLayout suggestionView;
         public ImageView iconView;
         public LinearLayout userEnteredView;
         public TextView userEnteredTextView;
     }
 
@@ -76,38 +78,44 @@ public class AllPagesTab extends Awesome
 
     public boolean onBackPressed() {
         return false;
     }
 
     public TabContentFactory getFactory() {
         return new TabContentFactory() {
            public View createTabContent(String tag) {
-               final ListView list = getListView();
-               list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+               getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
                    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                         handleItemClick(parent, view, position, id);
                    }
                });
-               return list;
+               return getAllPagesView();
            }
       };
     }
 
     public int getTitleStringId() {
         return R.string.awesomebar_all_pages_title;
     }
 
     public String getTag() {
         return TAG;
     }
 
+    private LinearLayout getAllPagesView() {
+        if (mAllPagesView == null) {
+            mAllPagesView = (LinearLayout) (LayoutInflater.from(mContext).inflate(R.layout.awesomebar_allpages_list, null));
+        }
+        return mAllPagesView;
+    }
+
     public ListView getListView() {
         if (mView == null) {
-            mView = (ListView) (LayoutInflater.from(mContext).inflate(R.layout.awesomebar_list, null));
+            mView = getAllPagesView().findViewById(R.id.awesomebar_list);
             ((Activity)mContext).registerForContextMenu(mView);
             mView.setTag(TAG);
             AwesomeBarCursorAdapter adapter = getCursorAdapter();
             ((ListView)mView).setAdapter(adapter);
             mView.setOnTouchListener(mListListener);
         }
         return (ListView)mView;
     }
@@ -124,16 +132,22 @@ public class AllPagesTab extends Awesome
             cursor.close();
     }
 
     public void filter(String searchTerm) {
         AwesomeBarCursorAdapter adapter = getCursorAdapter();
         adapter.filter(searchTerm);
 
         filterSuggestions(searchTerm);
+        if (mSuggestionsOptInPrompt != null) {
+            int visibility = searchTerm.isEmpty() ? View.GONE : View.VISIBLE;
+            if (mSuggestionsOptInPrompt.getVisibility() != visibility) {
+                mSuggestionsOptInPrompt.setVisibility(visibility);
+            }
+        }
     }
 
     private void filterSuggestions(String searchTerm) {
         // cancel previous query
         if (mSuggestTask != null) {
             mSuggestTask.cancel(true);
         }
 
@@ -403,18 +417,17 @@ public class AllPagesTab extends Awesome
 
             // user-entered search term is first suggestion
             viewHolder.userEnteredTextView.setText(mSearchTerm);
             viewHolder.userEnteredView.setOnClickListener(clickListener);
             
             // add additional suggestions given by this engine
             int recycledSuggestionCount = suggestionView.getChildCount();
             int suggestionCount = engine.suggestions.size();
-            int i = 0;
-            for (i = 0; i < suggestionCount; i++) {
+            for (int i = 0; i < suggestionCount; i++) {
                 String suggestion = engine.suggestions.get(i);
                 View suggestionItem = null;
 
                 // reuse suggestion views from recycled view, if possible
                 if (i+1 < recycledSuggestionCount) {
                     suggestionItem = suggestionView.getChildAt(i+1);
                     suggestionItem.setVisibility(View.VISIBLE);
                 } else {
@@ -424,17 +437,17 @@ public class AllPagesTab extends Awesome
                 }
                 ((TextView) suggestionItem.findViewById(R.id.suggestion_text)).setText(suggestion);
 
                 suggestionItem.setOnClickListener(clickListener);
                 suggestionItem.setOnLongClickListener(longClickListener);
             }
             
             // hide extra suggestions that have been recycled
-            for (++i; i < recycledSuggestionCount; i++) {
+            for (int i = suggestionCount + 1; i < recycledSuggestionCount; i++) {
                 suggestionView.getChildAt(i).setVisibility(View.GONE);
             }
         }
     };
 
     private class SearchEngine {
         public String name;
         public Drawable icon;
@@ -450,47 +463,54 @@ public class AllPagesTab extends Awesome
             this.suggestions = new ArrayList<String>();
         }
     };
 
     /**
      * Sets suggestions associated with the current suggest engine.
      * If there is no suggest engine, this does nothing.
      */
-    public void setSuggestions(final ArrayList<String> suggestions) {
+    private void setSuggestions(final ArrayList<String> suggestions) {
         if (mSuggestClient != null) {
             mSearchEngines.get(0).suggestions = suggestions;
             getCursorAdapter().notifyDataSetChanged();
         }
     }
 
     /**
      * Sets search engines to be shown for user-entered queries.
      */
-    public void setSearchEngines(JSONObject data) {
+    private void setSearchEngines(JSONObject data) {
         try {
-            String suggestEngine = data.isNull("suggestEngine") ? null : data.getString("suggestEngine");
-            String suggestTemplate = data.isNull("suggestTemplate") ? null : data.getString("suggestTemplate");
+            JSONObject suggest = data.getJSONObject("suggest");
+            String suggestEngine = suggest.isNull("engine") ? null : suggest.getString("engine");
+            String suggestTemplate = suggest.isNull("template") ? null : suggest.getString("template");
+            mSuggestionsEnabled = suggest.getBoolean("enabled");
+            boolean suggestionsPrompted = suggest.getBoolean("prompted");
             JSONArray engines = data.getJSONArray("searchEngines");
-            mSuggestionsEnabled = data.getBoolean("suggestEnabled");
 
             mSearchEngines = new ArrayList<SearchEngine>();
             for (int i = 0; i < engines.length(); i++) {
                 JSONObject engineJSON = engines.getJSONObject(i);
                 String name = engineJSON.getString("name");
                 String iconURI = engineJSON.getString("iconURI");
                 Drawable icon = getDrawableFromDataURI(iconURI);
                 if (name.equals(suggestEngine) && suggestTemplate != null) {
                     // suggest engine should be at the front of the list
                     mSearchEngines.add(0, new SearchEngine(name, icon));
                     mSuggestClient = new SuggestClient(GeckoApp.mAppContext, suggestTemplate, SUGGESTION_TIMEOUT, SUGGESTION_MAX);
                 } else {
                     mSearchEngines.add(new SearchEngine(name, icon));
                 }
             }
+
+            // show suggestions opt-in if user hasn't been prompted
+            if (!suggestionsPrompted && mSuggestClient != null) {
+                showSuggestionsOptIn();
+            }
         } catch (JSONException e) {
             Log.e(LOGTAG, "Error getting search engine JSON", e);
         }
 
         filterSuggestions(mSearchTerm);
     }
 
     private Drawable getDrawableFromDataURI(String dataURI) {
@@ -502,16 +522,52 @@ public class AllPagesTab extends Awesome
             drawable = Drawable.createFromStream(stream, "src");
             stream.close();
         } catch (IllegalArgumentException e) {
             Log.i(LOGTAG, "exception while decoding drawable: " + base64, e);
         } catch (IOException e) { }
         return drawable;
     }
 
+    private void showSuggestionsOptIn() {
+        mSuggestionsOptInPrompt = LayoutInflater.from(mContext).inflate(R.layout.awesomebar_suggestion_prompt, getAllPagesView(), false);
+        ((TextView) mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_title))
+                .setText(getResources().getString(R.string.suggestions_prompt, mSearchEngines.get(0).name));
+        mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_yes).setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                setSuggestionsEnabled(true);
+            }
+        });
+        mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_no).setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                setSuggestionsEnabled(false);
+            }
+        });
+        mSuggestionsOptInPrompt.setVisibility(View.GONE);
+        getAllPagesView().addView(mSuggestionsOptInPrompt, 0);
+    }
+
+    private void setSuggestionsEnabled(final boolean enabled) {
+        // Pref observer in gecko will also set prompted = true
+        PrefsHelper.setPref("browser.search.suggest.enabled", enabled);
+
+        getAllPagesView().post(new Runnable() {
+            public void run() {
+                getAllPagesView().removeView(mSuggestionsOptInPrompt);
+                mSuggestionsOptInPrompt = null;
+
+                if (enabled) {
+                    mSuggestionsEnabled = enabled;
+                    getCursorAdapter().notifyDataSetChanged();
+                    filterSuggestions(mSearchTerm);
+                }
+            }
+        });
+    }
+
     public void handleMessage(String event, final JSONObject message) {
         if (event.equals("SearchEngines:Data")) {
             GeckoAppShell.getMainHandler().post(new Runnable() {
                 public void run() {
                     setSearchEngines(message);
                 }
             });
         }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -7,17 +7,16 @@
 <!ENTITY  splash_bookmarks_history "We are updating your bookmarks and history, which may take a minute or so.">
 
 <!ENTITY  no_space_to_start_error "There is not enough space available for &brandShortName; to start.">
 <!ENTITY  error_loading_file "An error occurred when trying to load files required to run &brandShortName;">
 
 <!ENTITY  awesomebar_all_pages_title "Top Sites">
 <!ENTITY  awesomebar_bookmarks_title "Bookmarks">
 <!ENTITY  awesomebar_history_title "History">
-<!ENTITY  awesomebar_search_engine "Search for \&quot;&#037;s\&quot;">
 
 <!ENTITY  crash_reporter_title "&brandShortName; Crash Reporter">
 <!ENTITY  crash_message "&brandShortName; has crashed. Your tabs should be listed on the &brandShortName; Start page when you restart.">
 <!ENTITY  crash_help_message "Please help us fix this problem!">
 <!ENTITY  crash_send_report_message "Send Mozilla a crash report">
 <!ENTITY  crash_include_url "Include page address">
 <!ENTITY  crash_close_label "Close">
 <!ENTITY  crash_restart_label "Restart &brandShortName;">
@@ -166,16 +165,18 @@ size. -->
 
 <!ENTITY masterpassword_create_title "Create Master Password">
 <!ENTITY masterpassword_remove_title "Remove Master Password">
 <!ENTITY masterpassword_password "Password">
 <!ENTITY masterpassword_confirm "Confirm password">
 
 <!ENTITY button_ok "OK">
 <!ENTITY button_cancel "Cancel">
+<!ENTITY button_yes "Yes">
+<!ENTITY button_no "No">
 <!ENTITY button_clear_data "Clear data">
 <!ENTITY button_set "Set">
 <!ENTITY button_clear "Clear">
 
 <!ENTITY abouthome_addons_title "Add-ons for your &brandShortName;">
 <!ENTITY abouthome_addons_browse "Browse all &brandShortName; add-ons">
 <!ENTITY abouthome_last_tabs_title "Your tabs from last time">
 <!ENTITY abouthome_last_tabs_open "Open all tabs from last time">
@@ -222,16 +223,21 @@ just addresses the organization to follo
 <!ENTITY bookmarkhistory_import_both "Importing bookmarks and history
                                       from Android">
 <!ENTITY bookmarkhistory_import_bookmarks "Importing bookmarks
                                            from Android">
 <!ENTITY bookmarkhistory_import_history "Importing history
                                          from Android">
 <!ENTITY bookmarkhistory_import_wait "Please wait...">
 
+<!-- Localization note (suggestions_prompt): The placeholder (&#037;s) should
+     be kept in the string; this will be replaced with the name of the search
+     engine. -->
+<!ENTITY suggestions_prompt "Would you like to turn on &#037;s search suggestions?">
+
 <!ENTITY webapp_generic_name "App">
 
 <!-- Updater notifications -->
 <!ENTITY updater_start_title "&brandShortName;">
 <!ENTITY updater_start_ticker "&brandShortName; update available&#8230;">
 <!ENTITY updater_start_select "Select to download update.">
 
 <!ENTITY updater_downloading_title "Downloading &brandShortName;">
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/awesomebar_allpages_list.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent"
+              android:orientation="vertical">
+
+    <include layout="@layout/awesomebar_list"
+             android:id="@+id/awesomebar_list"/>
+
+</LinearLayout>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/awesomebar_suggestion_prompt.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              android:minHeight="@dimen/awesomebar_row_height"
+              android:orientation="horizontal"
+              android:gravity="center_vertical"
+              android:padding="10dip">
+
+    <TextView android:id="@+id/suggestions_prompt_title"
+              android:layout_height="wrap_content"
+              android:layout_width="wrap_content"
+              android:textColor="?android:attr/textColorPrimary"
+              android:layout_marginLeft="6dip"
+              android:textSize="13sp"
+              android:layout_weight="1" />
+
+    <TextView android:id="@+id/suggestions_prompt_yes"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:textSize="13sp"
+            android:layout_marginLeft="15dip"
+            android:background="@drawable/suggestion_selector"
+            android:paddingLeft="15dp"
+            android:paddingRight="15dp"
+            android:paddingTop="7dp"
+            android:paddingBottom="7dp"
+            android:clickable="true"
+            android:text="@string/button_yes" />
+
+    <TextView android:id="@+id/suggestions_prompt_no"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:textSize="13sp"
+            android:layout_marginLeft="6dip"
+            android:background="@drawable/suggestion_selector"
+            android:paddingLeft="15dp"
+            android:paddingRight="15dp"
+            android:paddingTop="7dp"
+            android:paddingBottom="7dp"
+            android:clickable="true"
+            android:text="@string/button_no" />
+
+</LinearLayout>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -15,17 +15,16 @@
   <string name="splash_settingup">&splash_settingup;</string>
   <string name="splash_bookmarks_history">&splash_bookmarks_history;</string>
   <string name="no_space_to_start_error">&no_space_to_start_error;</string>
   <string name="error_loading_file">&error_loading_file;</string>
 
   <string name="awesomebar_all_pages_title">&awesomebar_all_pages_title;</string>
   <string name="awesomebar_bookmarks_title">&awesomebar_bookmarks_title;</string>
   <string name="awesomebar_history_title">&awesomebar_history_title;</string>
-  <string name="awesomebar_search_engine">&awesomebar_search_engine;</string>
 
   <string name="crash_reporter_title">&crash_reporter_title;</string>
   <string name="crash_message">&crash_message;</string>
   <string name="crash_help_message">&crash_help_message;</string>
   <string name="crash_send_report_message">&crash_send_report_message;</string>
   <string name="crash_include_url">&crash_include_url;</string>
   <string name="crash_close_label">&crash_close_label;</string>
   <string name="crash_restart_label">&crash_restart_label;</string>
@@ -162,16 +161,18 @@
   <string name="masterpassword_password">&masterpassword_password;</string>
   <string name="masterpassword_confirm">&masterpassword_confirm;</string>
 
   <string name="button_ok">&button_ok;</string>
   <string name="button_cancel">&button_cancel;</string>
   <string name="button_clear_data">&button_clear_data;</string>
   <string name="button_set">&button_set;</string>
   <string name="button_clear">&button_clear;</string>
+  <string name="button_yes">&button_yes;</string>
+  <string name="button_no">&button_no;</string>
 
   <string name="abouthome_addons_title">&abouthome_addons_title;</string>
   <string name="abouthome_addons_browse">&abouthome_addons_browse;</string>
   <string name="abouthome_last_tabs_title">&abouthome_last_tabs_title;</string>
   <string name="abouthome_last_tabs_open">&abouthome_last_tabs_open;</string>
   <string name="abouthome_top_sites_title">&abouthome_top_sites_title;</string>
   <string name="abouthome_top_sites_browse">&abouthome_top_sites_browse;</string>
   <string name="abouthome_about_sync">&abouthome_about_sync3;</string>
@@ -226,9 +227,12 @@
   <string name="updater_downloading_select">&updater_downloading_select;</string>
   <string name="updater_downloading_retry">&updater_downloading_retry;</string>
   <string name="updater_downloading_willapply">&updater_downloading_willapply;</string>
   
   <string name="updater_apply_title">&updater_apply_title;</string>
   <string name="updater_apply_ticker">&updater_apply_ticker;</string>
   <string name="updater_apply_select">&updater_apply_select;</string>
 
+  <!-- Search suggestions opt-in -->
+  <string name="suggestions_prompt">&suggestions_prompt;</string>
+
 </resources>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -917,16 +917,19 @@ var BrowserApp = {
       return;
     } else if (json.name == MasterPassword.pref) {
       // MasterPassword pref is not real, we just need take action and leave
       if (MasterPassword.enabled)
         MasterPassword.removePassword(json.value);
       else
         MasterPassword.setPassword(json.value);
       return;
+    } else if (json.name == SearchEngines.PREF_SUGGEST_ENABLED) {
+      // Enabling or disabling suggestions will prevent future prompts
+      Services.prefs.setBoolPref(SearchEngines.PREF_SUGGEST_PROMPTED, true);
     }
 
     // when sending to java, we normalized special preferences that use
     // integers and strings to represent booleans.  here, we convert them back
     // to their actual types so we can store them.
     switch (json.name) {
       case "network.cookie.cookieBehavior":
         json.type = "int";
@@ -6097,30 +6100,32 @@ OverscrollController.prototype = {
     sendMessageToJava({ gecko: { type: "ToggleChrome:Focus" } });
   },
 
   onEvent : function onEvent(aEvent) { }
 };
 
 var SearchEngines = {
   _contextMenuId: null,
+  PREF_SUGGEST_ENABLED: "browser.search.suggest.enabled",
+  PREF_SUGGEST_PROMPTED: "browser.search.suggest.prompted",
 
   init: function init() {
     Services.obs.addObserver(this, "SearchEngines:Get", false);
     let contextName = Strings.browser.GetStringFromName("contextmenu.addSearchEngine");
     let filter = {
       matches: function (aElement) {
         return (aElement.form && NativeWindow.contextmenus.textContext.matches(aElement));
       }
     };
     this._contextMenuId = NativeWindow.contextmenus.add(contextName, filter, this.addEngine);
   },
 
   uninit: function uninit() {
-    Services.obs.removeObserver(this, "SearchEngines:Get", false);
+    Services.obs.removeObserver(this, "SearchEngines:Get");
     if (this._contextMenuId != null)
       NativeWindow.contextmenus.remove(this._contextMenuId);
   },
 
   _handleSearchEnginesGet: function _handleSearchEnginesGet(rv) {
     if (!Components.isSuccessCode(rv)) {
       Cu.reportError("Could not initialize search service, bailing out.");
       return;
@@ -6140,19 +6145,22 @@ var SearchEngines = {
       suggestEngine = engine.name;
       suggestTemplate = engine.getSubmission("__searchTerms__", "application/x-suggestions+json").uri.spec;
     }
 
     sendMessageToJava({
       gecko: {
         type: "SearchEngines:Data",
         searchEngines: searchEngines,
-        suggestEngine: suggestEngine,
-        suggestTemplate: suggestTemplate,
-        suggestEnabled: Services.prefs.getBoolPref("browser.search.suggest.enabled")
+        suggest: {
+          engine: suggestEngine,
+          template: suggestTemplate,
+          enabled: Services.prefs.getBoolPref(this.PREF_SUGGEST_ENABLED),
+          prompted: Services.prefs.getBoolPref(this.PREF_SUGGEST_PROMPTED)
+        }
       }
     });
   },
 
   observe: function observe(aSubject, aTopic, aData) {
     if (aTopic == "SearchEngines:Get") {
       Services.search.init(this._handleSearchEnginesGet.bind(this));
     }