Chromium Code Reviews| 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..2fba2e1766863541b0b36eb679fb387188394ec7 100644 |
| --- a/chrome/renderer/searchbox/searchbox_extension.cc |
| +++ b/chrome/renderer/searchbox/searchbox_extension.cc |
| @@ -4,14 +4,51 @@ |
| #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 String to a UTF16 string16. |
| +// This code also exists in net/proxy/proxy_resolver_v8.cc. |
| +// TODO(shishir): Find a common place for this function. |
| +string16 V8StringToUTF16(v8::Handle<v8::String> s) { |
| + int len = s->Length(); |
| + string16 result; |
| + // Note that the reinterpret cast is because on Windows string16 is an alias |
| + // to wstring, and hence has character type wchar_t not uint16_t. |
| + if (len > 0) |
| + s->Write(reinterpret_cast<uint16_t*>(WriteInto(&result, len + 1)), 0, len); |
|
sreeram
2012/08/13 22:40:56
How about this instead?
string16 V8ValueToUTF16(v8
Shishir
2012/08/13 23:24:36
Done with minor modifications.
|
| + return result; |
| +} |
| + |
| +} // namespace |
| + |
| namespace extensions_v8 { |
| static const char kSearchBoxExtensionName[] = "v8/SearchBox"; |
| @@ -63,6 +100,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 +135,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 +162,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 +200,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 +216,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 +251,14 @@ 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()); |
| + SearchBox::Get(render_view)->query().data()), |
| + SearchBox::Get(render_view)->query().length()); |
|
sreeram
2012/08/13 22:40:56
This can be simplified to:
return UTF16ToV8String(
Shishir
2012/08/13 23:24:36
Done.
|
| } |
| // static |
| @@ -222,16 +318,61 @@ 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(); |
| + const string16& provider = results[i].provider; |
| + result->Set( |
| + v8::String::New("provider"), |
| + v8::String::New(reinterpret_cast<const uint16_t*>(provider.data()), |
| + provider.length())); |
| + const string16& contents = results[i].contents; |
| + result->Set( |
| + v8::String::New("contents"), |
| + v8::String::New(reinterpret_cast<const uint16_t*>(contents.data()), |
| + contents.length())); |
|
sreeram
2012/08/13 22:40:56
These can be simplified to:
result->Set(v8::Stri
Shishir
2012/08/13 23:24:36
Done.
|
| + 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(); |
|
sreeram
2012/08/13 22:40:56
I'm not sure why we refer to v8::Local explicitly,
Shishir
2012/08/13 23:24:36
Done.
|
| + InstantCompleteBehavior behavior = INSTANT_COMPLETE_NOW; |
| + InstantSuggestionType type = INSTANT_SUGGESTION_SEARCH; |
| v8::Local<v8::Value> complete_value = |
| - suggestion_json->Get(v8::String::New("complete_behavior")); |
| + 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,19 +380,18 @@ 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 = |
| suggestion_json->Get(v8::String::New("suggestions")); |
| - |
| if (suggestions_field->IsArray()) { |
| v8::Local<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); |
| @@ -261,16 +401,161 @@ v8::Handle<v8::Value> SearchBoxExtensionWrapper::SetSuggestions( |
| v8::Local<v8::Value> suggestion_object_value = |
| suggestion_object->Get(v8::String::New("value")); |
| if (!suggestion_object_value->IsString()) continue; |
| + string16 text = V8StringToUTF16(suggestion_object_value->ToString()); |
|
sreeram
2012/08/13 22:40:56
This can be simplified to:
string16 text = V8Value
Shishir
2012/08/13 23:24:36
Done.
|
| - 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 = V8StringToUTF16(args[0]->ToString()); |
| + 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 = V8StringToUTF16(args[0]->ToString()); |
| + 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 +587,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; |