mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge fx-team to m-c.
This commit is contained in:
commit
8fbbb10186
@ -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");
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)));
|
||||
});
|
@ -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
|
||||
|
@ -73,10 +73,5 @@
|
||||
</body>
|
||||
</method>
|
||||
</implementation>
|
||||
|
||||
<handlers>
|
||||
<!-- Work around for bug 835175 -->
|
||||
<handler event="click">false;</handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
</bindings>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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", { });
|
||||
},
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 \
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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 & data">
|
||||
<!ENTITY datareporting_notification_action_long "Choose what information to share">
|
||||
|
229
mobile/android/base/preferences/SearchEnginePreference.java
Normal file
229
mobile/android/base/preferences/SearchEnginePreference.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
BIN
mobile/android/base/resources/drawable-hdpi/tip_addsearch.png
Normal file
BIN
mobile/android/base/resources/drawable-hdpi/tip_addsearch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
BIN
mobile/android/base/resources/drawable-mdpi/tip_addsearch.png
Normal file
BIN
mobile/android/base/resources/drawable-mdpi/tip_addsearch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
mobile/android/base/resources/drawable-xhdpi/tip_addsearch.png
Normal file
BIN
mobile/android/base/resources/drawable-xhdpi/tip_addsearch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
},
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -10,4 +10,3 @@
|
||||
<!ENTITY addonAction.disable "Disable">
|
||||
<!ENTITY addonAction.uninstall "Uninstall">
|
||||
<!ENTITY addonAction.undo "Undo">
|
||||
<!ENTITY addonAction.setDefault "Set as Default">
|
||||
|
@ -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.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user