| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/renderer/searchbox_extension.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/string_split.h" |
| #include "base/stringprintf.h" |
| #include "chrome/renderer/searchbox.h" |
| #include "content/public/renderer/render_view.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/platform/WebString.h" |
| #include "v8/include/v8.h" |
| |
| using WebKit::WebFrame; |
| using WebKit::WebScriptSource; |
| using WebKit::WebString; |
| using WebKit::WebView; |
| |
| namespace extensions_v8 { |
| |
| static const char kSearchBoxExtensionName[] = "v8/SearchBox"; |
| |
| static const char kSearchBoxExtensionScript[] = |
| "var chrome;" |
| "if (!chrome)" |
| " chrome = {};" |
| "if (!chrome.searchBox) {" |
| " chrome.searchBox = new function() {" |
| " native function GetValue();" |
| " native function GetVerbatim();" |
| " native function GetSelectionStart();" |
| " native function GetSelectionEnd();" |
| " native function GetX();" |
| " native function GetY();" |
| " native function GetWidth();" |
| " native function GetHeight();" |
| " native function SetSuggestions();" |
| " this.__defineGetter__('value', GetValue);" |
| " this.__defineGetter__('verbatim', GetVerbatim);" |
| " this.__defineGetter__('selectionStart', GetSelectionStart);" |
| " this.__defineGetter__('selectionEnd', GetSelectionEnd);" |
| " this.__defineGetter__('x', GetX);" |
| " this.__defineGetter__('y', GetY);" |
| " this.__defineGetter__('width', GetWidth);" |
| " this.__defineGetter__('height', GetHeight);" |
| " this.setSuggestions = function(text) {" |
| " SetSuggestions(text);" |
| " };" |
| " this.onchange = null;" |
| " this.onsubmit = null;" |
| " this.oncancel = null;" |
| " this.onresize = null;" |
| " };" |
| "}"; |
| |
| static const char kDispatchChangeEventScript[] = |
| "if (window.chrome &&" |
| " window.chrome.searchBox &&" |
| " window.chrome.searchBox.onchange &&" |
| " typeof window.chrome.searchBox.onchange == 'function') {" |
| " window.chrome.searchBox.onchange();" |
| " true;" |
| "}"; |
| |
| static const char kDispatchSubmitEventScript[] = |
| "if (window.chrome &&" |
| " window.chrome.searchBox &&" |
| " window.chrome.searchBox.onsubmit &&" |
| " typeof window.chrome.searchBox.onsubmit == 'function') {" |
| " window.chrome.searchBox.onsubmit();" |
| " true;" |
| "}"; |
| |
| static const char kDispatchCancelEventScript[] = |
| "if (window.chrome &&" |
| " window.chrome.searchBox &&" |
| " window.chrome.searchBox.oncancel &&" |
| " typeof window.chrome.searchBox.oncancel == 'function') {" |
| " window.chrome.searchBox.oncancel();" |
| " true;" |
| "}"; |
| |
| static const char kDispatchResizeEventScript[] = |
| "if (window.chrome &&" |
| " window.chrome.searchBox &&" |
| " window.chrome.searchBox.onresize &&" |
| " typeof window.chrome.searchBox.onresize == 'function') {" |
| " window.chrome.searchBox.onresize();" |
| " true;" |
| "}"; |
| |
| // Deprecated API support. |
| // TODO(tonyg): Remove these when they are no longer used. |
| // ---------------------------------------------------------------------------- |
| // Script sent as the user is typing and the provider supports instant. |
| // Params: |
| // . the text the user typed. |
| // '46' forces the server to give us verbatim results. |
| static const char kUserInputScript[] = |
| "if (window.chrome.userInput)" |
| " window.chrome.userInput(" |
| " window.chrome.searchBox.value," |
| " window.chrome.searchBox.verbatim ? 46 : 0," |
| " window.chrome.searchBox.selectionStart);"; |
| |
| // Script sent when the page is committed and the provider supports instant. |
| // Params: |
| // . the text the user typed. |
| // . boolean indicating if the user pressed enter to accept the text. |
| static const char kUserDoneScript[] = |
| "if (window.chrome.userWantsQuery)" |
| " window.chrome.userWantsQuery(" |
| " window.chrome.searchBox.value," |
| " window.chrome.searchBox.verbatim);"; |
| |
| // Script sent when the bounds of the omnibox changes and the provider supports |
| // instant. The params are the bounds relative to the origin of the preview |
| // (x, y, width, height). |
| static const char kSetOmniboxBoundsScript[] = |
| "if (window.chrome.setDropdownDimensions)" |
| " window.chrome.setDropdownDimensions(" |
| " window.chrome.searchBox.x," |
| " window.chrome.searchBox.y," |
| " window.chrome.searchBox.width," |
| " window.chrome.searchBox.height);"; |
| |
| // We first send this script down to determine if the page supports instant. |
| static const char kSupportsInstantScript[] = |
| "if (window.chrome.sv) true; else false;"; |
| |
| // The google.y.first array is a list of functions which are to be executed |
| // after the external JavaScript used by Google web search loads. The deprecated |
| // API requires setDropdownDimensions and userInput to be invoked after |
| // the external JavaScript loads. So if they are not already registered, we add |
| // them to the array of functions the page will execute after load. This tight |
| // coupling discourages proliferation of the deprecated API. |
| static const char kInitScript[] = |
| "(function() {" |
| "var initScript = function(){%s%s};" |
| "if (window.chrome.setDropdownDimensions)" |
| " initScript();" |
| "else if (window.google && window.google.y)" |
| " window.google.y.first.push(initScript);" |
| "})();"; |
| // ---------------------------------------------------------------------------- |
| |
| class SearchBoxExtensionWrapper : public v8::Extension { |
| public: |
| SearchBoxExtensionWrapper(); |
| |
| // Allows v8's javascript code to call the native functions defined |
| // in this class for window.chrome. |
| virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction( |
| v8::Handle<v8::String> name); |
| |
| // Helper function to find the RenderView. May return NULL. |
| static content::RenderView* GetRenderView(); |
| |
| // Gets the value of the user's search query. |
| static v8::Handle<v8::Value> GetValue(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 |
| // forward delete, or in other cases where Chrome overrides. |
| static v8::Handle<v8::Value> GetVerbatim(const v8::Arguments& args); |
| |
| // Gets the start of the selection in the search box. |
| static v8::Handle<v8::Value> GetSelectionStart(const v8::Arguments& args); |
| |
| // Gets the end of the selection in the search box. |
| static v8::Handle<v8::Value> GetSelectionEnd(const v8::Arguments& args); |
| |
| // Gets the x coordinate (relative to |window|) of the left edge of the |
| // region of the search box that overlaps the window. |
| static v8::Handle<v8::Value> GetX(const v8::Arguments& args); |
| |
| // Gets the y coordinate (relative to |window|) of the right edge of the |
| // region of the search box that overlaps the window. |
| static v8::Handle<v8::Value> GetY(const v8::Arguments& args); |
| |
| // Gets the width of the region of the search box that overlaps the window. |
| static v8::Handle<v8::Value> GetWidth(const v8::Arguments& args); |
| |
| // Gets the height of the region of the search box that overlaps the window. |
| static v8::Handle<v8::Value> GetHeight(const v8::Arguments& args); |
| |
| // Sets ordered suggestions. Valid for current |value|. |
| static v8::Handle<v8::Value> SetSuggestions(const v8::Arguments& args); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SearchBoxExtensionWrapper); |
| }; |
| |
| SearchBoxExtensionWrapper::SearchBoxExtensionWrapper() |
| : v8::Extension(kSearchBoxExtensionName, kSearchBoxExtensionScript) {} |
| |
| v8::Handle<v8::FunctionTemplate> SearchBoxExtensionWrapper::GetNativeFunction( |
| v8::Handle<v8::String> name) { |
| if (name->Equals(v8::String::New("GetValue"))) { |
| return v8::FunctionTemplate::New(GetValue); |
| } else if (name->Equals(v8::String::New("GetVerbatim"))) { |
| return v8::FunctionTemplate::New(GetVerbatim); |
| } else if (name->Equals(v8::String::New("GetSelectionStart"))) { |
| return v8::FunctionTemplate::New(GetSelectionStart); |
| } else if (name->Equals(v8::String::New("GetSelectionEnd"))) { |
| return v8::FunctionTemplate::New(GetSelectionEnd); |
| } else if (name->Equals(v8::String::New("GetX"))) { |
| return v8::FunctionTemplate::New(GetX); |
| } else if (name->Equals(v8::String::New("GetY"))) { |
| return v8::FunctionTemplate::New(GetY); |
| } else if (name->Equals(v8::String::New("GetWidth"))) { |
| 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("SetSuggestions"))) { |
| return v8::FunctionTemplate::New(SetSuggestions); |
| } |
| return v8::Handle<v8::FunctionTemplate>(); |
| } |
| |
| // static |
| content::RenderView* SearchBoxExtensionWrapper::GetRenderView() { |
| WebFrame* webframe = WebFrame::frameForEnteredContext(); |
| DCHECK(webframe) << "There should be an active frame since we just got " |
| "a native function called."; |
| if (!webframe) return NULL; |
| |
| WebView* webview = webframe->view(); |
| if (!webview) return NULL; // can happen during closing |
| |
| return content::RenderView::FromWebView(webview); |
| } |
| |
| // static |
| v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetValue( |
| 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().c_str()), |
| SearchBox::Get(render_view)->value().length()); |
| } |
| |
| // static |
| v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetVerbatim( |
| const v8::Arguments& args) { |
| content::RenderView* render_view = GetRenderView(); |
| if (!render_view) return v8::Undefined(); |
| return v8::Boolean::New(SearchBox::Get(render_view)->verbatim()); |
| } |
| |
| // static |
| v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetSelectionStart( |
| const v8::Arguments& args) { |
| content::RenderView* render_view = GetRenderView(); |
| if (!render_view) return v8::Undefined(); |
| return v8::Int32::New(SearchBox::Get(render_view)->selection_start()); |
| } |
| |
| // static |
| v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetSelectionEnd( |
| const v8::Arguments& args) { |
| content::RenderView* render_view = GetRenderView(); |
| if (!render_view) return v8::Undefined(); |
| return v8::Int32::New(SearchBox::Get(render_view)->selection_end()); |
| } |
| |
| // static |
| v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetX( |
| const v8::Arguments& args) { |
| content::RenderView* render_view = GetRenderView(); |
| if (!render_view) return v8::Undefined(); |
| return v8::Int32::New(SearchBox::Get(render_view)->GetRect().x()); |
| } |
| |
| // static |
| v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetY( |
| const v8::Arguments& args) { |
| content::RenderView* render_view = GetRenderView(); |
| if (!render_view) return v8::Undefined(); |
| return v8::Int32::New(SearchBox::Get(render_view)->GetRect().y()); |
| } |
| |
| // static |
| v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetWidth( |
| const v8::Arguments& args) { |
| content::RenderView* render_view = GetRenderView(); |
| if (!render_view) return v8::Undefined(); |
| return v8::Int32::New(SearchBox::Get(render_view)->GetRect().width()); |
| } |
| |
| // static |
| v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetHeight( |
| const v8::Arguments& args) { |
| content::RenderView* render_view = GetRenderView(); |
| if (!render_view) return v8::Undefined(); |
| return v8::Int32::New(SearchBox::Get(render_view)->GetRect().height()); |
| } |
| |
| // Accepts a single argument in form: |
| // { |
| // suggestions: [ |
| // { |
| // value: "..." |
| // } |
| // ] |
| // } |
| // static |
| v8::Handle<v8::Value> SearchBoxExtensionWrapper::SetSuggestions( |
| const v8::Arguments& args) { |
| std::vector<std::string> suggestions; |
| InstantCompleteBehavior behavior = INSTANT_COMPLETE_NOW; |
| |
| if (args.Length() && args[0]->IsArray()) { |
| // For backwards compatibility, also accept an array of strings. |
| // TODO(tonyg): Remove this when it is confirmed to be unused. |
| v8::Local<v8::Array> suggestions_array = |
| v8::Local<v8::Array>::Cast(args[0]); |
| uint32_t length = suggestions_array->Length(); |
| for (uint32_t i = 0; i < length; i++) { |
| std::string suggestion = *v8::String::Utf8Value( |
| suggestions_array->Get(v8::Integer::New(i))->ToString()); |
| if (!suggestion.length()) continue; |
| suggestions.push_back(suggestion); |
| } |
| } else if (args.Length() && args[0]->IsObject()) { |
| // Standard version, object argument. |
| v8::Local<v8::Object> suggestion_json = |
| v8::Local<v8::Object>::Cast(args[0]); |
| 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>(); |
| |
| uint32_t length = suggestions_array->Length(); |
| for (uint32_t i = 0; i < length; i++) { |
| v8::Local<v8::Value> suggestion_value = |
| suggestions_array->Get(v8::Integer::New(i)); |
| if (!suggestion_value->IsObject()) continue; |
| |
| v8::Local<v8::Object> suggestion_object = |
| suggestion_value.As<v8::Object>(); |
| v8::Local<v8::Value> suggestion_object_value = |
| suggestion_object->Get(v8::String::New("value")); |
| if (!suggestion_object_value->IsString()) continue; |
| |
| std::string suggestion = *v8::String::Utf8Value( |
| suggestion_object_value->ToString()); |
| if (!suggestion.length()) continue; |
| suggestions.push_back(suggestion); |
| } |
| } |
| if (suggestion_json->Has(v8::String::New("complete_behavior"))) { |
| v8::Local<v8::Value> complete_value = |
| suggestion_json->Get(v8::String::New("complete_behavior")); |
| if (complete_value->IsString()) { |
| if (complete_value->Equals(v8::String::New("never"))) |
| behavior = INSTANT_COMPLETE_NEVER; |
| else if (complete_value->Equals(v8::String::New("delayed"))) |
| behavior = INSTANT_COMPLETE_DELAYED; |
| } |
| } |
| } |
| |
| if (content::RenderView* render_view = GetRenderView()) |
| SearchBox::Get(render_view)->SetSuggestions(suggestions, behavior); |
| return v8::Undefined(); |
| } |
| |
| // static |
| void Dispatch(WebFrame* frame, |
| WebString event_dispatch_script, |
| WebString no_event_handler_script) { |
| DCHECK(frame) << "Dispatch requires frame"; |
| if (!frame) |
| return; |
| |
| v8::Handle<v8::Value> result = frame->executeScriptAndReturnValue( |
| WebScriptSource(event_dispatch_script)); |
| if (result.IsEmpty() || result->IsUndefined() || result->IsNull() || |
| result->IsFalse()) { |
| frame->executeScript(WebScriptSource(no_event_handler_script)); |
| } |
| } |
| |
| // static |
| void SearchBoxExtension::DispatchChange(WebFrame* frame) { |
| Dispatch(frame, kDispatchChangeEventScript, kUserInputScript); |
| } |
| |
| // static |
| void SearchBoxExtension::DispatchSubmit(WebFrame* frame) { |
| Dispatch(frame, kDispatchSubmitEventScript, kUserDoneScript); |
| } |
| |
| // static |
| void SearchBoxExtension::DispatchCancel(WebFrame* frame) { |
| Dispatch(frame, kDispatchCancelEventScript, kUserDoneScript); |
| } |
| |
| // static |
| void SearchBoxExtension::DispatchResize(WebFrame* frame) { |
| Dispatch(frame, kDispatchResizeEventScript, kSetOmniboxBoundsScript); |
| } |
| |
| // static |
| bool SearchBoxExtension::PageSupportsInstant(WebFrame* frame) { |
| DCHECK(frame) << "PageSupportsInstant requires frame"; |
| if (!frame) return false; |
| |
| v8::Handle<v8::Value> v = frame->executeScriptAndReturnValue( |
| WebScriptSource(kSupportsInstantScript)); |
| bool supports_deprecated_api = !v.IsEmpty() && v->BooleanValue(); |
| // TODO(tonyg): Add way of detecting instant support to SearchBox API. |
| bool supports_searchbox_api = supports_deprecated_api; |
| |
| // The deprecated API needs to notify the page of events it may have missed. |
| // This isn't necessary in the SearchBox API, since the page can query the |
| // API at any time. |
| CR_DEFINE_STATIC_LOCAL(std::string, init_script, |
| (StringPrintf(kInitScript, kSetOmniboxBoundsScript, kUserInputScript))); |
| if (supports_deprecated_api) { |
| frame->executeScript(WebScriptSource(WebString::fromUTF8(init_script))); |
| } |
| |
| return supports_searchbox_api || supports_deprecated_api; |
| } |
| |
| // static |
| v8::Extension* SearchBoxExtension::Get() { |
| return new SearchBoxExtensionWrapper(); |
| } |
| |
| } // namespace extensions_v8 |