Merge "Refactor input dispatcher use of window/app handles."
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 0294e3f..88583df 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -120,6 +120,7 @@
     private static final int AUTO_LOGIN                          = 140;
     private static final int CLIENT_CERT_REQUEST                 = 141;
     private static final int SEARCHBOX_IS_SUPPORTED_CALLBACK     = 142;
+    private static final int SEARCHBOX_DISPATCH_COMPLETE_CALLBACK= 143;
 
     // Message triggered by the client to resume execution
     private static final int NOTIFY                              = 200;
@@ -821,6 +822,13 @@
                 searchBox.handleIsSupportedCallback(supported);
                 break;
             }
+            case SEARCHBOX_DISPATCH_COMPLETE_CALLBACK: {
+                SearchBoxImpl searchBox = (SearchBoxImpl) mWebView.getSearchBox();
+                Boolean success = (Boolean) msg.obj;
+                searchBox.handleDispatchCompleteCallback(msg.getData().getString("function"),
+                        msg.getData().getInt("id"), success);
+                break;
+            }
         }
     }
 
@@ -1641,4 +1649,13 @@
         msg.obj = new Boolean(isSupported);
         sendMessage(msg);
     }
+
+    void onSearchboxDispatchCompleteCallback(String function, int id, boolean success) {
+        Message msg = obtainMessage(SEARCHBOX_DISPATCH_COMPLETE_CALLBACK);
+        msg.obj = Boolean.valueOf(success);
+        msg.getData().putString("function", function);
+        msg.getData().putInt("id", id);
+
+        sendMessage(msg);
+    }
 }
diff --git a/core/java/android/webkit/SearchBox.java b/core/java/android/webkit/SearchBox.java
index 5075302..6512c4b 100644
--- a/core/java/android/webkit/SearchBox.java
+++ b/core/java/android/webkit/SearchBox.java
@@ -68,11 +68,15 @@
      * Notify the search page of any changes to the searchbox. Such as
      * a change in the typed query (onchange), the user commiting a given query
      * (onsubmit), or a change in size of a suggestions dropdown (onresize).
+     *
+     * @param listener an optional listener to notify of the success of the operation,
+     *      indicating if the javascript function existed and could be called or not.
+     *      It will be called on the UI thread.
      */
-    void onchange();
-    void onsubmit();
-    void onresize();
-    void oncancel();
+    void onchange(SearchBoxListener listener);
+    void onsubmit(SearchBoxListener listener);
+    void onresize(SearchBoxListener listener);
+    void oncancel(SearchBoxListener listener);
 
     /**
      * Add and remove listeners to the given Searchbox. Listeners are notified
@@ -91,8 +95,12 @@
      * Listeners (if any) will be called on the thread that created the
      * webview.
      */
-    interface SearchBoxListener {
-        void onSuggestionsReceived(String query, List<String> suggestions);
+    public abstract class SearchBoxListener {
+        public void onSuggestionsReceived(String query, List<String> suggestions) {}
+        public void onChangeComplete(boolean called) {}
+        public void onSubmitComplete(boolean called) {}
+        public void onResizeComplete(boolean called) {}
+        public void onCancelComplete(boolean called) {}
     }
 
     interface IsSupportedCallback {
diff --git a/core/java/android/webkit/SearchBoxImpl.java b/core/java/android/webkit/SearchBoxImpl.java
index 61fb2ce..9942d25 100644
--- a/core/java/android/webkit/SearchBoxImpl.java
+++ b/core/java/android/webkit/SearchBoxImpl.java
@@ -16,10 +16,12 @@
 
 package android.webkit;
 
+import android.text.TextUtils;
 import android.util.Log;
 import android.webkit.WebViewCore.EventHub;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 
 import org.json.JSONArray;
@@ -69,7 +71,7 @@
 
     private static final String SET_VERBATIM_SCRIPT
             =  "if (window.chrome && window.chrome.searchBox) {"
-            + "  window.chrome.searchBox.verbatim = %s;"
+            + "  window.chrome.searchBox.verbatim = %1$s;"
             + "}";
 
     private static final String SET_SELECTION_SCRIPT
@@ -89,13 +91,21 @@
             + "}";
 
     private static final String DISPATCH_EVENT_SCRIPT
-            = "if (window.chrome && window.chrome.searchBox &&"
-            + "  window.chrome.searchBox.on%1$s) { window.chrome.searchBox.on%1$s(); }";
+            = "if (window.chrome && window.chrome.searchBox && window.chrome.searchBox.on%1$s) {"
+            + "  window.chrome.searchBox.on%1$s();"
+            + "  window.searchBoxJavaBridge_.dispatchCompleteCallback('%1$s', %2$d, true);"
+            + "} else {"
+            + "  window.searchBoxJavaBridge_.dispatchCompleteCallback('%1$s', %2$d, false);"
+            + "}";
+
+    private static final String EVENT_CHANGE = "change";
+    private static final String EVENT_SUBMIT = "submit";
+    private static final String EVENT_RESIZE = "resize";
+    private static final String EVENT_CANCEL = "cancel";
 
     private static final String IS_SUPPORTED_SCRIPT
             = "if (window.searchBoxJavaBridge_) {"
-            + "  if (window.chrome && window.chrome.searchBox && "
-            + "  window.chrome.searchBox.onsubmit) {"
+            + "  if (window.chrome && window.chrome.sv) {"
             + "    window.searchBoxJavaBridge_.isSupportedCallback(true);"
             + "  } else {"
             + "    window.searchBoxJavaBridge_.isSupportedCallback(false);"
@@ -105,11 +115,14 @@
     private final WebViewCore mWebViewCore;
     private final CallbackProxy mCallbackProxy;
     private IsSupportedCallback mSupportedCallback;
+    private int mNextEventId = 1;
+    private final HashMap<Integer, SearchBoxListener> mEventCallbacks;
 
     SearchBoxImpl(WebViewCore webViewCore, CallbackProxy callbackProxy) {
         mListeners = new ArrayList<SearchBoxListener>();
         mWebViewCore = webViewCore;
         mCallbackProxy = callbackProxy;
+        mEventCallbacks = new HashMap<Integer, SearchBoxListener>();
     }
 
     @Override
@@ -141,27 +154,36 @@
     }
 
     @Override
-    public void onchange() {
-        dispatchEvent("change");
+    public void onchange(SearchBoxListener callback) {
+        dispatchEvent(EVENT_CHANGE, callback);
     }
 
     @Override
-    public void onsubmit() {
-        dispatchEvent("submit");
+    public void onsubmit(SearchBoxListener callback) {
+        dispatchEvent(EVENT_SUBMIT, callback);
     }
 
     @Override
-    public void onresize() {
-        dispatchEvent("resize");
+    public void onresize(SearchBoxListener callback) {
+        dispatchEvent(EVENT_RESIZE, callback);
     }
 
     @Override
-    public void oncancel() {
-        dispatchEvent("cancel");
+    public void oncancel(SearchBoxListener callback) {
+        dispatchEvent(EVENT_CANCEL, callback);
     }
 
-    private void dispatchEvent(String eventName) {
-        final String js = String.format(DISPATCH_EVENT_SCRIPT, eventName);
+    private void dispatchEvent(String eventName, SearchBoxListener callback) {
+        int eventId;
+        if (callback != null) {
+            synchronized(this) {
+                eventId = mNextEventId++;
+                mEventCallbacks.put(eventId, callback);
+            }
+        } else {
+            eventId = 0;
+        }
+        final String js = String.format(DISPATCH_EVENT_SCRIPT, eventName, eventId);
         dispatchJs(js);
     }
 
@@ -202,9 +224,35 @@
         }
     }
 
+    // Called by Javascript through the Java bridge.
+    public void dispatchCompleteCallback(String function, int id, boolean successful) {
+        mCallbackProxy.onSearchboxDispatchCompleteCallback(function, id, successful);
+    }
+
+    public void handleDispatchCompleteCallback(String function, int id, boolean successful) {
+        if (id != 0) {
+            SearchBoxListener listener;
+            synchronized(this) {
+                listener = mEventCallbacks.get(id);
+                mEventCallbacks.remove(id);
+            }
+            if (listener != null) {
+                if (TextUtils.equals(EVENT_CHANGE, function)) {
+                    listener.onChangeComplete(successful);
+                } else if (TextUtils.equals(EVENT_SUBMIT, function)) {
+                    listener.onSubmitComplete(successful);
+                } else if (TextUtils.equals(EVENT_RESIZE, function)) {
+                    listener.onResizeComplete(successful);
+                } else if (TextUtils.equals(EVENT_CANCEL, function)) {
+                    listener.onCancelComplete(successful);
+                }
+            }
+        }
+    }
+
     // This is used as a hackish alternative to javascript escaping.
     // There appears to be no such functionality in the core framework.
-    private String jsonSerialize(String query) {
+    private static String jsonSerialize(String query) {
         JSONStringer stringer = new JSONStringer();
         try {
             stringer.array().value(query).endArray();
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index c652e55..5414b79 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -248,7 +248,7 @@
 
     /* Get the BrowserFrame component. This is used for subwindow creation and
      * is called only from BrowserFrame in the WebCore thread. */
-    /* package */ BrowserFrame getBrowserFrame() {
+    /* package */ synchronized BrowserFrame getBrowserFrame() {
         return mBrowserFrame;
     }