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; |