Bug 1398409 - 2. Use event callback to communicate FormAssistPopup actions; r=sebastian

Use event callbacks instead of separate events to deliver
FormAssistPopup replies back to FormAssistant. This lets us better
handle having multiple FormAssistPopup instances across Fennec, custom
tabs, and PWAs.

FormAssistant._currentInputElement is removed because it does not allow
us to have multiple concurrent popups. Instead, we track the current
element through the event callback closure.
FormAssistant._currentInputValue is also removed for similar reasons,
and I don't think it was really necessary.

MozReview-Commit-ID: DdeMBGCxDou
This commit is contained in:
Jim Chen 2017-09-14 17:50:57 -04:00
Родитель 3488170d4d
Коммит bee5ada2ca
6 изменённых файлов: 62 добавлений и 82 удалений

Просмотреть файл

@ -1640,14 +1640,6 @@ public class BrowserApp extends GeckoApp
deleteTempFiles(getApplicationContext());
if (mDoorHangerPopup != null) {
mDoorHangerPopup.destroy();
mDoorHangerPopup = null;
}
if (mFormAssistPopup != null)
mFormAssistPopup.destroy();
if (mTextSelection != null)
mTextSelection.destroy();
NotificationHelper.destroy();
GeckoNetworkManager.destroy();

Просмотреть файл

@ -48,7 +48,9 @@ public class FormAssistPopup extends RelativeLayout implements BundleEventListen
private TextView mValidationMessageText;
private ImageView mValidationMessageArrow;
private ImageView mValidationMessageArrowInverted;
private final GeckoApp geckoApp;
private GeckoView mGeckoView;
private EventCallback mAutoCompleteCallback;
private double mX;
private double mY;
@ -88,37 +90,29 @@ public class FormAssistPopup extends RelativeLayout implements BundleEventListen
mAnimation.setDuration(75);
setFocusable(false);
geckoApp = (GeckoApp) ActivityUtils.getActivityFromContext(context);
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
geckoApp.getAppEventDispatcher().registerUiThreadListener(this,
public void create(final GeckoView view) {
mGeckoView = view;
mGeckoView.getEventDispatcher().registerUiThreadListener(this,
"FormAssist:AutoCompleteResult",
"FormAssist:ValidationMessage",
"FormAssist:Hide");
}
void destroy() {
}
@Override
public void onDetachedFromWindow() {
geckoApp.getAppEventDispatcher().unregisterUiThreadListener(this,
public void destroy() {
mGeckoView.getEventDispatcher().unregisterUiThreadListener(this,
"FormAssist:AutoCompleteResult",
"FormAssist:ValidationMessage",
"FormAssist:Hide");
super.onDetachedFromWindow();
mGeckoView = null;
}
@Override // BundleEventListener
public void handleMessage(final String event, final GeckoBundle message,
final EventCallback callback) {
if ("FormAssist:AutoCompleteResult".equals(event)) {
mAutoCompleteCallback = callback;
showAutoCompleteSuggestions(message.getBundleArray("suggestions"),
message.getBundle("rect"),
message.getBoolean("isEmpty"));
@ -149,13 +143,20 @@ public class FormAssistPopup extends RelativeLayout implements BundleEventListen
mAutoCompleteList.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parentView, View view, int position, long id) {
public void onItemClick(final AdapterView<?> parentView, final View view,
final int position, final long id) {
if (mAutoCompleteCallback == null) {
hide();
return;
}
// Use the value stored with the autocomplete view, not the label text,
// since they can be different.
final TextView textView = (TextView) view;
final GeckoBundle message = new GeckoBundle(1);
final GeckoBundle message = new GeckoBundle(2);
message.putString("action", "autocomplete");
message.putString("value", (String) textView.getTag());
geckoApp.getAppEventDispatcher().dispatch("FormAssist:AutoComplete", message);
mAutoCompleteCallback.sendSuccess(message);
hide();
}
});
@ -166,15 +167,20 @@ public class FormAssistPopup extends RelativeLayout implements BundleEventListen
final SwipeDismissListViewTouchListener touchListener = new SwipeDismissListViewTouchListener(mAutoCompleteList, new OnDismissCallback() {
@Override
public void onDismiss(ListView listView, final int position) {
if (mAutoCompleteCallback == null) {
return;
}
// Use the value stored with the autocomplete view, not the label text,
// since they can be different.
AutoCompleteListAdapter adapter = (AutoCompleteListAdapter) listView.getAdapter();
Pair<String, String> item = adapter.getItem(position);
// Remove the item from form history.
final GeckoBundle message = new GeckoBundle(1);
final GeckoBundle message = new GeckoBundle(2);
message.putString("action", "remove");
message.putString("value", item.second);
geckoApp.getAppEventDispatcher().dispatch("FormAssist:Remove", message);
mAutoCompleteCallback.sendSuccess(message);
// Update the list
adapter.remove(item);
@ -248,7 +254,7 @@ public class FormAssistPopup extends RelativeLayout implements BundleEventListen
}
private void positionAndShowPopup() {
positionAndShowPopup(GeckoAppShell.getLayerView().getViewportMetrics());
positionAndShowPopup(mGeckoView.getViewportMetrics());
}
private void positionAndShowPopup(ImmutableViewportMetrics aMetrics) {
@ -281,7 +287,8 @@ public class FormAssistPopup extends RelativeLayout implements BundleEventListen
// These values correspond to the input box for which we want to
// display the FormAssistPopup.
int left = (int) (mX * zoom - aMetrics.viewportRectLeft);
int top = (int) (mY * zoom - aMetrics.viewportRectTop + GeckoAppShell.getLayerView().getCurrentToolbarHeight());
int top = (int) (mY * zoom - aMetrics.viewportRectTop + mGeckoView.getTop() +
mGeckoView.getCurrentToolbarHeight());
int width = (int) (mW * zoom);
int height = (int) (mH * zoom);
@ -329,7 +336,7 @@ public class FormAssistPopup extends RelativeLayout implements BundleEventListen
// If the popup doesn't fit below the input box, shrink its height, or
// see if we can place it above the input instead.
if ((popupTop + popupHeight) > viewport.height) {
if ((popupTop + popupHeight) > (mGeckoView.getTop() + viewport.height)) {
// Find where the maximum space is, and put the popup there.
if ((viewport.height - popupTop) > top) {
// Shrink the height to fit it below the input box.
@ -366,8 +373,8 @@ public class FormAssistPopup extends RelativeLayout implements BundleEventListen
public void hide() {
if (isShown()) {
setVisibility(GONE);
geckoApp.getAppEventDispatcher().dispatch("FormAssist:Hidden", null);
}
mAutoCompleteCallback = null;
}
void onTranslationChanged() {

Просмотреть файл

@ -1483,6 +1483,7 @@ public abstract class GeckoApp extends GeckoActivity
mDoorHangerPopup = new DoorHangerPopup(this, getAppEventDispatcher());
mDoorHangerPopup.setOnVisibilityChangeListener(this);
mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
mFormAssistPopup.create(mLayerView);
}
@Override
@ -2186,6 +2187,21 @@ public abstract class GeckoApp extends GeckoActivity
return;
}
if (mFormAssistPopup != null) {
mFormAssistPopup.destroy();
mFormAssistPopup = null;
}
if (mDoorHangerPopup != null) {
mDoorHangerPopup.destroy();
mDoorHangerPopup = null;
}
if (mTextSelection != null) {
mTextSelection.destroy();
mTextSelection = null;
}
EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
"Accessibility:Ready",
"Gecko:Ready",

Просмотреть файл

@ -10,13 +10,6 @@ var FormAssistant = {
// Weak-ref used to keep track of the currently focused element.
_currentFocusedElement: null,
// Used to keep track of the element that corresponds to the current
// autocomplete suggestions.
_currentInputElement: null,
// The value of the currently focused input.
_currentInputValue: null,
// Whether we're in the middle of an autocomplete.
_doingAutocomplete: false,
@ -24,22 +17,10 @@ var FormAssistant = {
Services.obs.addObserver(this, "PanZoom:StateChange");
},
register: function(aWindow) {
GeckoViewUtils.getDispatcherForWindow(aWindow).registerListener(this, [
"FormAssist:AutoComplete",
"FormAssist:Hidden",
"FormAssist:Remove",
]);
},
onEvent: function(event, message, callback) {
switch (event) {
case "FormAssist:AutoComplete": {
if (!this._currentInputElement) {
break;
}
let editableElement = this._currentInputElement.QueryInterface(
Ci.nsIDOMNSEditableElement);
_onPopupResponse: function(currentElement, message) {
switch (message.action) {
case "autocomplete": {
let editableElement = currentElement.QueryInterface(Ci.nsIDOMNSEditableElement);
this._doingAutocomplete = true;
// If we have an active composition string, commit it before sending
@ -51,29 +32,19 @@ var FormAssistant = {
} catch (e) {}
editableElement.setUserInput(message.value);
this._currentInputValue = message.value;
let event = this._currentInputElement.ownerDocument.createEvent("Events");
let event = currentElement.ownerDocument.createEvent("Events");
event.initEvent("DOMAutoComplete", true, true);
this._currentInputElement.dispatchEvent(event);
currentElement.dispatchEvent(event);
this._doingAutocomplete = false;
break;
}
case "FormAssist:Hidden": {
this._currentInputElement = null;
break;
}
case "FormAssist:Remove": {
if (!this._currentInputElement) {
break;
}
case "remove": {
FormHistory.update({
op: "remove",
fieldname: this._currentInputElement.name,
fieldname: currentElement.name,
value: message.value,
});
break;
@ -136,7 +107,6 @@ var FormAssistant = {
}
case "blur": {
this._currentInputValue = null;
this._currentFocusedElement = null;
break;
}
@ -168,14 +138,10 @@ var FormAssistant = {
// If this element isn't focused, we're already in middle of an
// autocomplete, or its value hasn't changed, don't show the
// autocomplete popup.
if (currentElement !== focused ||
this._doingAutocomplete ||
currentElement.value === this._currentInputValue) {
if (currentElement !== focused || this._doingAutocomplete) {
break;
}
this._currentInputValue = currentElement.value;
// Since we can only show one popup at a time, prioritze autocomplete
// suggestions over a form validation message
let checkResultsInput = hasResults => {
@ -295,11 +261,11 @@ var FormAssistant = {
suggestions: suggestions,
rect: this._getBoundingContentRect(aElement),
isEmpty: isEmpty,
}, {
onSuccess: response => this._onPopupResponse(aElement, response),
onError: error => Cu.reportError(error),
});
// Keep track of input element so we can fill it in if the user
// selects an autocomplete suggestion
this._currentInputElement = aElement;
aCallback(true);
};

Просмотреть файл

@ -142,7 +142,6 @@ BrowserCLH.prototype = {
event.target instanceof Ci.nsIDOMHTMLSelectElement ||
event.target instanceof Ci.nsIDOMHTMLButtonElement) {
// Only load FormAssistant when the event target is what we care about.
this.FormAssistant.register(win);
return this.FormAssistant;
}
return null;

Просмотреть файл

@ -113,10 +113,10 @@ DispatcherDelegate.prototype = {
*
* @param msg Message to send; must be an object with a "type" property
*/
sendRequest: function(msg) {
sendRequest: function(msg, callback) {
let type = msg.type;
msg.type = undefined;
this.dispatch(type, msg);
this.dispatch(type, msg, callback);
},
/**