Merge fx-team to m-c.

This commit is contained in:
Ryan VanderMeulen 2013-08-05 19:52:46 -04:00
commit 8fbbb10186
30 changed files with 599 additions and 293 deletions

View File

@ -1416,9 +1416,15 @@
t.setAttribute("crop", "end");
t.setAttribute("onerror", "this.removeAttribute('image');");
t.className = "tabbrowser-tab";
#ifdef MAKE_E10S_WORK
let remote = this._shouldBrowserBeRemote(aURI);
#else
let remote = gMultiProcessBrowser;
#endif
if (remote)
t.setAttribute("remote", "true");
t.className = "tabbrowser-tab";
this.tabContainer._unlockTabSizing();
@ -1453,11 +1459,6 @@
b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
#ifdef MAKE_E10S_WORK
let remote = this._shouldBrowserBeRemote(aURI);
#else
let remote = gMultiProcessBrowser;
#endif
if (remote)
b.setAttribute("remote", "true");

View File

@ -547,13 +547,6 @@ let SessionStoreInternal = {
return this._prefBranch.getIntPref("sessionstore.interval");
});
// when crash recovery is disabled, session data is not written to disk
XPCOMUtils.defineLazyGetter(this, "_resume_from_crash", function () {
// get crash recovery state from prefs and allow for proper reaction to state changes
this._prefBranch.addObserver("sessionstore.resume_from_crash", this, true);
return this._prefBranch.getBoolPref("sessionstore.resume_from_crash");
});
XPCOMUtils.defineLazyGetter(this, "_max_tabs_undo", function () {
this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
return this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
@ -1191,14 +1184,6 @@ let SessionStoreInternal = {
}
this.saveStateDelayed(null, -1);
break;
case "sessionstore.resume_from_crash":
this._resume_from_crash = this._prefBranch.getBoolPref("sessionstore.resume_from_crash");
// either create the file with crash recovery information or remove it
// (when _loadState is not STATE_RUNNING, that file is used for session resuming instead)
if (!this._resume_from_crash)
_SessionFile.wipe();
this.saveState(true);
break;
}
},
@ -2547,11 +2532,9 @@ let SessionStoreInternal = {
* gather session data as object
* @param aUpdateAll
* Bool update all windows
* @param aPinnedOnly
* Bool collect pinned tabs only
* @returns object
*/
_getCurrentState: function ssi_getCurrentState(aUpdateAll, aPinnedOnly) {
_getCurrentState: function ssi_getCurrentState(aUpdateAll) {
this._handleClosedWindows();
var activeWindow = this._getMostRecentBrowserWindow();
@ -2614,24 +2597,6 @@ let SessionStoreInternal = {
}
#endif
if (aPinnedOnly) {
// perform a deep copy so that existing session variables are not changed.
total = JSON.parse(this._toJSONString(total));
total = total.filter(function (win) {
win.tabs = win.tabs.filter(function (tab) tab.pinned);
// remove closed tabs
win._closedTabs = [];
// correct selected tab index if it was stripped out
if (win.selected > win.tabs.length)
win.selected = 1;
return win.tabs.length > 0;
});
if (total.length == 0)
return null;
lastClosedWindowsCopy = [];
}
if (activeWindow) {
this.activeWindowSSiCache = activeWindow.__SSi || "";
}
@ -3759,12 +3724,10 @@ let SessionStoreInternal = {
saveState: function ssi_saveState(aUpdateAll) {
// If crash recovery is disabled, we only want to resume with pinned tabs
// if we crash.
let pinnedOnly = this._loadState == STATE_RUNNING && !this._resume_from_crash;
TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_DATA_MS");
TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS");
var oState = this._getCurrentState(aUpdateAll, pinnedOnly);
var oState = this._getCurrentState(aUpdateAll);
if (!oState) {
TelemetryStopwatch.cancel("FX_SESSION_RESTORE_COLLECT_DATA_MS");
TelemetryStopwatch.cancel("FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS");
@ -3844,8 +3807,7 @@ let SessionStoreInternal = {
}
// Write (atomically) to a session file, using a tmp file.
let promise =
_SessionFile.write(data, {backupOnFirstWrite: this._resume_from_crash});
let promise = _SessionFile.write(data);
// Once the session file is successfully updated, save the time stamp of the
// last save and notify the observers.

View File

@ -124,23 +124,33 @@ let Agent = {
/**
* Write the session to disk.
*/
write: function (stateString, options) {
write: function (stateString) {
let exn;
let telemetry = {};
if (!this.hasWrittenState) {
if (options && options.backupOnFirstWrite) {
try {
let startMs = Date.now();
File.move(this.path, this.backupPath);
telemetry.FX_SESSION_RESTORE_BACKUP_FILE_MS = Date.now() - startMs;
} catch (ex if isNoSuchFileEx(ex)) {
// Ignore exceptions about non-existent files.
}
try {
let startMs = Date.now();
File.move(this.path, this.backupPath);
telemetry.FX_SESSION_RESTORE_BACKUP_FILE_MS = Date.now() - startMs;
} catch (ex if isNoSuchFileEx(ex)) {
// Ignore exceptions about non-existent files.
} catch (ex) {
// Throw the exception after we wrote the state to disk
// so that the backup can't interfere with the actual write.
exn = ex;
}
this.hasWrittenState = true;
}
return this._write(stateString, telemetry);
let ret = this._write(stateString, telemetry);
if (exn) {
throw exn;
}
return ret;
},
/**

View File

@ -67,8 +67,8 @@ this._SessionFile = {
/**
* Write the contents of the session file, asynchronously.
*/
write: function (aData, aOptions = {}) {
return SessionFileInternal.write(aData, aOptions);
write: function (aData) {
return SessionFileInternal.write(aData);
},
/**
* Writes the initial state to disk again only to change the session's load
@ -209,13 +209,13 @@ let SessionFileInternal = {
});
},
write: function (aData, aOptions) {
write: function (aData) {
let refObj = {};
return TaskUtils.spawn(function task() {
TelemetryStopwatch.start("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
try {
let promise = SessionWorker.post("write", [aData, aOptions]);
let promise = SessionWorker.post("write", [aData]);
// At this point, we measure how long we stop the main thread
TelemetryStopwatch.finish("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);

View File

@ -25,7 +25,7 @@ add_task(function test_first_write_backup() {
let initial_content = decoder.decode(yield OS.File.read(pathStore));
do_check_true(!(yield OS.File.exists(pathBackup)));
yield _SessionFile.write(content, {backupOnFirstWrite: true});
yield _SessionFile.write(content);
do_check_true(yield OS.File.exists(pathBackup));
let backup_content = decoder.decode(yield OS.File.read(pathBackup));
@ -38,7 +38,7 @@ add_task(function test_second_write_no_backup() {
let initial_content = decoder.decode(yield OS.File.read(pathStore));
let initial_backup_content = decoder.decode(yield OS.File.read(pathBackup));
yield _SessionFile.write(content, {backupOnFirstWrite: true});
yield _SessionFile.write(content);
let written_content = decoder.decode(yield OS.File.read(pathStore));
do_check_eq(content, written_content);

View File

@ -1,32 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let toplevel = this;
Cu.import("resource://gre/modules/osfile.jsm");
function run_test() {
let profd = do_get_profile();
Cu.import("resource:///modules/sessionstore/_SessionFile.jsm", toplevel);
pathStore = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js");
pathBackup = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak");
let source = do_get_file("data/sessionstore_valid.js");
source.copyTo(profd, "sessionstore.js");
run_next_test();
}
let pathStore;
let pathBackup;
// Write to the store first with |backupOnFirstWrite: false|,
// and make sure second write does not backup even with
// |backupOnFirstWrite: true|
add_task(function test_no_backup_on_second_write() {
let content = "test_1";
do_check_true(!(yield OS.File.exists(pathBackup)));
yield _SessionFile.write(content, {backupOnFirstWrite: false});
do_check_true(!(yield OS.File.exists(pathBackup)));
yield _SessionFile.write(content, {backupOnFirstWrite: true});
do_check_true(!(yield OS.File.exists(pathBackup)));
});

View File

@ -5,7 +5,6 @@ firefox-appdir = browser
[test_backup.js]
[test_backup_once.js]
[test_no_backup_first_write.js]
[test_startup_nosession_sync.js]
# bug 845190 - thread pool wasn't shutdown assertions
skip-if = (os == "win" || "linux") && debug

View File

@ -73,10 +73,5 @@
</body>
</method>
</implementation>
<handlers>
<!-- Work around for bug 835175 -->
<handler event="click">false;</handler>
</handlers>
</binding>
</bindings>

View File

@ -99,10 +99,5 @@
document.getAnonymousElementByAttribute(this, "class", "flyoutpanel-contents");
]]></field>
</implementation>
<handlers>
<!-- Work around for bug 835175 -->
<handler event="click">false;</handler>
</handlers>
</binding>
</bindings>

View File

@ -181,8 +181,7 @@
<vbox id="page">
<vbox id="tray" class="tray-toolbar" observes="bcast_windowState" >
<!-- Tabs -->
<!-- onclick handler to work around bug 837242 -->
<hbox id="tabs-container" observes="bcast_windowState" onclick="void(0);">
<hbox id="tabs-container" observes="bcast_windowState">
<box id="tabs" flex="1"
observes="bcast_preciseInput"
onselect="BrowserUI.selectTabAndDismiss(this);"
@ -243,8 +242,7 @@
<box id="horizontal-scroller" class="scroller" orient="horizontal" left="0" bottom="0"/>
<!-- Content touch selection overlay -->
<!-- onclick addresses dom bug 835175 -->
<box onclick="false" class="selection-overlay-hidden" id="content-selection-overlay"/>
<box class="selection-overlay-hidden" id="content-selection-overlay"/>
</stack>
</vbox>
@ -738,11 +736,9 @@
</flyoutpanel>
<!-- Chrome touch selection overlay -->
<!-- onclick addresses dom bug 835175 -->
<box onclick="false" class="selection-overlay-hidden" id="chrome-selection-overlay"/>
<box class="selection-overlay-hidden" id="chrome-selection-overlay"/>
<box onclick="event.stopPropagation();" id="context-container" class="menu-container" hidden="true">
<!-- onclick is dom bug 835175 -->
<vbox id="context-popup" class="menu-popup">
<richlistbox id="context-commands" bindingType="contextmenu" flex="1">
<!-- priority="low" items are hidden by default when a context is being displayed

View File

@ -153,10 +153,12 @@ var FindHelperUI = {
},
goToPrevious: function findHelperGoToPrevious() {
this._textbox.blur();
Browser.selectedBrowser.messageManager.sendAsyncMessage("FindAssist:Previous", { });
},
goToNext: function findHelperGoToNext() {
this._textbox.blur();
Browser.selectedBrowser.messageManager.sendAsyncMessage("FindAssist:Next", { });
},

View File

@ -35,18 +35,22 @@ class CameraImageResultHandler implements ActivityResultHandler {
@Override
public void onActivityResult(int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {
mFilePickerResult.offer("");
if (mFilePickerResult != null) {
mFilePickerResult.offer("");
}
return;
}
File file = new File(Environment.getExternalStorageDirectory(), sImageName);
sImageName = "";
if (mFilePickerResult != null)
if (mFilePickerResult != null) {
mFilePickerResult.offer(file.getAbsolutePath());
}
if (mHandler != null)
if (mHandler != null) {
mHandler.gotFile(file.getAbsolutePath());
}
}
// this code is really hacky and doesn't belong anywhere so I'm putting it here for now

View File

@ -234,6 +234,7 @@ FENNEC_JAVA_FILES = \
menu/MenuPanel.java \
menu/MenuPopup.java \
preferences/SearchPreferenceCategory.java \
preferences/SearchEnginePreference.java \
widget/AboutHome.java \
widget/AboutHomeView.java \
widget/AboutHomeSection.java \
@ -473,6 +474,7 @@ RES_LAYOUT = \
res/layout/notification_progress.xml \
res/layout/notification_progress_text.xml \
res/layout/preference_rightalign_icon.xml \
res/layout/preference_search_tip.xml \
res/layout/search_engine_row.xml \
res/layout/site_setting_item.xml \
res/layout/site_setting_title.xml \
@ -671,6 +673,7 @@ RES_DRAWABLE_MDPI = \
res/drawable-mdpi/tab_thumbnail_shadow.png \
res/drawable-mdpi/tabs_count.png \
res/drawable-mdpi/tabs_count_foreground.png \
res/drawable-mdpi/tip_addsearch.png \
res/drawable-mdpi/toast.9.png \
res/drawable-mdpi/toast_button_focused.9.png \
res/drawable-mdpi/toast_button_pressed.9.png \
@ -783,6 +786,7 @@ RES_DRAWABLE_HDPI = \
res/drawable-hdpi/tab_thumbnail_shadow.png \
res/drawable-hdpi/tabs_count.png \
res/drawable-hdpi/tabs_count_foreground.png \
res/drawable-hdpi/tip_addsearch.png \
res/drawable-hdpi/address_bar_url_default.9.png \
res/drawable-hdpi/address_bar_url_default_pb.9.png \
res/drawable-hdpi/address_bar_url_pressed.9.png \
@ -879,6 +883,7 @@ RES_DRAWABLE_XHDPI = \
res/drawable-xhdpi/tab_thumbnail_shadow.png \
res/drawable-xhdpi/tabs_count.png \
res/drawable-xhdpi/tabs_count_foreground.png \
res/drawable-xhdpi/tip_addsearch.png \
res/drawable-xhdpi/find_close.png \
res/drawable-xhdpi/find_next.png \
res/drawable-xhdpi/find_prev.png \

View File

@ -90,7 +90,7 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener {
mSearchEngines = new ArrayList<SearchEngine>();
registerEventListener("SearchEngines:Data");
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:Get", null));
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:GetVisible", null));
mHandler = new AllPagesHandler();
}

View File

@ -72,6 +72,11 @@
<!ENTITY pref_category_vendor "&vendorShortName;">
<!ENTITY pref_category_datareporting "Data choices">
<!ENTITY pref_category_installed_search_engines "Installed search engines">
<!ENTITY pref_category_add_search_providers "Add more search providers">
<!-- Localization note (pref_search_tip) : "TIP" as in "hint", "clue" etc. Displayed as an
advisory message on the customise search providers settings page explaining how to add new
search providers.-->
<!ENTITY pref_search_tip "TIP: Add any website to your list of search providers by long-pressing on its search field.">
<!ENTITY pref_category_devtools "Developer tools">
<!ENTITY pref_developer_remotedebugging "Remote debugging">
<!ENTITY pref_developer_remotedebugging_docs "Learn more">
@ -144,6 +149,10 @@ size. -->
<!ENTITY pref_about_firefox "About &brandShortName;">
<!ENTITY pref_vendor_faqs "FAQs">
<!ENTITY pref_vendor_feedback "Give feedback">
<!ENTITY pref_search_set_default "Set as default">
<!ENTITY pref_search_default "Default">
<!ENTITY pref_search_remove "Remove">
<!ENTITY pref_search_last_toast "You can\'t remove or disable your last search engine.">
<!ENTITY datareporting_notification_title "&brandShortName; stats &amp; data">
<!ENTITY datareporting_notification_action_long "Choose what information to share">

View File

@ -0,0 +1,229 @@
/* 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.gecko.preferences;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.preference.Preference;
import android.text.SpannableString;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.R;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.ThreadUtils;
/**
* Represents an element in the list of search engines on the preferences menu.
*/
public class SearchEnginePreference extends Preference {
private static final String LOGTAG = "SearchEnginePreference";
// Dimensions, in dp, of the icon to display for this engine.
public static int sIconSize;
// Indices in button array of the AlertDialog of the three buttons.
public static final int INDEX_SET_DEFAULT_BUTTON = 0;
public static final int INDEX_REMOVE_BUTTON = 1;
// Cache label to avoid repeated use of the resource system.
public final String LABEL_IS_DEFAULT;
// Specifies if this engine is configured as the default search engine.
private boolean mIsDefaultEngine;
// Specifies if this engine is one of the ones bundled with the app, which cannot be deleted.
private boolean mIsImmutableEngine;
// Dialog element labels.
private String[] mDialogItems;
// The popup displayed when this element is tapped.
private AlertDialog mDialog;
private final SearchPreferenceCategory mParentCategory;
/**
* Create a preference object to represent a search engine that is attached to category
* containingCategory.
* @param context The activity context we operate under.
* @param parentCategory The PreferenceCategory this object exists within.
* @see this.setSearchEngine
*/
public SearchEnginePreference(Context context, SearchPreferenceCategory parentCategory) {
super(context);
mParentCategory = parentCategory;
Resources res = getContext().getResources();
// Fetch the icon dimensions from the resource file.
sIconSize = res.getDimensionPixelSize(R.dimen.searchpreferences_icon_size);
setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
SearchEnginePreference sPref = (SearchEnginePreference) preference;
sPref.showDialog();
return true;
}
});
// Fetch this resource now, instead of every time we ever want to relabel a button.
LABEL_IS_DEFAULT = res.getString(R.string.pref_search_default);
// Set up default dialog items.
mDialogItems = new String[] { res.getString(R.string.pref_search_set_default),
res.getString(R.string.pref_search_remove) };
}
/**
* Configure this Preference object from the Gecko search engine JSON object.
* @param geckoEngineJSON The Gecko-formatted JSON object representing the search engine.
* @throws JSONException If the JSONObject is invalid.
*/
public void setSearchEngineFromJSON(JSONObject geckoEngineJSON) throws JSONException {
final String engineName = geckoEngineJSON.getString("name");
SpannableString titleSpannable = new SpannableString(engineName);
mIsImmutableEngine = geckoEngineJSON.getBoolean("immutable");
if (mIsImmutableEngine) {
// Delete the "Remove" option from the menu.
mDialogItems = new String[] { getContext().getResources().getString(R.string.pref_search_set_default) };
}
setTitle(titleSpannable);
// setIcon is only available on Honeycomb and up.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// Create a drawable from the iconURI and assign it to this Preference for display.
String iconURI = geckoEngineJSON.getString("iconURI");
Bitmap iconBitmap = BitmapUtils.getBitmapFromDataURI(iconURI);
Bitmap scaledIconBitmap = Bitmap.createScaledBitmap(iconBitmap, sIconSize, sIconSize, false);
BitmapDrawable drawable = new BitmapDrawable(scaledIconBitmap);
setIcon(drawable);
}
}
/**
* Set if this object's UI should show that this is the default engine.
* @param isDefault Flag indicating if this represents the default engine.
*/
public void setIsDefaultEngine(boolean isDefault) {
mIsDefaultEngine = isDefault;
if (isDefault) {
setOrder(0);
setSummary(LABEL_IS_DEFAULT);
} else {
setOrder(1);
setSummary("");
}
}
/**
* Display the AlertDialog providing options to reconfigure this search engine. Sets an event
* listener to disable buttons in the dialog as appropriate after they have been constructed by
* Android.
* @see this.configureShownDialog
* @see this.hideDialog
*/
public void showDialog() {
// If we are the only engine left, then we are the default engine, and none of the options
// on this menu can do anything.
if (mParentCategory.getPreferenceCount() == 1) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getContext(), R.string.pref_search_last_toast, Toast.LENGTH_SHORT).show();
}
});
return;
}
// If we are both default and immutable, we have no enabled items to show on the menu - abort.
if (mIsDefaultEngine && mIsImmutableEngine) {
return;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(getTitle().toString());
builder.setItems(mDialogItems, new DialogInterface.OnClickListener() {
// Forward the various events that we care about to the container class for handling.
@Override
public void onClick(DialogInterface dialog, int indexClicked) {
hideDialog();
switch (indexClicked) {
case INDEX_SET_DEFAULT_BUTTON:
mParentCategory.setDefault(SearchEnginePreference.this);
break;
case INDEX_REMOVE_BUTTON:
mParentCategory.uninstall(SearchEnginePreference.this);
break;
default:
Log.w(LOGTAG, "Selected index out of range.");
break;
}
}
});
// Copy the icon, if any, from this object to the prompt we produce.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
builder.setIcon(getIcon());
}
// We have to construct the dialog itself on the UI thread.
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
mDialog = builder.create();
mDialog.setOnShowListener(new DialogInterface.OnShowListener() {
// Called when the dialog is shown (so we're finally able to manipulate button enabledness).
@Override
public void onShow(DialogInterface dialog) {
configureShownDialog();
}
});
mDialog.show();
}
});
}
/**
* Hide the dialog we previously created, if any.
*/
public void hideDialog() {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
// Null check so we can chain engine-mutating methods up in SearchPreferenceCategory
// without consequence.
if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss();
}
}
});
}
/**
* Disables buttons in the shown AlertDialog as required. The button elements are not created
* until after we call show, so this method has to be called from the onShowListener above.
* @see this.showDialog
*/
private void configureShownDialog() {
// If we are the default engine, disable the "Set as default" button.
TextView defaultButton = (TextView) mDialog.getListView().getChildAt(INDEX_SET_DEFAULT_BUTTON);
// Disable "Set as default" button if we are already the default.
if (mIsDefaultEngine) {
defaultButton.setEnabled(false);
// Failure to unregister this listener leads to tapping the button dismissing the dialog
// without doing anything.
defaultButton.setOnClickListener(null);
}
}
}

View File

@ -1,9 +1,10 @@
/* 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.gecko.preferences;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.util.AttributeSet;
@ -13,38 +14,36 @@ import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.R;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.GeckoEventListener;
public class SearchPreferenceCategory extends PreferenceCategory implements GeckoEventListener {
public static final String LOGTAG = "SearchPrefCategory";
private static int sIconSize;
private SearchEnginePreference mDefaultEngineReference;
// These seemingly redundant constructors are mandated by the Android system, else it fails to
// inflate this object.
public SearchPreferenceCategory(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public SearchPreferenceCategory(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SearchPreferenceCategory(Context context) {
super(context);
init();
}
private void init() {
sIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.searchpreferences_icon_size);
}
@Override
protected void onAttachedToActivity() {
super.onAttachedToActivity();
// Request list of search engines from Gecko
// Ensures default engine remains at top of list.
setOrderingAsAdded(false);
// Request list of search engines from Gecko.
GeckoAppShell.registerEventListener("SearchEngines:Data", this);
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:Get", null));
}
@ -52,9 +51,17 @@ public class SearchPreferenceCategory extends PreferenceCategory implements Geck
@Override
public void handleMessage(String event, final JSONObject data) {
if (event.equals("SearchEngines:Data")) {
// Parse engines array from JSON. The first element in the array is the default engine.
JSONArray engines;
JSONObject defaultEngine;
final String defaultEngineName;
try {
engines = data.getJSONArray("searchEngines");
if (engines.length() == 0) {
return;
}
defaultEngine = engines.getJSONObject(0);
defaultEngineName = defaultEngine.getString("name");
} catch (JSONException e) {
Log.e(LOGTAG, "Unable to decode search engine data from Gecko.", e);
return;
@ -66,24 +73,90 @@ public class SearchPreferenceCategory extends PreferenceCategory implements Geck
JSONObject engineJSON = engines.getJSONObject(i);
final String engineName = engineJSON.getString("name");
Preference engine = new Preference(getContext());
engine.setTitle(engineName);
engine.setKey(engineName);
// The setIcon feature is not available prior to API 11.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
String iconURI = engineJSON.getString("iconURI");
Bitmap iconBitmap = BitmapUtils.getBitmapFromDataURI(iconURI);
Bitmap scaledIconBitmap = Bitmap.createScaledBitmap(iconBitmap, sIconSize, sIconSize, false);
BitmapDrawable drawable = new BitmapDrawable(scaledIconBitmap);
engine.setIcon(drawable);
SearchEnginePreference enginePreference = new SearchEnginePreference(getContext(), this);
enginePreference.setSearchEngineFromJSON(engineJSON);
if (engineName.equals(defaultEngineName)) {
// We set this here, not in setSearchEngineFromJSON, because it allows us to
// keep a reference to the default engine to use when the AlertDialog
// callbacks are used.
enginePreference.setIsDefaultEngine(true);
mDefaultEngineReference = enginePreference;
}
addPreference(engine);
// TODO: Bug 892113 - Add event listener here for tapping on each element. Produce a dialog to provide options.
enginePreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
SearchEnginePreference sPref = (SearchEnginePreference) preference;
// Display the configuration dialog associated with the tapped engine.
sPref.showDialog();
return true;
}
});
addPreference(enginePreference);
} catch (JSONException e) {
Log.e(LOGTAG, "JSONException parsing engine at index " + i, e);
}
}
}
// We are no longer interested in this event from Gecko, as we do not request it again with
// this instance.
GeckoAppShell.unregisterEventListener("SearchEngines:Data", this);
}
/**
* Set the default engine to any available engine. Used if the current default is removed or
* disabled.
*/
private void setFallbackDefaultEngine() {
if (getPreferenceCount() > 0) {
SearchEnginePreference aEngine = (SearchEnginePreference) getPreference(0);
setDefault(aEngine);
}
}
/**
* Helper method to send a particular event string to Gecko with an associated engine name.
* @param event The type of event to send.
* @param engine The engine to which the event relates.
*/
private void sendGeckoEngineEvent(String event, SearchEnginePreference engine) {
JSONObject json = new JSONObject();
try {
json.put("engine", engine.getTitle());
} catch (JSONException e) {
Log.e(LOGTAG, "JSONException creating search engine configuration change message for Gecko.", e);
return;
}
GeckoAppShell.notifyGeckoOfEvent(GeckoEvent.createBroadcastEvent(event, json.toString()));
}
// Methods called by tapping items on the submenus for each search engine are below.
/**
* Removes the given engine from the set of available engines.
* @param engine The engine to remove.
*/
public void uninstall(SearchEnginePreference engine) {
removePreference(engine);
if (engine == mDefaultEngineReference) {
// If they're deleting their default engine, get them a new default engine.
setFallbackDefaultEngine();
}
sendGeckoEngineEvent("SearchEngines:Remove", engine);
}
/**
* Sets the given engine as the current default engine.
* @param engine The intended new default engine.
*/
public void setDefault(SearchEnginePreference engine) {
engine.setIsDefaultEngine(true);
mDefaultEngineReference.setIsDefaultEngine(false);
mDefaultEngineReference = engine;
sendGeckoEngineEvent("SearchEngines:SetDefault", engine);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -0,0 +1,37 @@
<?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:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingRight="?android:attr/scrollbarSize">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:layout_marginLeft="15dip"
android:layout_marginRight="6dip"
android:layout_marginTop="5dip"
android:layout_marginBottom="6dip"
android:paddingRight="6dip"
android:layout_weight="1">
<TextView android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:text="@string/pref_search_tip"/>
</RelativeLayout>
<ImageView android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingRight="8dp"
android:paddingTop="12dip"
android:src="@drawable/tip_addsearch"/>
</LinearLayout>

View File

@ -15,4 +15,13 @@
<org.mozilla.gecko.preferences.SearchPreferenceCategory
android:title="@string/pref_category_installed_search_engines"/>
<PreferenceCategory android:title="@string/pref_category_add_search_providers">
<Preference android:layout="@layout/preference_search_tip"
android:enabled="false"
android:selectable="false"/>
</PreferenceCategory>
</PreferenceScreen>

View File

@ -85,6 +85,8 @@
<string name="pref_category_vendor">&pref_category_vendor;</string>
<string name="pref_category_datareporting">&pref_category_datareporting;</string>
<string name="pref_category_installed_search_engines">&pref_category_installed_search_engines;</string>
<string name="pref_category_add_search_providers">&pref_category_add_search_providers;</string>
<string name="pref_search_tip">&pref_search_tip;</string>
<string name="pref_category_devtools">&pref_category_devtools;</string>
<string name="pref_developer_remotedebugging">&pref_developer_remotedebugging;</string>
<string name="pref_developer_remotedebugging_docs">&pref_developer_remotedebugging_docs;</string>
@ -151,6 +153,12 @@
<string name="pref_vendor_faqs">&pref_vendor_faqs;</string>
<string name="pref_vendor_feedback">&pref_vendor_feedback;</string>
<!-- Strings used in default search provider config preferences menu -->
<string name="pref_search_set_default">&pref_search_set_default;</string>
<string name="pref_search_default">&pref_search_default;</string>
<string name="pref_search_remove">&pref_search_remove;</string>
<string name="pref_search_last_toast">&pref_search_last_toast;</string>
<string name="datareporting_notification_title">&datareporting_notification_title;</string>
<string name="datareporting_notification_action_long">&datareporting_notification_action_long;</string>
<string name="datareporting_notification_action">&datareporting_notification_action;</string>

View File

@ -704,28 +704,6 @@ abstract class BaseTest extends ActivityInstrumentationTestCase2<Activity> {
hitEnterAndWait();
}
/**
* This method parses the string received as event data from blockForEventData()
* blockForEventData returns the data from a Gecko event as a string.
* The data contains tuples structured like this: "property":"value" separated by "," in most cases but this may vary
* The method takes as input the event data string and a string of delimiters to parse after.
* @return an ArrayList<String> with the 2*k (even) index the property and the 2k+1 (odd) index the value, 0<=k<=ArrayList.size()/2
*/
public ArrayList<String> parseEventData(String eventData, String dataDelimiters) {
ArrayList<String> parsedEventData = new ArrayList<String>();
String[] parseData = eventData.split(dataDelimiters);
for (String data:parseData) {
// Sometimes the property can contain : so only split the string when we have "String":"String"
String[] parseDataEntry = data.split("\":");
for (String dataContent:parseDataEntry) {
// Strip extra characters like event data beginning and ending brakets, remaining extra quote marks
dataContent = dataContent.replaceAll("[}{\"]","");
parsedEventData.add(dataContent);
}
}
return parsedEventData;
}
public final void runOnUiThreadSync(Runnable runnable) {
RobocopUtils.runOnUiThreadSync(mActivity, runnable);
}

View File

@ -5,13 +5,17 @@ import @ANDROID_PACKAGE_NAME@.*;
import android.view.View;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.util.Log;
import java.util.ArrayList;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Test adding a search engine from an input field context menu
* 1. Get the number of existing search engines
* 2. Load a page with a text field, open the context menu and add a search engine from the page
* 3. Get the number of search engines after adding the new one
* Test adding a search engine from an input field context menu.
* 1. Get the number of existing search engines (As shown in the AwesomeScreen).
* 2. Load a page with a text field, open the context menu and add a search engine from the page.
* 3. Get the number of search engines after adding the new one and verify it has increased by 1.
*/
public class testAddSearchEngine extends PixelTest {
private final int MAX_WAIT_TEST_MS = 5000;
@ -21,40 +25,45 @@ public class testAddSearchEngine extends PixelTest {
}
public void testAddSearchEngine() {
int height,width;
final int initialNumSearchEngines;
String blank = getAbsoluteUrl("/robocop/robocop_blank_01.html");
String url = getAbsoluteUrl("/robocop/robocop_search.html");
String blankPageURL = getAbsoluteUrl("/robocop/robocop_blank_01.html");
String searchEngineURL = getAbsoluteUrl("/robocop/robocop_search.html");
blockForGeckoReady();
loadUrl(blank);
loadUrl(blankPageURL);
waitForText("Browser Blank Page 01");
// Get the searchengine data
// Get the searchengine data by clicking the awesomebar - this causes Gecko to send Java the list
// of search engines.
Actions.EventExpecter searchEngineDataEventExpector = mActions.expectGeckoEvent("SearchEngines:Data");
clickOnAwesomeBar();
String eventData = searchEngineDataEventExpector.blockForEventData();
searchEngineDataEventExpector.unregisterListener();
// Parse the data to get the number of searchengines
ArrayList<String> parsedData = parseEventData(eventData, "[\"{}],");
ArrayList<String> searchEngines = getSearchEnginesNames(parsedData);
initialNumSearchEngines = searchEngines.size();
ArrayList<String> searchEngines;
try {
// Parse the data to get the number of searchengines.
searchEngines = getSearchEnginesNames(eventData);
} catch (JSONException e) {
mAsserter.ok(false, "Fatal exception in testAddSearchEngine while decoding JSON search engine string from Gecko prior to addition of new engine.", e.toString());
return;
}
final int initialNumSearchEngines = searchEngines.size();
mAsserter.dumpLog("Search Engines list = " + searchEngines.toString());
// Verify that the number of displayed searchengines is the same as the one received through the SearchEngines:Data event
// Verify that the number of displayed search engines is the same as the one received through the SearchEngines:Data event.
verifyDisplayedSearchEnginesCount("Browser Blank Page 01", initialNumSearchEngines);
loadUrl(url);
// Load the page for the search engine to add.
loadUrl(searchEngineURL);
waitForText("Robocop Search Engine");
// Open the context menu for the input field
height = mDriver.getGeckoTop() + 10;
width = mDriver.getGeckoLeft() + 20;
// Used to long-tap on the search input box for the search engine to add.
int height = mDriver.getGeckoTop() + 10;
int width = mDriver.getGeckoLeft() + 20;
mAsserter.dumpLog("Long Clicking at width = " + String.valueOf(width) + " and height = " + String.valueOf(height));
mSolo.clickLongOnScreen(width,height);
if (!waitForText("Add Search Engine")) {
// TODO: clickLongOnScreen does not always work - known Robotium issue - . Clicking a second time seems to work
// TODO: clickLongOnScreen does not always work - known Robotium issue - . Clicking a second time seems to work.
mAsserter.dumpLog("Something went wrong and the context menu was not opened. Trying again");
mSolo.clickLongOnScreen(width,height);
}
@ -67,33 +76,59 @@ public class testAddSearchEngine extends PixelTest {
mAsserter.ok(!mSolo.searchText("Add Search Engine"), "Adding the Search Engine", "The add Search Engine pop-up has been closed");
// Load Robocop Blank 1 again to give the time for the searchengine to be added
loadAndPaint(blank);
// TODO: This is a potential source of intermittent oranges - it's a race condition!
loadAndPaint(blankPageURL);
waitForText("Browser Blank Page 01");
// Check that the number of search results has increased
// Load search engines again and check that the quantity of engines has increased by 1.
searchEngineDataEventExpector = mActions.expectGeckoEvent("SearchEngines:Data");
clickOnAwesomeBar();
eventData = searchEngineDataEventExpector.blockForEventData();
parsedData = parseEventData(eventData, "[\"{}],");
searchEngines = getSearchEnginesNames(parsedData);
try {
// Parse the data to get the number of searchengines
searchEngines = getSearchEnginesNames(eventData);
} catch (JSONException e) {
mAsserter.ok(false, "Fatal exception in testAddSearchEngine while decoding JSON search engine string from Gecko after adding of new engine.", e.toString());
return;
}
mAsserter.dumpLog("Search Engines list = " + searchEngines.toString());
mAsserter.is(searchEngines.size(), initialNumSearchEngines + 1, "Checking the number of Search Engines has increased");
// Verify that the number of displayed searchengines is the same as the one received through the SearchEngines:Data event
// Verify that the number of displayed searchengines is the same as the one received through the SearchEngines:Data event.
verifyDisplayedSearchEnginesCount("Browser Blank Page 01", initialNumSearchEngines + 1);
searchEngineDataEventExpector.unregisterListener();
}
public ArrayList<String> getSearchEnginesNames(ArrayList<String> parsedSearchEngineData) {
/**
* Helper method to decode a list of search engine names from the provided search engine information
* JSON string sent from Gecko.
* @param searchEngineData The JSON string representing the search engine array to process
* @return An ArrayList<String> containing the names of all the search engines represented in
* the provided JSON message.
* @throws JSONException In the event that the JSON provided cannot be decoded.
*/
public ArrayList<String> getSearchEnginesNames(String searchEngineData) throws JSONException {
JSONObject data = new JSONObject(searchEngineData);
JSONArray engines = data.getJSONArray("searchEngines");
ArrayList<String> searchEngineNames = new ArrayList<String>();
for (int i=0; i<parsedSearchEngineData.size(); i++) {
if (parsedSearchEngineData.get(i).equals("name")) {
searchEngineNames.add(parsedSearchEngineData.get(i+1));
}
for (int i = 0; i < engines.length(); i++) {
JSONObject engineJSON = engines.getJSONObject(i);
searchEngineNames.add(engineJSON.getString("name"));
}
return searchEngineNames;
}
/**
* Method to verify that the displayed number of search engines matches the expected number.
* Uses a BooleanTest which counts how many SearchEngineRow instances are being displayed
* in the Awesomescreen.
* @param waitText Text from the loaded page to expect. Used to detect when the Awesomescreen
* close animation has completed.
* @param expectedCountParam The expected number of search engines.
*/
public void verifyDisplayedSearchEnginesCount(String waitText, int expectedCountParam) {
final int expectedCount = expectedCountParam;
mActions.sendKeys("Firefox for Android");
@ -106,16 +141,16 @@ public class testAddSearchEngine extends PixelTest {
for (ListView view : views) {
ListAdapter adapter = view.getAdapter();
if (adapter != null) {
// Get only the SearchEngineRow views since getCurrentViews can also add to the ArrayList also the Top Sites entries
// Only count SearchEngineRow views - other views are not relavent to this test.
try {
ClassLoader classLoader = getActivity().getClassLoader();
Class searchEngineRow = classLoader.loadClass("org.mozilla.gecko.SearchEngineRow");
for (int i = 0; i < adapter.getCount(); i++ ) {
View item = view.getChildAt(i);
if (searchEngineRow.isInstance(item)) {
searchEngineCount++;
}
ClassLoader classLoader = getActivity().getClassLoader();
Class searchEngineRow = classLoader.loadClass("org.mozilla.gecko.SearchEngineRow");
for (int i = 0; i < adapter.getCount(); i++ ) {
View item = view.getChildAt(i);
if (searchEngineRow.isInstance(item)) {
searchEngineCount++;
}
}
} catch (Exception e) {
mAsserter.dumpLog("Exception in verifyDisplayedSearchEnginesCount", e);
}

View File

@ -61,12 +61,7 @@ var ContextMenus = {
document.getElementById("contextmenu-disable").setAttribute("hidden", "true");
}
// Only show the "Set as Default" menuitem for enabled non-default search engines.
if (addon.type == "search" && enabled && addon.id != Services.search.defaultEngine.name) {
document.getElementById("contextmenu-default").removeAttribute("hidden");
} else {
document.getElementById("contextmenu-default").setAttribute("hidden", "true");
}
document.getElementById("contextmenu-default").setAttribute("hidden", "true");
},
enable: function(event) {
@ -82,17 +77,11 @@ var ContextMenus = {
uninstall: function (event) {
Addons.uninstall(this.target.addon);
this.target = null;
},
setDefaultSearch: function(event) {
Addons.setDefaultSearch(this.target.addon);
this.target = null;
}
}
function init() {
window.addEventListener("popstate", onPopState, false);
Services.obs.addObserver(Addons, "browser-search-engine-modified", false);
AddonManager.addInstallListener(Addons);
AddonManager.addAddonListener(Addons);
@ -102,7 +91,6 @@ function init() {
}
function uninit() {
Services.obs.removeObserver(Addons, "browser-search-engine-modified");
AddonManager.removeInstallListener(Addons);
AddonManager.removeAddonListener(Addons);
}
@ -361,16 +349,7 @@ var Addons = {
uninstallBtn.removeAttribute("disabled");
let defaultButton = document.getElementById("default-btn");
if (addon.type == "search") {
if (addon.id == Services.search.defaultEngine.name)
defaultButton.setAttribute("disabled", "true");
else
defaultButton.removeAttribute("disabled");
defaultButton.removeAttribute("hidden");
} else {
defaultButton.setAttribute("hidden", "true");
}
defaultButton.setAttribute("hidden", "true");
let box = document.querySelector("#addons-details > .addon-item .options-box");
box.innerHTML = "";
@ -424,10 +403,7 @@ var Addons = {
let listItem = this._getElementForAddon(addon.id);
let opType;
if (addon.type == "search") {
addon.engine.hidden = !aValue;
opType = aValue ? "needs-enable" : "needs-disable";
} else if (addon.type == "theme") {
if (addon.type == "theme") {
if (aValue) {
// We can have only one theme enabled, so disable the current one if any
let list = document.getElementById("addons-list");
@ -492,30 +468,21 @@ var Addons = {
let listItem = this._getElementForAddon(addon.id);
if (addon.type == "search") {
// Make sure the engine isn't hidden before removing it, to make sure it's
// visible if the user later re-adds it (works around bug 341833)
addon.engine.hidden = false;
Services.search.removeEngine(addon.engine);
// the search-engine-modified observer will take care of updating the list
history.back();
addon.uninstall();
if (addon.pendingOperations & AddonManager.PENDING_UNINSTALL) {
this.showRestart();
// A disabled addon doesn't need a restart so it has no pending ops and
// can't be cancelled
let opType = this._getOpTypeForOperations(addon.pendingOperations);
if (!addon.isActive && opType == "")
opType = "needs-uninstall";
detailItem.setAttribute("opType", opType);
listItem.setAttribute("opType", opType);
} else {
addon.uninstall();
if (addon.pendingOperations & AddonManager.PENDING_UNINSTALL) {
this.showRestart();
// A disabled addon doesn't need a restart so it has no pending ops and
// can't be cancelled
let opType = this._getOpTypeForOperations(addon.pendingOperations);
if (!addon.isActive && opType == "")
opType = "needs-uninstall";
detailItem.setAttribute("opType", opType);
listItem.setAttribute("opType", opType);
} else {
list.removeChild(listItem);
history.back();
}
list.removeChild(listItem);
history.back();
}
},
@ -535,20 +502,6 @@ var Addons = {
listItem.setAttribute("opType", opType);
},
setDefaultSearch: function setDefaultSearch(aAddon) {
let addon = aAddon || document.querySelector("#addons-details > .addon-item").addon;
if (addon.type != "search")
return;
let engine = Services.search.getEngineByName(addon.id);
// Move the new default search engine to the top of the search engine list.
Services.search.moveEngine(engine, 0);
Services.search.defaultEngine = engine;
document.getElementById("default-btn").setAttribute("disabled", "true");
},
showRestart: function showRestart() {
this._restartCount++;
gChromeWin.XPInstallObserver.showRestartPrompt();
@ -590,18 +543,6 @@ var Addons = {
element.setAttribute("opType", "needs-restart");
},
observe: function observe(aSubject, aTopic, aData) {
if (aTopic == "browser-search-engine-modified") {
switch (aData) {
case "engine-added":
case "engine-removed":
case "engine-changed":
this.getAddons();
break;
}
}
},
onInstallFailed: function(aInstall) {
},

View File

@ -57,7 +57,6 @@
<menuitem id="contextmenu-enable" label="&addonAction.enable;" onclick="ContextMenus.enable();"></menuitem>
<menuitem id="contextmenu-disable" label="&addonAction.disable;" onclick="ContextMenus.disable();"></menuitem>
<menuitem id="contextmenu-uninstall" label="&addonAction.uninstall;" onclick="ContextMenus.uninstall();"></menuitem>
<menuitem id="contextmenu-default" label="&addonAction.setDefault;" onclick="ContextMenus.setDefaultSearch();"></menuitem>
</menu>
<div id="addons-header" class="header">
@ -84,7 +83,6 @@
<button id="disable-btn" class="show-on-enable hide-on-disable hide-on-uninstall" onclick="Addons.disable();">&addonAction.disable;</button>
<button id="uninstall-btn" class="hide-on-uninstall" onclick="Addons.uninstall();">&addonAction.uninstall;</button>
<button id="cancel-btn" class="show-on-uninstall" onclick="Addons.cancelUninstall();">&addonAction.undo;</button>
<button id="default-btn" class="show-on-enable hide-on-disable hide-on-uninstall" onclick="Addons.setDefaultSearch();">&addonAction.setDefault;</button>
</div>
<div class="options-header">&aboutAddons.options;</div>
<div class="options-box"></div>

View File

@ -1641,7 +1641,7 @@ var NativeWindow = {
type: "PageActions:Add",
id: id,
title: aOptions.title,
icon: resolveGeckoUri(aOptions.icon)
icon: resolveGeckoURI(aOptions.icon)
});
this._items[id] = {
clickCallback: aOptions.clickCallback,
@ -6421,6 +6421,9 @@ var SearchEngines = {
init: function init() {
Services.obs.addObserver(this, "SearchEngines:Get", false);
Services.obs.addObserver(this, "SearchEngines:GetVisible", false);
Services.obs.addObserver(this, "SearchEngines:SetDefault", false);
Services.obs.addObserver(this, "SearchEngines:Remove", false);
let contextName = Strings.browser.GetStringFromName("contextmenu.addSearchEngine");
let filter = {
matches: function (aElement) {
@ -6432,21 +6435,36 @@ var SearchEngines = {
uninit: function uninit() {
Services.obs.removeObserver(this, "SearchEngines:Get");
Services.obs.removeObserver(this, "SearchEngines:GetVisible");
Services.obs.removeObserver(this, "SearchEngines:SetDefault");
Services.obs.removeObserver(this, "SearchEngines:Remove");
if (this._contextMenuId != null)
NativeWindow.contextmenus.remove(this._contextMenuId);
},
_handleSearchEnginesGet: function _handleSearchEnginesGet(rv) {
// Fetch list of search engines. all ? All engines : Visible engines only.
_handleSearchEnginesGet: function _handleSearchEnginesGet(rv, all) {
if (!Components.isSuccessCode(rv)) {
Cu.reportError("Could not initialize search service, bailing out.");
return;
}
let engineData = Services.search.getVisibleEngines({});
let engineData;
if (all) {
engineData = Services.search.getEngines({});
} else {
engineData = Services.search.getVisibleEngines({});
}
// These engines are the bundled ones - they may not be uninstalled.
let immutableEngines = Services.search.getDefaultEngines();
let searchEngines = engineData.map(function (engine) {
return {
name: engine.name,
identifier: engine.identifier,
iconURI: (engine.iconURI ? engine.iconURI.spec : null)
iconURI: (engine.iconURI ? engine.iconURI.spec : null),
hidden: engine.hidden,
immutable: immutableEngines.indexOf(engine) != -1
};
});
@ -6458,6 +6476,8 @@ var SearchEngines = {
suggestTemplate = engine.getSubmission("__searchTerms__", "application/x-suggestions+json").uri.spec;
}
// By convention, the currently configured default engine is at position zero in searchEngines.
sendMessageToJava({
type: "SearchEngines:Data",
searchEngines: searchEngines,
@ -6470,9 +6490,45 @@ var SearchEngines = {
});
},
_handleSearchEnginesGetAll: function _handleSearchEnginesGetAll(rv) {
this._handleSearchEnginesGet(rv, true);
},
_handleSearchEnginesGetVisible: function _handleSearchEnginesGetVisible(rv) {
this._handleSearchEnginesGet(rv, false)
},
// Helper method to extract the engine name from a JSON. Simplifies the observe function.
_extractEngineFromJSON: function _extractEngineFromJSON(aData) {
let data = JSON.parse(aData);
return Services.search.getEngineByName(data.engine);
},
observe: function observe(aSubject, aTopic, aData) {
if (aTopic == "SearchEngines:Get") {
Services.search.init(this._handleSearchEnginesGet.bind(this));
let engine;
switch(aTopic) {
case "SearchEngines:GetVisible":
Services.search.init(this._handleSearchEnginesGetVisible.bind(this));
break;
case "SearchEngines:Get":
// Return a list of all engines, including "Hidden" ones.
Services.search.init(this._handleSearchEnginesGetAll.bind(this));
break;
case "SearchEngines:SetDefault":
engine = this._extractEngineFromJSON(aData);
// Move the new default search engine to the top of the search engine list.
Services.search.moveEngine(engine, 0);
Services.search.defaultEngine = engine;
break;
case "SearchEngines:Remove":
// Make sure the engine isn't hidden before removing it, to make sure it's
// visible if the user later re-adds it (works around bug 341833)
engine = this._extractEngineFromJSON(aData);
engine.hidden = false;
Services.search.removeEngine(engine);
break;
default:
dump("Unexpected message type observed: " + aTopic);
break;
}
},

View File

@ -10,4 +10,3 @@
<!ENTITY addonAction.disable "Disable">
<!ENTITY addonAction.uninstall "Uninstall">
<!ENTITY addonAction.undo "Undo">
<!ENTITY addonAction.setDefault "Set as Default">

View File

@ -2,12 +2,9 @@
# 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/.
addonsSearchEngine.description=Integrated Search
addonType.extension=Extension
addonType.theme=Theme
addonType.locale=Locale
addonType.search=Search
addonStatus.uninstalled=%S will be uninstalled after restart.