--- a/mobile/android/chrome/content/SelectionHandler.js
+++ b/mobile/android/chrome/content/SelectionHandler.js
@@ -53,32 +53,42 @@ var SelectionHandler = {
_addObservers: function sh_addObservers() {
Services.obs.addObserver(this, "Gesture:SingleTap", false);
Services.obs.addObserver(this, "Tab:Selected", false);
Services.obs.addObserver(this, "after-viewport-change", false);
Services.obs.addObserver(this, "TextSelection:Move", false);
Services.obs.addObserver(this, "TextSelection:Position", false);
Services.obs.addObserver(this, "TextSelection:End", false);
Services.obs.addObserver(this, "TextSelection:Action", false);
- BrowserApp.deck.addEventListener("compositionend", this, false);
+
+ BrowserApp.deck.addEventListener("pagehide", this, false);
+ BrowserApp.deck.addEventListener("blur", this, true);
},
_removeObservers: function sh_removeObservers() {
Services.obs.removeObserver(this, "Gesture:SingleTap");
Services.obs.removeObserver(this, "Tab:Selected");
Services.obs.removeObserver(this, "after-viewport-change");
Services.obs.removeObserver(this, "TextSelection:Move");
Services.obs.removeObserver(this, "TextSelection:Position");
Services.obs.removeObserver(this, "TextSelection:End");
Services.obs.removeObserver(this, "TextSelection:Action");
- BrowserApp.deck.removeEventListener("compositionend", this);
+
+ BrowserApp.deck.removeEventListener("pagehide", this);
+ BrowserApp.deck.removeEventListener("blur", this);
},
observe: function sh_observe(aSubject, aTopic, aData) {
switch (aTopic) {
+ // Update caret position on keyboard activity
+ case "TextSelection:UpdateCaretPos":
+ // Generated by IME close, autoCorrection / styling
+ this._positionHandles();
+ break;
+
case "Gesture:SingleTap": {
if (this._activeType == this.TYPE_SELECTION) {
let data = JSON.parse(aData);
if (!this._pointInSelection(data.x, data.y))
this._closeSelection();
} else if (this._activeType == this.TYPE_CURSOR) {
// attachCaret() is called in the "Gesture:SingleTap" handler in BrowserEventHandler
// We're guaranteed to call this first, because this observer was added last
@@ -170,26 +180,28 @@ var SelectionHandler = {
});
break;
}
},
handleEvent: function sh_handleEvent(aEvent) {
switch (aEvent.type) {
case "pagehide":
- // We only add keydown and blur listeners for TYPE_CURSOR
- case "keydown":
case "blur":
this._closeSelection();
break;
+ // Update caret position on keyboard activity
+ case "keyup":
+ // Not generated by Swiftkeyboard
+ case "compositionupdate":
case "compositionend":
- // compositionend messages normally terminate caret display
- if (this._activeType == this.TYPE_CURSOR && !this._ignoreCompositionChanges) {
- this._deactivate();
+ // Generated by SwiftKeyboard, et. al.
+ if (!this._ignoreCompositionChanges) {
+ this._positionHandles();
}
break;
}
},
/** Returns true if the provided element can be selected in text selection, false otherwise. */
canSelect: function sh_canSelect(aElement) {
return !(aElement instanceof Ci.nsIDOMHTMLButtonElement ||
@@ -492,38 +504,42 @@ var SelectionHandler = {
*/
attachCaret: function sh_attachCaret(aElement) {
// See if its an input element, and it isn't disabled, nor handled by Android native dialog
if (aElement.disabled ||
InputWidgetHelper.hasInputWidget(aElement) ||
!((aElement instanceof HTMLInputElement && aElement.mozIsTextField(false)) ||
(aElement instanceof HTMLTextAreaElement)))
return;
+
this._initTargetInfo(aElement);
- this._contentWindow.addEventListener("keydown", this, false);
- this._contentWindow.addEventListener("blur", this, true);
+ // Caret-specific observer/listeners
+ Services.obs.addObserver(this, "TextSelection:UpdateCaretPos", false);
+ BrowserApp.deck.addEventListener("keyup", this, false);
+ BrowserApp.deck.addEventListener("compositionupdate", this, false);
+ BrowserApp.deck.addEventListener("compositionend", this, false);
this._activeType = this.TYPE_CURSOR;
this._positionHandles();
this._sendMessage("TextSelection:ShowHandles", [this.HANDLE_TYPE_MIDDLE]);
},
+ // Target initialization for both TYPE_CURSOR and TYPE_SELECTION
_initTargetInfo: function sh_initTargetInfo(aElement) {
this._targetElement = aElement;
if (aElement instanceof Ci.nsIDOMNSEditableElement) {
aElement.focus();
}
this._contentWindow = aElement.ownerDocument.defaultView;
this._isRTL = (this._contentWindow.getComputedStyle(aElement, "").direction == "rtl");
this._addObservers();
- this._contentWindow.addEventListener("pagehide", this, false);
},
_getSelection: function sh_getSelection() {
if (this._targetElement instanceof Ci.nsIDOMNSEditableElement)
return this._targetElement.QueryInterface(Ci.nsIDOMNSEditableElement).editor.selection;
else
return this._contentWindow.getSelection();
},
@@ -744,30 +760,36 @@ var SelectionHandler = {
// Clear selection without clearing the anchorNode or focusNode
if (selection.rangeCount != 0) {
selection.collapseToStart();
}
}
},
_deactivate: function sh_deactivate() {
- this._activeType = this.TYPE_NONE;
-
sendMessageToJava({ type: "TextSelection:HideHandles" });
this._removeObservers();
- this._contentWindow.removeEventListener("pagehide", this, false);
- this._contentWindow.removeEventListener("keydown", this, false);
- this._contentWindow.removeEventListener("blur", this, true);
+
+ // Only observed for caret positioning
+ if (this._activeType == this.TYPE_CURSOR) {
+ Services.obs.removeObserver(this, "TextSelection:UpdateCaretPos");
+ BrowserApp.deck.removeEventListener("keyup", this);
+ BrowserApp.deck.removeEventListener("compositionupdate", this);
+ BrowserApp.deck.removeEventListener("compositionend", this);
+ }
+
this._contentWindow = null;
this._targetElement = null;
this._isRTL = false;
this._cache = null;
this._ignoreSelectionChanges = false;
this._ignoreCompositionChanges = false;
+
+ this._activeType = this.TYPE_NONE;
},
_getViewOffset: function sh_getViewOffset() {
let offset = { x: 0, y: 0 };
let win = this._contentWindow;
// Recursively look through frames to compute the total position offset.
while (win.frameElement) {
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -1955,16 +1955,23 @@ nsWindow::OnIMEEvent(AndroidGeckoEvent *
}
selEvent.mOffset = std::min(start, end);
selEvent.mLength = std::max(start, end) - selEvent.mOffset;
selEvent.mReversed = start > end;
selEvent.mExpandToClusterBoundary = false;
DispatchEvent(&selEvent);
+
+ // Notify SelectionHandler of final caret position
+ // Required after IME hide via 'Back' button
+ AndroidGeckoEvent* broadcastEvent = AndroidGeckoEvent::MakeBroadcastEvent(
+ NS_LITERAL_CSTRING("TextSelection:UpdateCaretPos"),
+ NS_LITERAL_CSTRING(""));
+ nsAppShell::gAppShell->PostEvent(broadcastEvent);
}
break;
case AndroidGeckoEvent::IME_ADD_COMPOSITION_RANGE:
{
TextRange range;
range.mStartOffset = ae->Start();
range.mEndOffset = ae->End();
range.mRangeType = ae->RangeType();
@@ -2042,16 +2049,23 @@ nsWindow::OnIMEEvent(AndroidGeckoEvent *
const NS_ConvertUTF16toUTF8 theText8(event.theText);
const char* text = theText8.get();
ALOGIME("IME: IME_SET_TEXT: text=\"%s\", length=%u, range=%u",
text, event.theText.Length(), mIMERanges.Length());
#endif // DEBUG_ANDROID_IME
DispatchEvent(&event);
mIMERanges.Clear();
+
+ // Notify SelectionHandler of final caret position
+ // Required in cases of keyboards providing autoCorrections
+ AndroidGeckoEvent* broadcastEvent = AndroidGeckoEvent::MakeBroadcastEvent(
+ NS_LITERAL_CSTRING("TextSelection:UpdateCaretPos"),
+ NS_LITERAL_CSTRING(""));
+ nsAppShell::gAppShell->PostEvent(broadcastEvent);
}
break;
case AndroidGeckoEvent::IME_REMOVE_COMPOSITION:
{
/*
* Remove any previous composition. This is only used for
* visual indication and does not affect the text content.
*