Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9497c5f8 authored by Narayan Kamath's avatar Narayan Kamath
Browse files

A Java implementation of the SearchBox API.

Implemented as a Java bridge object and a JS adapter.
Works on both V8 and JSC.

Change-Id: I8a4268fb3c6688b5ece9e27c11724c0776017255
parent 43cdf9b4
Loading
Loading
Loading
Loading
+24 −16
Original line number Diff line number Diff line
@@ -46,7 +46,6 @@ import java.lang.ref.WeakReference;
import java.net.URLEncoder;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
@@ -89,6 +88,9 @@ class BrowserFrame extends Handler {
    // Key store handler when Chromium HTTP stack is used.
    private KeyStoreHandler mKeyStoreHandler = null;

    // Implementation of the searchbox API.
    private final SearchBoxImpl mSearchBox;

    // message ids
    // a message posted when a frame loading is completed
    static final int FRAME_COMPLETED = 1001;
@@ -225,6 +227,9 @@ class BrowserFrame extends Handler {
        sConfigCallback.addHandler(this);

        mJSInterfaceMap = javascriptInterfaces;
        if (mJSInterfaceMap == null) {
            mJSInterfaceMap = new HashMap<String, Object>();
        }

        mSettings = settings;
        mContext = context;
@@ -232,6 +237,9 @@ class BrowserFrame extends Handler {
        mDatabase = WebViewDatabase.getInstance(appContext);
        mWebViewCore = w;

        mSearchBox = new SearchBoxImpl(mWebViewCore, mCallbackProxy);
        mJSInterfaceMap.put(SearchBoxImpl.JS_INTERFACE_NAME, mSearchBox);

        AssetManager am = context.getAssets();
        nativeCreateFrame(w, am, proxy.getBackForwardList());

@@ -587,17 +595,17 @@ class BrowserFrame extends Handler {
     * We should re-attach any attached js interfaces.
     */
    private void windowObjectCleared(int nativeFramePointer) {
        if (mJSInterfaceMap != null) {
            Iterator iter = mJSInterfaceMap.keySet().iterator();
        Iterator<String> iter = mJSInterfaceMap.keySet().iterator();
        while (iter.hasNext())  {
                String interfaceName = (String) iter.next();
            String interfaceName = iter.next();
            Object object = mJSInterfaceMap.get(interfaceName);
            if (object != null) {
                nativeAddJavascriptInterface(nativeFramePointer,
                        mJSInterfaceMap.get(interfaceName), interfaceName);
            }
        }
        }

        stringByEvaluatingJavaScriptFromString(SearchBoxImpl.JS_BRIDGE);
    }

    /**
@@ -620,16 +628,11 @@ class BrowserFrame extends Handler {
    public void addJavascriptInterface(Object obj, String interfaceName) {
        assert obj != null;
        removeJavascriptInterface(interfaceName);
        if (mJSInterfaceMap == null) {
            mJSInterfaceMap = new HashMap<String, Object>();
        }

        mJSInterfaceMap.put(interfaceName, obj);
    }

    public void removeJavascriptInterface(String interfaceName) {
        if (mJSInterfaceMap == null) {
            return;
        }
        if (mJSInterfaceMap.containsKey(interfaceName)) {
            mJSInterfaceMap.remove(interfaceName);
        }
@@ -1234,6 +1237,11 @@ class BrowserFrame extends Handler {
        }
    }


    /*package*/ SearchBox getSearchBox() {
        return mSearchBox;
    }

    //==========================================================================
    // native functions
    //==========================================================================
+16 −0
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import com.android.internal.R;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
@@ -115,6 +116,7 @@ class CallbackProxy extends Handler {
    private static final int HISTORY_INDEX_CHANGED               = 136;
    private static final int AUTH_CREDENTIALS                    = 137;
    private static final int SET_INSTALLABLE_WEBAPP              = 138;
    private static final int NOTIFY_SEARCHBOX_LISTENERS          = 139;

    // Message triggered by the client to resume execution
    private static final int NOTIFY                              = 200;
@@ -781,6 +783,12 @@ class CallbackProxy extends Handler {
                    mWebChromeClient.setInstallableWebApp();
                }
                break;
            case NOTIFY_SEARCHBOX_LISTENERS:
                SearchBoxImpl searchBox = (SearchBoxImpl) mWebView.getSearchBox();

                @SuppressWarnings("unchecked")
                List<String> suggestions = (List<String>) msg.obj;
                searchBox.handleSuggestions(msg.getData().getString("query"), suggestions);
        }
    }

@@ -1557,4 +1565,12 @@ class CallbackProxy extends Handler {
        // See bug 3166409
        return mContext instanceof Activity;
    }

    void onSearchboxSuggestionsReceived(String query, List<String> suggestions) {
        Message msg = obtainMessage(NOTIFY_SEARCHBOX_LISTENERS);
        msg.obj = suggestions;
        msg.getData().putString("query", query);

        sendMessage(msg);
    }
}
+92 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 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.
 */


package android.webkit;

import java.util.List;

/**
 * Defines the interaction between the browser/renderer and the page running on
 * a given WebView frame, if the page supports the chromium SearchBox API.
 *
 * http://dev.chromium.org/searchbox
 *
 * The browser or container app can query the page for search results using
 * SearchBox.query() and receive suggestions by registering a listener on the
 * SearchBox object.
 *
 * @hide pending API council approval.
 */
public interface SearchBox {
    /**
     * Sets the current searchbox query. Note that the caller must call
     * onchange() to ensure that the search page processes this query.
     */
    void setQuery(String query);

    /**
     * Verbatim is true if the caller suggests that the search page
     * treat the current query as a verbatim search query (as opposed to a
     * partially typed search query). As with setQuery, onchange() must be
     * called to ensure that the search page processes the query.
     */
    void setVerbatim(boolean verbatim);

    /**
     * These attributes must contain the offset to the characters that immediately
     * follow the start and end of the selection in the search box. If there is
     * no such selection, then both selectionStart and selectionEnd must be the offset
     * to the character that immediately follows the text entry cursor. In the case
     * that there is no explicit text entry cursor, the cursor is
     * implicitly at the end of the input.
     */
    void setSelection(int selectionStart, int selectionEnd);

    /**
     * Sets the dimensions of the view (if any) that overlaps the current
     * window object. This is to ensure that the page renders results in
     * a manner that allows them to not be obscured by such a view. Note
     * that a call to onresize() is required if these dimensions change.
     */
    void setDimensions(int x, int y, int width, int height);

    /**
     * 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).
     */
    void onchange();
    void onsubmit();
    void onresize();
    void oncancel();

    /**
     * Add and remove listeners to the given Searchbox. Listeners are notified
     * of any suggestions to the query that the underlying search engine might
     * provide.
     */
    void addSearchBoxListener(SearchBoxListener l);
    void removeSearchBoxListener(SearchBoxListener l);

    /**
     * Listeners (if any) will be called on the thread that created the
     * webview.
     */
    interface SearchBoxListener {
        void onSuggestionsReceived(String query, List<String> suggestions);
    }
}
+227 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 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.
 */

package android.webkit;

import android.util.Log;
import android.webkit.WebViewCore.EventHub;

import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONStringer;

/**
 * The default implementation of the SearchBox interface. Implemented
 * as a java bridge object and a javascript adapter that is called into
 * by the page hosted in the frame.
 */
final class SearchBoxImpl implements SearchBox {
    private static final String TAG = "WebKit.SearchBoxImpl";

    /* package */ static final String JS_INTERFACE_NAME = "searchBoxJavaBridge_";

    /* package */ static final String JS_BRIDGE
            = "(function()"
            + "{"
            + "if (!window.chrome) {"
            + "  window.chrome = {};"
            + "}"
            + "if (!window.chrome.searchBox) {"
            + "  var sb = window.chrome.searchBox = {};"
            + "  sb.setSuggestions = function(suggestions) {"
            + "    if (window.searchBoxJavaBridge_) {"
            + "      window.searchBoxJavaBridge_.setSuggestions(JSON.stringify(suggestions));"
            + "    }"
            + "  };"
            + "  sb.setValue = function(valueArray) { sb.value = valueArray[0]; };"
            + "  sb.value = '';"
            + "  sb.x = 0;"
            + "  sb.y = 0;"
            + "  sb.width = 0;"
            + "  sb.height = 0;"
            + "  sb.selectionStart = 0;"
            + "  sb.selectionEnd = 0;"
            + "  sb.verbatim = false;"
            + "}"
            + "})();";

    private static final String SET_QUERY_SCRIPT
            = "if (window.chrome && window.chrome.searchBox) {"
            + "  window.chrome.searchBox.setValue(%s);"
            + "}";

    private static final String SET_VERBATIM_SCRIPT
            =  "if (window.chrome && window.chrome.searchBox) {"
            + "  window.chrome.searchBox.verbatim = %s;"
            + "}";

    private static final String SET_SELECTION_SCRIPT
            = "if (window.chrome && window.chrome.searchBox) {"
            + "  var f = window.chrome.searchBox;"
            + "  f.selectionStart = %d"
            + "  f.selectionEnd = %d"
            + "}";

    private static final String SET_DIMENSIONS_SCRIPT
            = "if (window.chrome && window.chrome.searchBox) { "
            + "  var f = window.chrome.searchBox;"
            + "  f.x = %d;"
            + "  f.y = %d;"
            + "  f.width = %d;"
            + "  f.height = %d;"
            + "}";

    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(); }";

    private final List<SearchBoxListener> mListeners;
    private final WebViewCore mWebViewCore;
    private final CallbackProxy mCallbackProxy;

    SearchBoxImpl(WebViewCore webViewCore, CallbackProxy callbackProxy) {
        mListeners = new ArrayList<SearchBoxListener>();
        mWebViewCore = webViewCore;
        mCallbackProxy = callbackProxy;
    }

    @Override
    public void setQuery(String query) {
        final String formattedQuery = jsonSerialize(query);
        if (formattedQuery != null) {
            final String js = String.format(SET_QUERY_SCRIPT, formattedQuery);
            dispatchJs(js);
        }
    }

    @Override
    public void setVerbatim(boolean verbatim) {
        final String js = String.format(SET_VERBATIM_SCRIPT, String.valueOf(verbatim));
        dispatchJs(js);
    }


    @Override
    public void setSelection(int selectionStart, int selectionEnd) {
        final String js = String.format(SET_SELECTION_SCRIPT, selectionStart, selectionEnd);
        dispatchJs(js);
    }

    @Override
    public void setDimensions(int x, int y, int width, int height) {
        final String js = String.format(SET_DIMENSIONS_SCRIPT, x, y, width, height);
        dispatchJs(js);
    }

    @Override
    public void onchange() {
        dispatchEvent("change");
    }

    @Override
    public void onsubmit() {
        dispatchEvent("submit");
    }

    @Override
    public void onresize() {
        dispatchEvent("resize");
    }

    @Override
    public void oncancel() {
        dispatchEvent("cancel");
    }

    private void dispatchEvent(String eventName) {
        final String js = String.format(DISPATCH_EVENT_SCRIPT, eventName);
        dispatchJs(js);
    }

    private void dispatchJs(String js) {
        mWebViewCore.sendMessage(EventHub.EXECUTE_JS, js);
    }

    @Override
    public void addSearchBoxListener(SearchBoxListener l) {
        synchronized (mListeners) {
            mListeners.add(l);
        }
    }

    @Override
    public void removeSearchBoxListener(SearchBoxListener l) {
        synchronized (mListeners) {
            mListeners.remove(l);
        }
    }

    // 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) {
        JSONStringer stringer = new JSONStringer();
        try {
            stringer.array().value(query).endArray();
        } catch (JSONException e) {
            Log.w(TAG, "Error serializing query : " + query);
            return null;
        }
        return stringer.toString();
    }

    // Called by Javascript through the Java bridge.
    public void setSuggestions(String jsonArguments) {
        if (jsonArguments == null) {
            return;
        }

        String query = null;
        List<String> suggestions = new ArrayList<String>();
        try {
            JSONObject suggestionsJson = new JSONObject(jsonArguments);
            query = suggestionsJson.getString("query");

            final JSONArray suggestionsArray = suggestionsJson.getJSONArray("suggestions");
            for (int i = 0; i < suggestionsArray.length(); ++i) {
                final JSONObject suggestion = suggestionsArray.getJSONObject(i);
                final String value = suggestion.getString("value");
                if (value != null) {
                    suggestions.add(value);
                }
                // We currently ignore the "type" of the suggestion. This isn't
                // documented anywhere in the API documents.
                // final String type = suggestions.getString("type");
            }
        } catch (JSONException je) {
            Log.w(TAG, "Error parsing json [" + jsonArguments + "], exception = " + je);
            return;
        }

        mCallbackProxy.onSearchboxSuggestionsReceived(query, suggestions);
    }

    /* package */ void handleSuggestions(String query, List<String> suggestions) {
        synchronized (mListeners) {
            for (int i = mListeners.size() - 1; i >= 0; i--) {
                mListeners.get(i).onSuggestionsReceived(query, suggestions);
            }
        }
    }
}
+8 −1
Original line number Diff line number Diff line
@@ -5030,6 +5030,13 @@ public class WebView extends AbsoluteLayout
        return copiedSomething;
    }

    /**
     * @hide pending API Council approval.
     */
    public SearchBox getSearchBox() {
        return mWebViewCore.getBrowserFrame().getSearchBox();
    }

    /**
     * Returns the currently highlighted text as a string.
     */
@@ -8390,7 +8397,7 @@ public class WebView extends AbsoluteLayout
    private native void     nativeSetFindIsUp(boolean isUp);
    private native void     nativeSetHeightCanMeasure(boolean measure);
    private native void     nativeSetBaseLayer(int layer, Rect invalRect,
            boolean showVisualIndciator);
            boolean showVisualIndicator);
    private native void     nativeShowCursorTimed();
    private native void     nativeReplaceBaseContent(int content);
    private native void     nativeCopyBaseContentToPicture(Picture pict);
Loading