Merge fxteam to m-c
@ -575,7 +575,7 @@
|
||||
.variable-or-property[non-configurable] > tooltip > label[value=configurable],
|
||||
.variable-or-property[non-writable] > tooltip > label[value=writable],
|
||||
.variable-or-property[non-extensible] > tooltip > label[value=extensible] {
|
||||
color: #800;
|
||||
color: #f44;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
|
@ -132,6 +132,7 @@ FENNEC_JAVA_FILES = \
|
||||
prompts/Prompt.java \
|
||||
prompts/PromptInput.java \
|
||||
prompts/PromptService.java \
|
||||
prompts/IconGridInput.java \
|
||||
Restarter.java \
|
||||
sqlite/ByteBufferInputStream.java \
|
||||
sqlite/MatrixBlobCursor.java \
|
||||
@ -447,6 +448,8 @@ RES_LAYOUT = \
|
||||
res/layout/home_banner.xml \
|
||||
res/layout/home_suggestion_prompt.xml \
|
||||
res/layout/home_top_sites_page.xml \
|
||||
res/layout/icon_grid.xml \
|
||||
res/layout/icon_grid_item.xml \
|
||||
res/layout/web_app.xml \
|
||||
res/layout/launch_app_list.xml \
|
||||
res/layout/launch_app_listitem.xml \
|
||||
@ -613,6 +616,8 @@ RES_DRAWABLE_MDPI = \
|
||||
res/drawable-mdpi/bookmark_folder_closed.png \
|
||||
res/drawable-mdpi/bookmark_folder_opened.png \
|
||||
res/drawable-mdpi/desktop_notification.png \
|
||||
res/drawable-mdpi/grid_icon_bg_activated.9.png \
|
||||
res/drawable-mdpi/grid_icon_bg_focused.9.png \
|
||||
res/drawable-mdpi/home_tab_menu_strip.9.png \
|
||||
res/drawable-mdpi/ic_menu_addons_filler.png \
|
||||
res/drawable-mdpi/ic_menu_bookmark_add.png \
|
||||
@ -718,6 +723,8 @@ RES_DRAWABLE_HDPI = \
|
||||
res/drawable-hdpi/folder.png \
|
||||
res/drawable-hdpi/home_bg.png \
|
||||
res/drawable-hdpi/home_star.png \
|
||||
res/drawable-hdpi/grid_icon_bg_activated.9.png \
|
||||
res/drawable-hdpi/grid_icon_bg_focused.9.png \
|
||||
res/drawable-hdpi/abouthome_thumbnail.png \
|
||||
res/drawable-hdpi/alert_addon.png \
|
||||
res/drawable-hdpi/alert_app.png \
|
||||
@ -830,6 +837,8 @@ RES_DRAWABLE_XHDPI = \
|
||||
res/drawable-xhdpi/alert_mic_camera.png \
|
||||
res/drawable-xhdpi/arrow_popup_bg.9.png \
|
||||
res/drawable-xhdpi/home_tab_menu_strip.9.png \
|
||||
res/drawable-xhdpi/grid_icon_bg_activated.9.png \
|
||||
res/drawable-xhdpi/grid_icon_bg_focused.9.png \
|
||||
res/drawable-xhdpi/ic_menu_addons_filler.png \
|
||||
res/drawable-xhdpi/ic_menu_bookmark_add.png \
|
||||
res/drawable-xhdpi/ic_menu_bookmark_remove.png \
|
||||
@ -1079,6 +1088,7 @@ RES_DRAWABLE += \
|
||||
res/drawable/url_bar_bg.xml \
|
||||
res/drawable/url_bar_entry.xml \
|
||||
res/drawable/url_bar_nav_button.xml \
|
||||
res/drawable/icon_grid_item_bg.xml \
|
||||
res/drawable/url_bar_right_edge.xml \
|
||||
res/drawable/bookmark_folder.xml \
|
||||
res/drawable/divider_horizontal.xml \
|
||||
|
@ -21,12 +21,14 @@ import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
@ -125,6 +127,18 @@ public class LayerView extends FrameLayout {
|
||||
GeckoAccessibility.setDelegate(this);
|
||||
}
|
||||
|
||||
private Point getEventRadius(MotionEvent event) {
|
||||
if (Build.VERSION.SDK_INT >= 9) {
|
||||
return new Point((int)event.getToolMajor()/2,
|
||||
(int)event.getToolMinor()/2);
|
||||
}
|
||||
|
||||
float size = event.getSize();
|
||||
DisplayMetrics displaymetrics = getContext().getResources().getDisplayMetrics();
|
||||
size = size * Math.min(displaymetrics.heightPixels, displaymetrics.widthPixels);
|
||||
return new Point((int)size, (int)size);
|
||||
}
|
||||
|
||||
public void geckoConnected() {
|
||||
// See if we want to force 16-bit colour before doing anything
|
||||
PrefsHelper.getPref("gfx.android.rgb16.force", new PrefsHelper.PrefHandlerBase() {
|
||||
@ -157,8 +171,10 @@ public class LayerView extends FrameLayout {
|
||||
}
|
||||
|
||||
if (mInitialTouchPoint != null && action == MotionEvent.ACTION_MOVE) {
|
||||
Point p = getEventRadius(event);
|
||||
|
||||
if (PointUtils.subtract(point, mInitialTouchPoint).length() <
|
||||
PanZoomController.PAN_THRESHOLD) {
|
||||
Math.max(PanZoomController.CLICK_THRESHOLD, Math.min(Math.min(p.x, p.y), PanZoomController.PAN_THRESHOLD))) {
|
||||
// Don't send the touchmove event if if the users finger hasn't moved far.
|
||||
// Necessary for Google Maps to work correctly. See bug 771099.
|
||||
return true;
|
||||
|
@ -18,6 +18,9 @@ public interface PanZoomController {
|
||||
// between the touch-down and touch-up of a click). In units of density-independent pixels.
|
||||
public static final float PAN_THRESHOLD = 1/16f * GeckoAppShell.getDpi();
|
||||
|
||||
// Threshold for sending touch move events to content
|
||||
public static final float CLICK_THRESHOLD = 1/50f * GeckoAppShell.getDpi();
|
||||
|
||||
static class Factory {
|
||||
static PanZoomController create(PanZoomTarget target, View view, EventDispatcher dispatcher) {
|
||||
return new JavaPanZoomController(target, view, dispatcher);
|
||||
|
172
mobile/android/base/prompts/IconGridInput.java
Normal file
@ -0,0 +1,172 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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.prompts;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Display;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.GridView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class IconGridInput extends PromptInput implements OnItemClickListener {
|
||||
public static final String INPUT_TYPE = "icongrid";
|
||||
public static final String LOGTAG = "GeckoIconGridInput";
|
||||
|
||||
private ArrayAdapter<IconGridItem> mAdapter; // An adapter holding a list of items to show in the grid
|
||||
|
||||
private static int mColumnWidth = -1; // The maximum width of columns
|
||||
private static int mMaxColumns = -1; // The maximum number of columns to show
|
||||
private static int mIconSize = -1; // Size of icons in the grid
|
||||
private int mSelected = -1; // Current selection
|
||||
private JSONArray mArray;
|
||||
|
||||
public IconGridInput(JSONObject obj) {
|
||||
super(obj);
|
||||
mArray = obj.optJSONArray("items");
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(Context context) throws UnsupportedOperationException {
|
||||
if (mColumnWidth < 0) {
|
||||
// getColumnWidth isn't available on pre-ICS, so we pull it out and assign it here
|
||||
mColumnWidth = context.getResources().getDimensionPixelSize(R.dimen.icongrid_columnwidth);
|
||||
}
|
||||
|
||||
if (mIconSize < 0) {
|
||||
mIconSize = GeckoAppShell.getPreferredIconSize();
|
||||
}
|
||||
|
||||
if (mMaxColumns < 0) {
|
||||
mMaxColumns = context.getResources().getInteger(R.integer.max_icon_grid_columns);
|
||||
}
|
||||
|
||||
// TODO: Dynamically handle size changes
|
||||
final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
final Display display = wm.getDefaultDisplay();
|
||||
final int screenWidth = display.getWidth();
|
||||
int maxColumns = Math.min(mMaxColumns, screenWidth / mColumnWidth);
|
||||
|
||||
final GridView view = (GridView) LayoutInflater.from(context).inflate(R.layout.icon_grid, null, false);
|
||||
view.setColumnWidth(mColumnWidth);
|
||||
|
||||
final ArrayList<IconGridItem> items = new ArrayList<IconGridItem>(mArray.length());
|
||||
for (int i = 0; i < mArray.length(); i++) {
|
||||
IconGridItem item = new IconGridItem(context, mArray.optJSONObject(i));
|
||||
items.add(item);
|
||||
if (item.selected) {
|
||||
mSelected = i;
|
||||
view.setSelection(i);
|
||||
}
|
||||
}
|
||||
|
||||
view.setNumColumns(Math.min(items.size(), maxColumns));
|
||||
view.setOnItemClickListener(this);
|
||||
|
||||
mAdapter = new IconGridAdapter(context, -1, items);
|
||||
view.setAdapter(mAdapter);
|
||||
mView = view;
|
||||
return mView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
mSelected = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return Integer.toString(mSelected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getScrollable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private class IconGridAdapter extends ArrayAdapter<IconGridItem> {
|
||||
public IconGridAdapter(Context context, int resource, List<IconGridItem> items) {
|
||||
super(context, resource, items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convert, ViewGroup parent) {
|
||||
final Context context = parent.getContext();
|
||||
if (convert == null) {
|
||||
convert = LayoutInflater.from(context).inflate(R.layout.icon_grid_item, parent, false);
|
||||
}
|
||||
bindView(convert, context, position);
|
||||
return convert;
|
||||
}
|
||||
|
||||
private void bindView(View v, Context c, int position) {
|
||||
final IconGridItem item = getItem(position);
|
||||
final TextView text1 = (TextView) v.findViewById(android.R.id.text1);
|
||||
text1.setText(item.label);
|
||||
|
||||
final TextView text2 = (TextView) v.findViewById(android.R.id.text2);
|
||||
if (TextUtils.isEmpty(item.description)) {
|
||||
text2.setVisibility(View.GONE);
|
||||
} else {
|
||||
text2.setVisibility(View.VISIBLE);
|
||||
text2.setText(item.description);
|
||||
}
|
||||
|
||||
final ImageView icon = (ImageView) v.findViewById(R.id.icon);
|
||||
icon.setImageDrawable(item.icon);
|
||||
ViewGroup.LayoutParams lp = icon.getLayoutParams();
|
||||
lp.width = lp.height = mIconSize;
|
||||
}
|
||||
}
|
||||
|
||||
private class IconGridItem {
|
||||
final String label;
|
||||
final String description;
|
||||
final boolean selected;
|
||||
Drawable icon;
|
||||
|
||||
public IconGridItem(final Context context, final JSONObject obj) {
|
||||
label = obj.optString("name");
|
||||
final String iconUrl = obj.optString("iconUri");
|
||||
description = obj.optString("description");
|
||||
selected = obj.optBoolean("selected");
|
||||
|
||||
BitmapUtils.getDrawable(context, iconUrl, new BitmapUtils.BitmapLoader() {
|
||||
public void onBitmapFound(Drawable d) {
|
||||
icon = d;
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -35,6 +35,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
@ -116,89 +117,30 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
|
||||
processMessage(message);
|
||||
}
|
||||
|
||||
public void show(String aTitle, String aText, PromptListItem[] aMenuList, boolean aMultipleSelection) {
|
||||
public void show(String title, String text, PromptListItem[] listItems, boolean multipleSelection) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
|
||||
GeckoAppShell.getLayerView().abortPanning();
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
|
||||
if (!TextUtils.isEmpty(aTitle)) {
|
||||
builder.setTitle(aTitle);
|
||||
if (!TextUtils.isEmpty(title)) {
|
||||
builder.setTitle(title);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(aText)) {
|
||||
builder.setMessage(aText);
|
||||
if (!TextUtils.isEmpty(text)) {
|
||||
builder.setMessage(text);
|
||||
}
|
||||
|
||||
int length = mInputs == null ? 0 : mInputs.length;
|
||||
if (aMenuList != null && aMenuList.length > 0) {
|
||||
int resourceId = android.R.layout.simple_list_item_1;
|
||||
if (mSelected != null && mSelected.length > 0) {
|
||||
if (aMultipleSelection) {
|
||||
resourceId = R.layout.select_dialog_multichoice;
|
||||
} else {
|
||||
resourceId = R.layout.select_dialog_singlechoice;
|
||||
}
|
||||
}
|
||||
PromptListAdapter adapter = new PromptListAdapter(mContext, resourceId, aMenuList);
|
||||
if (mSelected != null && mSelected.length > 0) {
|
||||
if (aMultipleSelection) {
|
||||
adapter.listView = (ListView) mInflater.inflate(R.layout.select_dialog_list, null);
|
||||
adapter.listView.setOnItemClickListener(this);
|
||||
builder.setInverseBackgroundForced(true);
|
||||
adapter.listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||
adapter.listView.setAdapter(adapter);
|
||||
builder.setView(adapter.listView);
|
||||
} else {
|
||||
int selectedIndex = -1;
|
||||
for (int i = 0; i < mSelected.length; i++) {
|
||||
if (mSelected[i]) {
|
||||
selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mSelected = null;
|
||||
builder.setSingleChoiceItems(adapter, selectedIndex, this);
|
||||
}
|
||||
} else {
|
||||
builder.setAdapter(adapter, this);
|
||||
mSelected = null;
|
||||
}
|
||||
} else if (length == 1) {
|
||||
try {
|
||||
ScrollView view = new ScrollView(mContext);
|
||||
view.addView(mInputs[0].getView(mContext));
|
||||
builder.setView(applyInputStyle(view));
|
||||
} catch(UnsupportedOperationException ex) {
|
||||
// We cannot display these input widgets with this sdk version,
|
||||
// do not display any dialog and finish the prompt now.
|
||||
try {
|
||||
finishDialog(new JSONObject("{\"button\": -1}"));
|
||||
} catch(JSONException e) { }
|
||||
// Because lists are currently added through the normal Android AlertBuilder interface, they're
|
||||
// incompatible with also adding additional input elements to a dialog.
|
||||
if (listItems != null && listItems.length > 0) {
|
||||
addlistItems(builder, listItems, multipleSelection);
|
||||
} else if (!addInputs(builder)) {
|
||||
// If we failed to add any requested input elements, don't show the dialog
|
||||
return;
|
||||
}
|
||||
} else if (length > 1) {
|
||||
try {
|
||||
LinearLayout linearLayout = new LinearLayout(mContext);
|
||||
linearLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
for (int i = 0; i < length; i++) {
|
||||
View content = mInputs[i].getView(mContext);
|
||||
linearLayout.addView(content);
|
||||
}
|
||||
ScrollView view = new ScrollView(mContext);
|
||||
view.addView(linearLayout);
|
||||
builder.setView(applyInputStyle(view));
|
||||
} catch(UnsupportedOperationException ex) {
|
||||
// We cannot display these input widgets with this sdk version,
|
||||
// do not display any dialog and finish the prompt now.
|
||||
try {
|
||||
finishDialog(new JSONObject("{\"button\": -1}"));
|
||||
} catch(JSONException e) { }
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
length = mButtons == null ? 0 : mButtons.length;
|
||||
int length = mButtons == null ? 0 : mButtons.length;
|
||||
if (length > 0) {
|
||||
builder.setPositiveButton(mButtons[0], this);
|
||||
if (length > 1) {
|
||||
@ -222,64 +164,242 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
|
||||
mInputs = inputs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface aDialog, int aWhich) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
JSONObject ret = new JSONObject();
|
||||
/* Adds to a result value from the lists that can be shown in dialogs.
|
||||
* Will set the selected value(s) to the button attribute of the
|
||||
* object that's passed in. If this is a multi-select dialog, can set
|
||||
* the button attribute to an array.
|
||||
*/
|
||||
private void addListResult(final JSONObject result, int which) {
|
||||
try {
|
||||
int button = -1;
|
||||
ListView list = mDialog.getListView();
|
||||
if (list != null || mSelected != null) {
|
||||
button = aWhich;
|
||||
if (mSelected != null) {
|
||||
JSONArray selected = new JSONArray();
|
||||
for (int i = 0; i < mSelected.length; i++) {
|
||||
selected.put(mSelected[i]);
|
||||
}
|
||||
ret.put("button", selected);
|
||||
result.put("button", selected);
|
||||
} else {
|
||||
ret.put("button", button);
|
||||
result.put("button", which);
|
||||
}
|
||||
} else {
|
||||
switch(aWhich) {
|
||||
} catch(JSONException ex) { }
|
||||
}
|
||||
|
||||
/* Adds to a result value from the inputs that can be shown in dialogs.
|
||||
* Each input will set its own value in the result.
|
||||
*/
|
||||
private void addInputValues(final JSONObject result) {
|
||||
try {
|
||||
if (mInputs != null) {
|
||||
for (int i = 0; i < mInputs.length; i++) {
|
||||
result.put(mInputs[i].getId(), mInputs[i].getValue());
|
||||
}
|
||||
}
|
||||
} catch(JSONException ex) { }
|
||||
}
|
||||
|
||||
/* Adds the selected button to a result. This should only be called if there
|
||||
* are no lists shown on the dialog, since they also write their results to the button
|
||||
* attribute.
|
||||
*/
|
||||
private void addButtonResult(final JSONObject result, int which) {
|
||||
int button = -1;
|
||||
switch(which) {
|
||||
case DialogInterface.BUTTON_POSITIVE : button = 0; break;
|
||||
case DialogInterface.BUTTON_NEUTRAL : button = 1; break;
|
||||
case DialogInterface.BUTTON_NEGATIVE : button = 2; break;
|
||||
}
|
||||
ret.put("button", button);
|
||||
}
|
||||
if (mInputs != null) {
|
||||
for (int i = 0; i < mInputs.length; i++) {
|
||||
ret.put(mInputs[i].getId(), mInputs[i].getValue());
|
||||
try {
|
||||
result.put("button", button);
|
||||
} catch(JSONException ex) { }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
JSONObject ret = new JSONObject();
|
||||
try {
|
||||
ListView list = mDialog.getListView();
|
||||
if (list != null || mSelected != null) {
|
||||
addListResult(ret, which);
|
||||
} else {
|
||||
addButtonResult(ret, which);
|
||||
}
|
||||
addInputValues(ret);
|
||||
} catch(Exception ex) {
|
||||
Log.i(LOGTAG, "Error building return: " + ex);
|
||||
}
|
||||
|
||||
if (mDialog != null) {
|
||||
mDialog.dismiss();
|
||||
if (dialog != null) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
finishDialog(ret);
|
||||
}
|
||||
|
||||
/* Adds a set of list items to the prompt. This can be used for either context menu type dialogs, checked lists,
|
||||
* or multiple selection lists. If mSelected is set in the prompt before addlistItems is called, the items will be
|
||||
* shown with "checkmarks" on their left side.
|
||||
*
|
||||
* @param builder
|
||||
* The alert builder currently building this dialog.
|
||||
* @param listItems
|
||||
* The items to add.
|
||||
* @param multipleSelection
|
||||
* If true, and mSelected is defined to be a non-zero-length list, the list will show checkmarks on the
|
||||
* left and allow multiple selection.
|
||||
*/
|
||||
private void addlistItems(AlertDialog.Builder builder, PromptListItem[] listItems, boolean multipleSelection) {
|
||||
if (mSelected != null && mSelected.length > 0) {
|
||||
if (multipleSelection) {
|
||||
addMultiSelectList(builder, listItems);
|
||||
} else {
|
||||
addSingleSelectList(builder, listItems);
|
||||
}
|
||||
} else {
|
||||
addMenuList(builder, listItems);
|
||||
}
|
||||
}
|
||||
|
||||
/* Shows a multi-select list with checkmarks on the side. Android doesn't support using an adapter for
|
||||
* multi-choice lists by default so instead we insert our own custom list so that we can do fancy things
|
||||
* to the rows like disabling/indenting them.
|
||||
*
|
||||
* @param builder
|
||||
* The alert builder currently building this dialog.
|
||||
* @param listItems
|
||||
* The items to add.
|
||||
*/
|
||||
private void addMultiSelectList(AlertDialog.Builder builder, PromptListItem[] listItems) {
|
||||
PromptListAdapter adapter = new PromptListAdapter(mContext, R.layout.select_dialog_multichoice, listItems);
|
||||
adapter.listView = (ListView) mInflater.inflate(R.layout.select_dialog_list, null);
|
||||
adapter.listView.setOnItemClickListener(this);
|
||||
builder.setInverseBackgroundForced(true);
|
||||
adapter.listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||
adapter.listView.setAdapter(adapter);
|
||||
builder.setView(adapter.listView);
|
||||
}
|
||||
|
||||
/* Shows a single-select list with radio boxes on the side.
|
||||
*
|
||||
* @param builder
|
||||
* the alert builder currently building this dialog.
|
||||
* @param listItems
|
||||
* The items to add.
|
||||
*/
|
||||
private void addSingleSelectList(AlertDialog.Builder builder, PromptListItem[] listItems) {
|
||||
PromptListAdapter adapter = new PromptListAdapter(mContext, R.layout.select_dialog_singlechoice, listItems);
|
||||
// For single select, we only maintain a single index of the selected row
|
||||
int selectedIndex = -1;
|
||||
for (int i = 0; i < mSelected.length; i++) {
|
||||
if (mSelected[i]) {
|
||||
selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mSelected = null;
|
||||
|
||||
builder.setSingleChoiceItems(adapter, selectedIndex, this);
|
||||
}
|
||||
|
||||
/* Shows a single-select list.
|
||||
*
|
||||
* @param builder
|
||||
* the alert builder currently building this dialog.
|
||||
* @param listItems
|
||||
* The items to add.
|
||||
*/
|
||||
private void addMenuList(AlertDialog.Builder builder, PromptListItem[] listItems) {
|
||||
PromptListAdapter adapter = new PromptListAdapter(mContext, android.R.layout.simple_list_item_1, listItems);
|
||||
builder.setAdapter(adapter, this);
|
||||
mSelected = null;
|
||||
}
|
||||
|
||||
/* Add the requested input elements to the dialog.
|
||||
*
|
||||
* @param builder
|
||||
* the alert builder currently building this dialog.
|
||||
* @return
|
||||
* return true if the inputs were added successfully. This may fail
|
||||
* if the requested input is compatible with this Android verison
|
||||
*/
|
||||
private boolean addInputs(AlertDialog.Builder builder) {
|
||||
int length = mInputs == null ? 0 : mInputs.length;
|
||||
if (length == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
View root = null;
|
||||
boolean scrollable = false; // If any of the innuts are scrollable, we won't wrap this in a ScrollView
|
||||
|
||||
if (length == 1) {
|
||||
root = mInputs[0].getView(mContext);
|
||||
scrollable |= mInputs[0].getScrollable();
|
||||
} else if (length > 1) {
|
||||
LinearLayout linearLayout = new LinearLayout(mContext);
|
||||
linearLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
for (int i = 0; i < length; i++) {
|
||||
View content = mInputs[i].getView(mContext);
|
||||
linearLayout.addView(content);
|
||||
scrollable |= mInputs[i].getScrollable();
|
||||
}
|
||||
root = linearLayout;
|
||||
}
|
||||
|
||||
if (scrollable) {
|
||||
builder.setView(applyInputStyle(root));
|
||||
} else {
|
||||
ScrollView view = new ScrollView(mContext);
|
||||
view.addView(root);
|
||||
builder.setView(applyInputStyle(view));
|
||||
}
|
||||
} catch(Exception ex) {
|
||||
Log.e(LOGTAG, "Error showing prompt inputs", ex);
|
||||
// We cannot display these input widgets with this sdk version,
|
||||
// do not display any dialog and finish the prompt now.
|
||||
cancelDialog();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* AdapterView.OnItemClickListener
|
||||
* Called when a list item is clicked
|
||||
*/
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
mSelected[position] = !mSelected[position];
|
||||
}
|
||||
|
||||
/* @DialogInterface.OnCancelListener
|
||||
* Called when the user hits back to cancel a dialog. The dialog will close itself when this
|
||||
* ends. Setup the correct return values here.
|
||||
*
|
||||
* @param aDialog
|
||||
* A dialog interface for the dialog that's being closed.
|
||||
*/
|
||||
@Override
|
||||
public void onCancel(DialogInterface aDialog) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
cancelDialog();
|
||||
}
|
||||
|
||||
/* Called in situations where we want to cancel the dialog . This can happen if the user hits back,
|
||||
* or if the dialog can't be created because of invalid JSON.
|
||||
*/
|
||||
private void cancelDialog() {
|
||||
JSONObject ret = new JSONObject();
|
||||
try {
|
||||
ret.put("button", -1);
|
||||
} catch(Exception ex) { }
|
||||
addInputValues(ret);
|
||||
finishDialog(ret);
|
||||
}
|
||||
|
||||
/* Called any time we're closing the dialog to cleanup and notify listeners that the dialog
|
||||
* is closing.
|
||||
*/
|
||||
public void finishDialog(JSONObject aReturn) {
|
||||
mInputs = null;
|
||||
mButtons = null;
|
||||
@ -302,10 +422,12 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
|
||||
mGuid = null;
|
||||
}
|
||||
|
||||
/* Handles parsing the initial JSON sent to show dialogs
|
||||
*/
|
||||
private void processMessage(JSONObject geckoObject) {
|
||||
String title = getSafeString(geckoObject, "title");
|
||||
String text = getSafeString(geckoObject, "text");
|
||||
mGuid = getSafeString(geckoObject, "guid");
|
||||
String title = geckoObject.optString("title");
|
||||
String text = geckoObject.optString("text");
|
||||
mGuid = geckoObject.optString("guid");
|
||||
|
||||
mButtons = getStringArray(geckoObject, "buttons");
|
||||
|
||||
@ -323,14 +445,6 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
|
||||
show(title, text, menuitems, multiple);
|
||||
}
|
||||
|
||||
private static String getSafeString(JSONObject json, String key) {
|
||||
try {
|
||||
return json.getString(key);
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static JSONArray getSafeArray(JSONObject json, String key) {
|
||||
try {
|
||||
return json.getJSONArray(key);
|
||||
@ -339,22 +453,6 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean getSafeBool(JSONObject json, String key) {
|
||||
try {
|
||||
return json.getBoolean(key);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getSafeInt(JSONObject json, String key ) {
|
||||
try {
|
||||
return json.getInt(key);
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] getStringArray(JSONObject aObject, String aName) {
|
||||
JSONArray items = getSafeArray(aObject, aName);
|
||||
int length = items.length();
|
||||
@ -406,12 +504,12 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
|
||||
public Drawable icon;
|
||||
|
||||
PromptListItem(JSONObject aObject) {
|
||||
label = getSafeString(aObject, "label");
|
||||
isGroup = getSafeBool(aObject, "isGroup");
|
||||
inGroup = getSafeBool(aObject, "inGroup");
|
||||
disabled = getSafeBool(aObject, "disabled");
|
||||
id = getSafeInt(aObject, "id");
|
||||
isParent = getSafeBool(aObject, "isParent");
|
||||
label = aObject.optString("label");
|
||||
isGroup = aObject.optBoolean("isGroup");
|
||||
inGroup = aObject.optBoolean("inGroup");
|
||||
disabled = aObject.optBoolean("disabled");
|
||||
id = aObject.optInt("id");
|
||||
isParent = aObject.optBoolean("isParent");
|
||||
}
|
||||
|
||||
public PromptListItem(String aLabel) {
|
||||
|
@ -42,8 +42,6 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class PromptInput {
|
||||
private final JSONObject mJSONInput;
|
||||
|
||||
protected final String mLabel;
|
||||
protected final String mType;
|
||||
protected final String mId;
|
||||
@ -329,7 +327,6 @@ public class PromptInput {
|
||||
}
|
||||
|
||||
public PromptInput(JSONObject obj) {
|
||||
mJSONInput = obj;
|
||||
mLabel = obj.optString("label");
|
||||
mType = obj.optString("type");
|
||||
String id = obj.optString("id");
|
||||
@ -351,6 +348,8 @@ public class PromptInput {
|
||||
return new MenulistInput(obj);
|
||||
} else if (LabelInput.INPUT_TYPE.equals(type)) {
|
||||
return new LabelInput(obj);
|
||||
} else if (IconGridInput.INPUT_TYPE.equals(type)) {
|
||||
return new IconGridInput(obj);
|
||||
} else {
|
||||
for (String dtType : DateTimeInput.INPUT_TYPES) {
|
||||
if (dtType.equals(type)) {
|
||||
@ -372,4 +371,8 @@ public class PromptInput {
|
||||
public String getValue() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public boolean getScrollable() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 326 B |
After Width: | Height: | Size: 326 B |
After Width: | Height: | Size: 263 B |
After Width: | Height: | Size: 284 B |
After Width: | Height: | Size: 242 B |
After Width: | Height: | Size: 413 B |
After Width: | Height: | Size: 324 B |
26
mobile/android/base/resources/drawable/icon_grid_item_bg.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2012 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_focused="true"
|
||||
android:state_pressed="true"
|
||||
android:drawable="@drawable/grid_icon_bg_focused" />
|
||||
<item android:state_activated="true"
|
||||
android:drawable="@drawable/grid_icon_bg_activated" />
|
||||
<item android:drawable="@android:color/transparent" />
|
||||
|
||||
</selector>
|
10
mobile/android/base/resources/layout/icon_grid.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?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/. -->
|
||||
|
||||
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:choiceMode="singleChoice"
|
||||
android:padding="@dimen/icongrid_padding"/>
|
55
mobile/android/base/resources/layout/icon_grid_item.xml
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
//device/apps/common/res/any/layout/resolve_list_item.xml
|
||||
Copyright 2006, The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:background="@drawable/icon_grid_item_bg"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Extended activity info to distinguish between duplicate activity names -->
|
||||
<TextView android:id="@android:id/text2"
|
||||
android:textAppearance="?android:attr/textAppearance"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:minLines="2"
|
||||
android:maxLines="2"
|
||||
android:paddingTop="4dip"
|
||||
android:paddingBottom="4dip" />
|
||||
|
||||
<!-- Activity icon when presenting dialog
|
||||
Size will be filled in by ResolverActivity -->
|
||||
<ImageView android:id="@+id/icon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<!-- Activity name -->
|
||||
<TextView android:id="@android:id/text1"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:minLines="2"
|
||||
android:maxLines="2"
|
||||
android:paddingTop="4dip"
|
||||
android:paddingBottom="4dip" />
|
||||
</LinearLayout>
|
||||
|
@ -96,4 +96,8 @@
|
||||
|
||||
<!-- Banner -->
|
||||
<dimen name="home_banner_height">72dp</dimen>
|
||||
|
||||
<!-- Icon Grid -->
|
||||
<dimen name="icongrid_columnwidth">128dp</dimen>
|
||||
<dimen name="icongrid_padding">16dp</dimen>
|
||||
</resources>
|
||||
|
@ -7,5 +7,6 @@
|
||||
|
||||
<integer name="number_of_top_sites">6</integer>
|
||||
<integer name="number_of_top_sites_cols">2</integer>
|
||||
<integer name="max_icon_grid_columns">4</integer>
|
||||
|
||||
</resources>
|
||||
|
@ -20,6 +20,7 @@
|
||||
[testMailToContextMenu]
|
||||
[testPictureLinkContextMenu]
|
||||
[testPasswordProvider]
|
||||
[testPromptGridInput]
|
||||
# [testPasswordEncrypt] # see bug 824067
|
||||
[testFormHistory]
|
||||
[testBrowserProvider]
|
||||
|
@ -0,0 +1,51 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>IconGrid test page</title>
|
||||
<meta name="viewport" content="initial-scale=1.0"/>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="application/javascript">
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/Prompt.jsm");
|
||||
|
||||
function start() {
|
||||
var test = location.hash.substring(1);
|
||||
window[test]();
|
||||
}
|
||||
|
||||
function test1() {
|
||||
var p = new Prompt({
|
||||
title: "Prompt 1",
|
||||
buttons: [
|
||||
"OK"
|
||||
],
|
||||
}).addIconGrid({
|
||||
items: [
|
||||
{ iconUri: "drawable://alert_app", name: "Icon 1", selected: true },
|
||||
{ iconUri: "drawable://alert_download", name: "Icon 2" },
|
||||
{ iconUri: "drawable://alert_addon", name: "Icon 3" },
|
||||
{ iconUri: "drawable://alert_addon", name: "Icon 4" },
|
||||
{ iconUri: "drawable://alert_addon", name: "Icon 5" },
|
||||
{ iconUri: "drawable://alert_addon", name: "Icon 6" },
|
||||
{ iconUri: "drawable://alert_addon", name: "Icon 7" },
|
||||
{ iconUri: "drawable://alert_addon", name: "Icon 8" },
|
||||
{ iconUri: "drawable://alert_addon", name: "Icon 9" },
|
||||
{ iconUri: "drawable://alert_addon", name: "Icon 10" },
|
||||
{ iconUri: "drawable://alert_addon", name: "Icon 11" },
|
||||
]
|
||||
});
|
||||
p.show(function(data) {
|
||||
sendResult(data.icongrid0 == 10, "Got result " + data.icongrid0);
|
||||
});
|
||||
}
|
||||
|
||||
function sendResult(pass, message) {
|
||||
setTimeout(function() {
|
||||
alert((pass ? "PASS " : "FAIL ") + message);
|
||||
}, 1000);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="start();">
|
||||
</body>
|
||||
</html>
|
61
mobile/android/base/tests/testPromptGridInput.java.in
Normal file
@ -0,0 +1,61 @@
|
||||
#filter substitution
|
||||
package @ANDROID_PACKAGE_NAME@.tests;
|
||||
|
||||
import @ANDROID_PACKAGE_NAME@.*;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.widget.EditText;
|
||||
import android.widget.CheckedTextView;
|
||||
import android.widget.TextView;
|
||||
import android.text.InputType;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class testPromptGridInput extends BaseTest {
|
||||
@Override
|
||||
protected int getTestType() {
|
||||
return TEST_MOCHITEST;
|
||||
}
|
||||
|
||||
protected int index = 1;
|
||||
public void testPromptGridInput() {
|
||||
blockForGeckoReady();
|
||||
|
||||
test(1);
|
||||
|
||||
testGridItem("Icon 1");
|
||||
testGridItem("Icon 2");
|
||||
testGridItem("Icon 3");
|
||||
testGridItem("Icon 4");
|
||||
testGridItem("Icon 5");
|
||||
testGridItem("Icon 6");
|
||||
testGridItem("Icon 7");
|
||||
testGridItem("Icon 8");
|
||||
testGridItem("Icon 9");
|
||||
testGridItem("Icon 10");
|
||||
testGridItem("Icon 11");
|
||||
|
||||
mSolo.clickOnText("Icon 11");
|
||||
mSolo.clickOnText("OK");
|
||||
|
||||
mAsserter.ok(waitForText("PASS"), "test passed", "PASS");
|
||||
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
|
||||
}
|
||||
|
||||
public void testGridItem(String title) {
|
||||
// Force the list to scroll if necessary
|
||||
mSolo.waitForText(title, 1, 500, true);
|
||||
mAsserter.ok(waitForText(title), "Found grid item", title);
|
||||
}
|
||||
|
||||
public void test(final int num) {
|
||||
// Load about:blank between each test to ensure we reset state
|
||||
loadUrl("about:blank");
|
||||
mAsserter.ok(waitForText("about:blank"), "Loaded blank page", "page title match");
|
||||
|
||||
loadUrl("chrome://roboextender/content/robocop_prompt_gridinput.html#test" + num);
|
||||
}
|
||||
}
|
@ -224,7 +224,7 @@ InternalPrompt.prototype = {
|
||||
alertCheck: function alertCheck(aTitle, aText, aCheckMsg, aCheckState) {
|
||||
let p = this._getPrompt(aTitle, aText, [ PromptUtils.getLocaleString("OK") ], aCheckMsg, aCheckState);
|
||||
let data = this.showPrompt(p);
|
||||
if (aCheckState)
|
||||
if (aCheckState && data.button > -1)
|
||||
aCheckState.value = data.checkbox0 == "true";
|
||||
},
|
||||
|
||||
@ -238,7 +238,7 @@ InternalPrompt.prototype = {
|
||||
let p = this._getPrompt(aTitle, aText, null, aCheckMsg, aCheckState);
|
||||
let data = this.showPrompt(p);
|
||||
let ok = data.button == 0;
|
||||
if (aCheckState)
|
||||
if (aCheckState && data.button > -1)
|
||||
aCheckState.value = data.checkbox0 == "true";
|
||||
return ok;
|
||||
},
|
||||
@ -284,7 +284,7 @@ InternalPrompt.prototype = {
|
||||
|
||||
let p = this._getPrompt(aTitle, aText, buttons, aCheckMsg, aCheckState);
|
||||
let data = this.showPrompt(p);
|
||||
if (aCheckState)
|
||||
if (aCheckState && data.button > -1)
|
||||
aCheckState.value = data.checkbox0 == "true";
|
||||
return data.button;
|
||||
},
|
||||
@ -298,7 +298,7 @@ InternalPrompt.prototype = {
|
||||
let data = this.showPrompt(p);
|
||||
|
||||
let ok = data.button == 0;
|
||||
if (aCheckState)
|
||||
if (aCheckState && data.button > -1)
|
||||
aCheckState.value = data.checkbox0 == "true";
|
||||
if (ok)
|
||||
aValue.value = data.textbox0;
|
||||
@ -316,7 +316,7 @@ InternalPrompt.prototype = {
|
||||
let data = this.showPrompt(p);
|
||||
|
||||
let ok = data.button == 0;
|
||||
if (aCheckState)
|
||||
if (aCheckState && data.button > -1)
|
||||
aCheckState.value = data.checkbox0 == "true";
|
||||
if (ok)
|
||||
aPassword.value = data.password0;
|
||||
@ -337,7 +337,7 @@ InternalPrompt.prototype = {
|
||||
let data = this.showPrompt(p);
|
||||
|
||||
let ok = data.button == 0;
|
||||
if (aCheckState)
|
||||
if (aCheckState && data.button > -1)
|
||||
aCheckState.value = data.checkbox0 == "true";
|
||||
if (ok) {
|
||||
aUsername.value = data.textbox0;
|
||||
|
@ -122,6 +122,14 @@ Prompt.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
addIconGrid: function(aOptions) {
|
||||
return this._addInput({
|
||||
type: "icongrid",
|
||||
items: aOptions.items,
|
||||
id: aOptions.id
|
||||
});
|
||||
},
|
||||
|
||||
show: function(callback) {
|
||||
this.callback = callback;
|
||||
log("Sending message");
|
||||
|
@ -61,8 +61,8 @@
|
||||
#define END_RESULT_BATCH_AND_REFRESH_CONTENTS() \
|
||||
PR_BEGIN_MACRO \
|
||||
nsNavHistoryResult* result = GetResult(); \
|
||||
NS_ENSURE_STATE(result); \
|
||||
if (result->mBatchInProgress) { \
|
||||
NS_WARN_IF_FALSE(result, "Working with a non-live-updating Places container"); \
|
||||
if (result && result->mBatchInProgress) { \
|
||||
result->EndBatch(); \
|
||||
} \
|
||||
PR_END_MACRO
|
||||
|