| Index: chrome/renderer/searchbox/searchbox_extension.cc
|
| diff --git a/chrome/renderer/searchbox/searchbox_extension.cc b/chrome/renderer/searchbox/searchbox_extension.cc
|
| index ab3ad2b3db99004b7f3af1376ac2510f6ec50124..4165130b986012b47303cf51428495b0c29089b9 100644
|
| --- a/chrome/renderer/searchbox/searchbox_extension.cc
|
| +++ b/chrome/renderer/searchbox/searchbox_extension.cc
|
| @@ -4,14 +4,49 @@
|
|
|
| #include "chrome/renderer/searchbox/searchbox_extension.h"
|
|
|
| +#include <ctype.h>
|
| +#include <vector>
|
| +
|
| +#include "base/string_number_conversions.h"
|
| +#include "base/string_piece.h"
|
| +#include "base/string_util.h"
|
| +#include "base/utf_string_conversions.h"
|
| #include "chrome/renderer/searchbox/searchbox.h"
|
| #include "content/public/renderer/render_view.h"
|
| #include "grit/renderer_resources.h"
|
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
|
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebScriptSource.h"
|
| +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
|
| #include "ui/base/resource/resource_bundle.h"
|
| #include "v8/include/v8.h"
|
|
|
| +namespace {
|
| +
|
| +// Splits the string in |number| into two pieces, a leading number token (saved
|
| +// in |number|) and the rest (saved in |suffix|). Either piece may become empty,
|
| +// depending on whether the input had no digits or only digits. Neither argument
|
| +// may be NULL.
|
| +void SplitLeadingNumberToken(std::string* number, std::string* suffix) {
|
| + size_t i = 0;
|
| + while (i < number->size() && isdigit((*number)[i]))
|
| + ++i;
|
| + suffix->assign(*number, i, number->size() - i);
|
| + number->resize(i);
|
| +}
|
| +
|
| +// Converts a V8 value to a string16.
|
| +string16 V8ValueToUTF16(v8::Handle<v8::Value> v) {
|
| + v8::String::Value s(v);
|
| + return string16(reinterpret_cast<const char16*>(*s), s.length());
|
| +}
|
| +
|
| +// Converts string16 to V8 String.
|
| +v8::Handle<v8::String> UTF16ToV8String(const string16& s) {
|
| + return v8::String::New(reinterpret_cast<const uint16_t*>(s.data()), s.size());
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| namespace extensions_v8 {
|
|
|
| static const char kSearchBoxExtensionName[] = "v8/SearchBox";
|
| @@ -63,6 +98,26 @@ static const char kSupportsInstantScript[] =
|
| " false;"
|
| "}";
|
|
|
| +// Extended API.
|
| +static const char kDispatchAutocompleteResultsEventScript[] =
|
| + "if (window.chrome &&"
|
| + " window.chrome.searchBox &&"
|
| + " window.chrome.searchBox.onnativesuggestions &&"
|
| + " typeof window.chrome.searchBox.onnativesuggestions == 'function') {"
|
| + " window.chrome.searchBox.onnativesuggestions();"
|
| + " true;"
|
| + "}";
|
| +
|
| +static const char kDispatchKeyPressEventScript[] =
|
| + "if (window.chrome &&"
|
| + " window.chrome.searchBox &&"
|
| + " window.chrome.searchBox.onkeypress &&"
|
| + " typeof window.chrome.searchBox.onkeypress == 'function') {"
|
| + " window.chrome.searchBox.onkeypress("
|
| + " {keyCode:window.chrome.searchBox.keyCode});"
|
| + " true;"
|
| + "}";
|
| +
|
| // ----------------------------------------------------------------------------
|
|
|
| class SearchBoxExtensionWrapper : public v8::Extension {
|
| @@ -78,7 +133,7 @@ class SearchBoxExtensionWrapper : public v8::Extension {
|
| static content::RenderView* GetRenderView();
|
|
|
| // Gets the value of the user's search query.
|
| - static v8::Handle<v8::Value> GetValue(const v8::Arguments& args);
|
| + static v8::Handle<v8::Value> GetQuery(const v8::Arguments& args);
|
|
|
| // Gets whether the |value| should be considered final -- as opposed to a
|
| // partial match. This may be set if the user clicks a suggestion, presses
|
| @@ -105,9 +160,33 @@ class SearchBoxExtensionWrapper : public v8::Extension {
|
| // Gets the height of the region of the search box that overlaps the window.
|
| static v8::Handle<v8::Value> GetHeight(const v8::Arguments& args);
|
|
|
| + // Gets the autocomplete results from search box.
|
| + static v8::Handle<v8::Value> GetAutocompleteResults(
|
| + const v8::Arguments& args);
|
| +
|
| + // Gets the last key code entered in search box.
|
| + static v8::Handle<v8::Value> GetKeyCode(const v8::Arguments& args);
|
| +
|
| // Sets ordered suggestions. Valid for current |value|.
|
| static v8::Handle<v8::Value> SetSuggestions(const v8::Arguments& args);
|
|
|
| + // Sets the text to be autocompleted into the search box.
|
| + static v8::Handle<v8::Value> SetQuerySuggestion(const v8::Arguments& args);
|
| +
|
| + // Like |SetQuerySuggestion| but uses a restricted ID to identify the text.
|
| + static v8::Handle<v8::Value> SetQuerySuggestionFromAutocompleteResult(
|
| + const v8::Arguments& args);
|
| +
|
| + // Sets the search box text, completely replacing what the user typed.
|
| + static v8::Handle<v8::Value> SetQuery(const v8::Arguments& args);
|
| +
|
| + // Like |SetQuery| but uses a restricted ID to identify the text.
|
| + static v8::Handle<v8::Value> SetQueryFromAutocompleteResult(
|
| + const v8::Arguments& args);
|
| +
|
| + // Resize the preview to the given height.
|
| + static v8::Handle<v8::Value> SetPreviewHeight(const v8::Arguments& args);
|
| +
|
| private:
|
| DISALLOW_COPY_AND_ASSIGN(SearchBoxExtensionWrapper);
|
| };
|
| @@ -119,8 +198,8 @@ SearchBoxExtensionWrapper::SearchBoxExtensionWrapper(
|
|
|
| v8::Handle<v8::FunctionTemplate> SearchBoxExtensionWrapper::GetNativeFunction(
|
| v8::Handle<v8::String> name) {
|
| - if (name->Equals(v8::String::New("GetValue"))) {
|
| - return v8::FunctionTemplate::New(GetValue);
|
| + if (name->Equals(v8::String::New("GetQuery"))) {
|
| + return v8::FunctionTemplate::New(GetQuery);
|
| } else if (name->Equals(v8::String::New("GetVerbatim"))) {
|
| return v8::FunctionTemplate::New(GetVerbatim);
|
| } else if (name->Equals(v8::String::New("GetSelectionStart"))) {
|
| @@ -135,8 +214,23 @@ v8::Handle<v8::FunctionTemplate> SearchBoxExtensionWrapper::GetNativeFunction(
|
| return v8::FunctionTemplate::New(GetWidth);
|
| } else if (name->Equals(v8::String::New("GetHeight"))) {
|
| return v8::FunctionTemplate::New(GetHeight);
|
| + } else if (name->Equals(v8::String::New("GetAutocompleteResults"))) {
|
| + return v8::FunctionTemplate::New(GetAutocompleteResults);
|
| + } else if (name->Equals(v8::String::New("GetKeyCode"))) {
|
| + return v8::FunctionTemplate::New(GetKeyCode);
|
| } else if (name->Equals(v8::String::New("SetSuggestions"))) {
|
| return v8::FunctionTemplate::New(SetSuggestions);
|
| + } else if (name->Equals(v8::String::New("SetQuerySuggestion"))) {
|
| + return v8::FunctionTemplate::New(SetQuerySuggestion);
|
| + } else if (name->Equals(v8::String::New(
|
| + "SetQuerySuggestionFromAutocompleteResult"))) {
|
| + return v8::FunctionTemplate::New(SetQuerySuggestionFromAutocompleteResult);
|
| + } else if (name->Equals(v8::String::New("SetQuery"))) {
|
| + return v8::FunctionTemplate::New(SetQuery);
|
| + } else if (name->Equals(v8::String::New("SetQueryFromAutocompleteResult"))) {
|
| + return v8::FunctionTemplate::New(SetQueryFromAutocompleteResult);
|
| + } else if (name->Equals(v8::String::New("SetPreviewHeight"))) {
|
| + return v8::FunctionTemplate::New(SetPreviewHeight);
|
| }
|
| return v8::Handle<v8::FunctionTemplate>();
|
| }
|
| @@ -155,14 +249,11 @@ content::RenderView* SearchBoxExtensionWrapper::GetRenderView() {
|
| }
|
|
|
| // static
|
| -v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetValue(
|
| +v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetQuery(
|
| const v8::Arguments& args) {
|
| content::RenderView* render_view = GetRenderView();
|
| if (!render_view) return v8::Undefined();
|
| - return v8::String::New(
|
| - reinterpret_cast<const uint16_t*>(
|
| - SearchBox::Get(render_view)->value().data()),
|
| - SearchBox::Get(render_view)->value().length());
|
| + return UTF16ToV8String(SearchBox::Get(render_view)->query());
|
| }
|
|
|
| // static
|
| @@ -222,16 +313,55 @@ v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetHeight(
|
| }
|
|
|
| // static
|
| +v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetAutocompleteResults(
|
| + const v8::Arguments& args) {
|
| + content::RenderView* render_view = GetRenderView();
|
| + if (!render_view) return v8::Undefined();
|
| + const std::vector<InstantAutocompleteResult>& results =
|
| + SearchBox::Get(render_view)->autocomplete_results();
|
| + const int results_base = SearchBox::Get(render_view)->results_base();
|
| + v8::Handle<v8::Array> results_array = v8::Array::New(results.size());
|
| + for (size_t i = 0; i < results.size(); ++i) {
|
| + v8::Handle<v8::Object> result = v8::Object::New();
|
| + result->Set(v8::String::New("provider"),
|
| + UTF16ToV8String(results[i].provider));
|
| + result->Set(v8::String::New("contents"),
|
| + UTF16ToV8String(results[i].contents));
|
| + result->Set(v8::String::New("destination_url"),
|
| + v8::String::New(results[i].destination_url.spec().c_str()));
|
| + result->Set(v8::String::New("rid"), v8::Uint32::New(results_base + i));
|
| +
|
| + v8::Handle<v8::Object> ranking_data = v8::Object::New();
|
| + ranking_data->Set(v8::String::New("relevance"),
|
| + v8::Int32::New(results[i].relevance));
|
| + result->Set(v8::String::New("rankingData"), ranking_data);
|
| +
|
| + results_array->Set(i, result);
|
| + }
|
| +
|
| + return results_array;
|
| +}
|
| +
|
| +// static
|
| +v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetKeyCode(
|
| + const v8::Arguments& args) {
|
| + content::RenderView* render_view = GetRenderView();
|
| + if (!render_view) return v8::Undefined();
|
| + return v8::Int32::New(SearchBox::Get(render_view)->key_code());
|
| +}
|
| +
|
| +// static
|
| v8::Handle<v8::Value> SearchBoxExtensionWrapper::SetSuggestions(
|
| const v8::Arguments& args) {
|
| - std::vector<string16> suggestions;
|
| - InstantCompleteBehavior behavior = INSTANT_COMPLETE_NOW;
|
| + std::vector<InstantSuggestion> suggestions;
|
|
|
| if (args.Length() && args[0]->IsObject()) {
|
| - v8::Local<v8::Object> suggestion_json = args[0]->ToObject();
|
| + v8::Handle<v8::Object> suggestion_json = args[0]->ToObject();
|
|
|
| - v8::Local<v8::Value> complete_value =
|
| - suggestion_json->Get(v8::String::New("complete_behavior"));
|
| + InstantCompleteBehavior behavior = INSTANT_COMPLETE_NOW;
|
| + InstantSuggestionType type = INSTANT_SUGGESTION_SEARCH;
|
| + v8::Handle<v8::Value> complete_value =
|
| + suggestion_json->Get(v8::String::New("complete_behavior"));
|
| if (complete_value->IsString()) {
|
| if (complete_value->Equals(v8::String::New("now"))) {
|
| behavior = INSTANT_COMPLETE_NOW;
|
| @@ -239,38 +369,182 @@ v8::Handle<v8::Value> SearchBoxExtensionWrapper::SetSuggestions(
|
| behavior = INSTANT_COMPLETE_NEVER;
|
| } else if (complete_value->Equals(v8::String::New("delayed"))) {
|
| behavior = INSTANT_COMPLETE_DELAYED;
|
| + } else if (complete_value->Equals(v8::String::New("replace"))) {
|
| + behavior = INSTANT_COMPLETE_REPLACE;
|
| } else {
|
| VLOG(1) << "Unsupported complete behavior '"
|
| << *v8::String::Utf8Value(complete_value) << "'";
|
| }
|
| }
|
| -
|
| - v8::Local<v8::Value> suggestions_field =
|
| + v8::Handle<v8::Value> suggestions_field =
|
| suggestion_json->Get(v8::String::New("suggestions"));
|
| -
|
| if (suggestions_field->IsArray()) {
|
| - v8::Local<v8::Array> suggestions_array =
|
| + v8::Handle<v8::Array> suggestions_array =
|
| suggestions_field.As<v8::Array>();
|
| -
|
| size_t length = suggestions_array->Length();
|
| for (size_t i = 0; i < length; i++) {
|
| - v8::Local<v8::Value> suggestion_value = suggestions_array->Get(i);
|
| + v8::Handle<v8::Value> suggestion_value = suggestions_array->Get(i);
|
| if (!suggestion_value->IsObject()) continue;
|
|
|
| - v8::Local<v8::Object> suggestion_object = suggestion_value->ToObject();
|
| - v8::Local<v8::Value> suggestion_object_value =
|
| + v8::Handle<v8::Object> suggestion_object = suggestion_value->ToObject();
|
| + v8::Handle<v8::Value> suggestion_object_value =
|
| suggestion_object->Get(v8::String::New("value"));
|
| if (!suggestion_object_value->IsString()) continue;
|
| + string16 text = V8ValueToUTF16(suggestion_object_value);
|
|
|
| - string16 suggestion(reinterpret_cast<char16*>(*v8::String::Value(
|
| - suggestion_object_value->ToString())));
|
| - suggestions.push_back(suggestion);
|
| + suggestions.push_back(InstantSuggestion(text, behavior, type));
|
| }
|
| }
|
| }
|
|
|
| if (content::RenderView* render_view = GetRenderView())
|
| - SearchBox::Get(render_view)->SetSuggestions(suggestions, behavior);
|
| + SearchBox::Get(render_view)->SetSuggestions(suggestions);
|
| + return v8::Undefined();
|
| +}
|
| +
|
| +// static
|
| +v8::Handle<v8::Value> SearchBoxExtensionWrapper::SetQuerySuggestion(
|
| + const v8::Arguments& args) {
|
| + if (1 <= args.Length() && args.Length() <= 2 && args[0]->IsString()) {
|
| + string16 text = V8ValueToUTF16(args[0]);
|
| + InstantCompleteBehavior behavior = INSTANT_COMPLETE_NOW;
|
| + InstantSuggestionType type = INSTANT_SUGGESTION_URL;
|
| +
|
| + if (args.Length() >= 2 && args[1]->Uint32Value() == 2) {
|
| + behavior = INSTANT_COMPLETE_NEVER;
|
| + // TODO(sreeram): The page should really set the type explicitly.
|
| + type = INSTANT_SUGGESTION_SEARCH;
|
| + }
|
| +
|
| + if (content::RenderView* render_view = GetRenderView()) {
|
| + std::vector<InstantSuggestion> suggestions;
|
| + suggestions.push_back(InstantSuggestion(text, behavior, type));
|
| + SearchBox::Get(render_view)->SetSuggestions(suggestions);
|
| + }
|
| + }
|
| + return v8::Undefined();
|
| +}
|
| +
|
| +// static
|
| +v8::Handle<v8::Value>
|
| + SearchBoxExtensionWrapper::SetQuerySuggestionFromAutocompleteResult(
|
| + const v8::Arguments& args) {
|
| + content::RenderView* render_view = GetRenderView();
|
| + if (1 <= args.Length() && args.Length() <= 2 && args[0]->IsNumber() &&
|
| + render_view) {
|
| + const int results_id = args[0]->Uint32Value();
|
| + const int results_base = SearchBox::Get(render_view)->results_base();
|
| + // Note that stale results_ids, less than the current results_base, will
|
| + // wrap.
|
| + const size_t index = results_id - results_base;
|
| + const std::vector<InstantAutocompleteResult>& suggestions =
|
| + SearchBox::Get(render_view)->autocomplete_results();
|
| + if (index < suggestions.size()) {
|
| + string16 text = UTF8ToUTF16(suggestions[index].destination_url.spec());
|
| + InstantCompleteBehavior behavior = INSTANT_COMPLETE_NOW;
|
| + InstantSuggestionType type = INSTANT_SUGGESTION_URL;
|
| +
|
| + if (args.Length() >= 2 && args[1]->Uint32Value() == 2)
|
| + behavior = INSTANT_COMPLETE_NEVER;
|
| +
|
| + if (suggestions[index].is_search) {
|
| + text = suggestions[index].contents;
|
| + type = INSTANT_SUGGESTION_SEARCH;
|
| + }
|
| +
|
| + std::vector<InstantSuggestion> suggestions;
|
| + suggestions.push_back(InstantSuggestion(text, behavior, type));
|
| + SearchBox::Get(render_view)->SetSuggestions(suggestions);
|
| + } else {
|
| + VLOG(1) << "Invalid results_id " << results_id << "; "
|
| + << "results_base is " << results_base << ".";
|
| + }
|
| + }
|
| + return v8::Undefined();
|
| +}
|
| +
|
| +// static
|
| +v8::Handle<v8::Value> SearchBoxExtensionWrapper::SetQuery(
|
| + const v8::Arguments& args) {
|
| + // TODO(sreeram): Make the second argument (type) mandatory.
|
| + if (1 <= args.Length() && args.Length() <= 2 && args[0]->IsString()) {
|
| + string16 text = V8ValueToUTF16(args[0]);
|
| + InstantCompleteBehavior behavior = INSTANT_COMPLETE_REPLACE;
|
| + InstantSuggestionType type = INSTANT_SUGGESTION_SEARCH;
|
| +
|
| + if (args.Length() >= 2 && args[1]->Uint32Value() == 1)
|
| + type = INSTANT_SUGGESTION_URL;
|
| +
|
| + if (content::RenderView* render_view = GetRenderView()) {
|
| + std::vector<InstantSuggestion> suggestions;
|
| + suggestions.push_back(InstantSuggestion(text, behavior, type));
|
| + SearchBox::Get(render_view)->SetSuggestions(suggestions);
|
| + }
|
| + }
|
| + return v8::Undefined();
|
| +}
|
| +
|
| +v8::Handle<v8::Value>
|
| + SearchBoxExtensionWrapper::SetQueryFromAutocompleteResult(
|
| + const v8::Arguments& args) {
|
| + content::RenderView* render_view = GetRenderView();
|
| + if (1 <= args.Length() && args.Length() <= 2 && args[0]->IsNumber() &&
|
| + render_view) {
|
| + const int results_id = args[0]->Uint32Value();
|
| + const int results_base = SearchBox::Get(render_view)->results_base();
|
| + // Note that stale results_ids, less than the current results_base, will
|
| + // wrap.
|
| + const size_t index = results_id - results_base;
|
| + const std::vector<InstantAutocompleteResult>& suggestions =
|
| + SearchBox::Get(render_view)->autocomplete_results();
|
| + if (index < suggestions.size()) {
|
| + string16 text = UTF8ToUTF16(suggestions[index].destination_url.spec());
|
| + InstantCompleteBehavior behavior = INSTANT_COMPLETE_REPLACE;
|
| + InstantSuggestionType type = INSTANT_SUGGESTION_URL;
|
| +
|
| + if ((args.Length() >= 2 && args[1]->Uint32Value() == 0) ||
|
| + (args.Length() < 2 && suggestions[index].is_search)) {
|
| + text = suggestions[index].contents;
|
| + type = INSTANT_SUGGESTION_SEARCH;
|
| + }
|
| +
|
| + std::vector<InstantSuggestion> suggestions;
|
| + suggestions.push_back(InstantSuggestion(text, behavior, type));
|
| + SearchBox::Get(render_view)->SetSuggestions(suggestions);
|
| + } else {
|
| + VLOG(1) << "Invalid results_id " << results_id << "; "
|
| + << "results_base is " << results_base << ".";
|
| + }
|
| + }
|
| + return v8::Undefined();
|
| +}
|
| +
|
| +// static
|
| +v8::Handle<v8::Value> SearchBoxExtensionWrapper::SetPreviewHeight(
|
| + const v8::Arguments& args) {
|
| + if (args.Length() == 1) {
|
| + int height = 0;
|
| + InstantSizeUnits units = INSTANT_SIZE_PIXELS;
|
| + if (args[0]->IsInt32()) {
|
| + height = args[0]->Int32Value();
|
| + } else if (args[0]->IsString()) {
|
| + std::string height_str = *v8::String::Utf8Value(args[0]);
|
| + std::string units_str;
|
| + SplitLeadingNumberToken(&height_str, &units_str);
|
| + if (!base::StringToInt(height_str, &height))
|
| + return v8::Undefined();
|
| + if (units_str == "%") {
|
| + units = INSTANT_SIZE_PERCENT;
|
| + } else if (!units_str.empty() && units_str != "px") {
|
| + return v8::Undefined();
|
| + }
|
| + } else {
|
| + return v8::Undefined();
|
| + }
|
| + content::RenderView* render_view = GetRenderView();
|
| + if (render_view && height >= 0)
|
| + SearchBox::Get(render_view)->SetInstantPreviewHeight(height, units);
|
| + }
|
| return v8::Undefined();
|
| }
|
|
|
| @@ -302,6 +576,16 @@ void SearchBoxExtension::DispatchResize(WebKit::WebFrame* frame) {
|
| }
|
|
|
| // static
|
| +void SearchBoxExtension::DispatchAutocompleteResults(WebKit::WebFrame* frame) {
|
| + Dispatch(frame, kDispatchAutocompleteResultsEventScript);
|
| +}
|
| +
|
| +// static
|
| +void SearchBoxExtension::DispatchKeyPress(WebKit::WebFrame* frame) {
|
| + Dispatch(frame, kDispatchKeyPressEventScript);
|
| +}
|
| +
|
| +// static
|
| bool SearchBoxExtension::PageSupportsInstant(WebKit::WebFrame* frame) {
|
| DCHECK(frame) << "PageSupportsInstant requires frame";
|
| if (!frame) return false;
|
|
|