diff options
24 files changed, 1048 insertions, 250 deletions
diff --git a/chrome/browser/instant/instant_browsertest.cc b/chrome/browser/instant/instant_browsertest.cc new file mode 100644 index 0000000..ec4fc69 --- /dev/null +++ b/chrome/browser/instant/instant_browsertest.cc @@ -0,0 +1,248 @@ +// Copyright (c) 2010 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 "base/command_line.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/autocomplete/autocomplete_edit_view.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/browser_window.h" +#include "chrome/browser/instant/instant_controller.h" +#include "chrome/browser/location_bar.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/browser/search_engines/template_url_model.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/test/in_process_browser_test.h" +#include "chrome/test/ui_test_utils.h" + + +class InstantTest : public InProcessBrowserTest { + public: + InstantTest() + : location_bar_(NULL), + preview_(NULL) { + EnableDOMAutomation(); + } + + void SetupInstantProvider(const std::string& page) { + TemplateURLModel* model = browser()->profile()->GetTemplateURLModel(); + ASSERT_TRUE(model); + + if (!model->loaded()) { + model->Load(); + ui_test_utils::WaitForNotification( + NotificationType::TEMPLATE_URL_MODEL_LOADED); + } + + ASSERT_TRUE(model->loaded()); + + // TemplateURLModel takes ownership of this. + TemplateURL* template_url = new TemplateURL(); + + std::string url = StringPrintf( + "http://%s:%d/files/instant/%s?q={searchTerms}", + test_server()->host_port_pair().host().c_str(), + test_server()->host_port_pair().port(), + page.c_str()); + template_url->SetURL(url, 0, 0); + template_url->SetInstantURL(url, 0, 0); + template_url->set_keyword(UTF8ToWide("foo")); + template_url->set_short_name(UTF8ToWide("foo")); + + model->Add(template_url); + model->SetDefaultSearchProvider(template_url); + } + + // Type a character to get instant to trigger. + void SetupLocationBar() { + location_bar_ = browser()->window()->GetLocationBar(); + ASSERT_TRUE(location_bar_); + location_bar_->location_entry()->SetUserText(L"a"); + } + + // Wait for instant to load and ensure it is in the state we expect. + void SetupPreview() { + preview_ = browser()->instant()->GetPreviewContents(); + ASSERT_TRUE(preview_); + ui_test_utils::WaitForNavigation(&preview_->controller()); + + // Verify the initial setup of the search box. + ASSERT_TRUE(browser()->instant()); + EXPECT_TRUE(browser()->instant()->IsShowingInstant()); + EXPECT_FALSE(browser()->instant()->is_active()); + + // When the page loads, the initial searchBox values are set and no events + // have been called. + EXPECT_NO_FATAL_FAILURE(CheckBoolValueFromJavascript( + true, "window.chrome.sv", preview_)); + EXPECT_NO_FATAL_FAILURE(CheckIntValueFromJavascript( + 0, "window.onsubmitcalls", preview_)); + EXPECT_NO_FATAL_FAILURE(CheckIntValueFromJavascript( + 0, "window.oncancelcalls", preview_)); + EXPECT_NO_FATAL_FAILURE(CheckIntValueFromJavascript( + 0, "window.onchangecalls", preview_)); + EXPECT_NO_FATAL_FAILURE(CheckIntValueFromJavascript( + 0, "window.onresizecalls", preview_)); + EXPECT_NO_FATAL_FAILURE(CheckStringValueFromJavascript( + "a", "window.chrome.searchBox.value", preview_)); + EXPECT_NO_FATAL_FAILURE(CheckBoolValueFromJavascript( + false, "window.chrome.searchBox.verbatim", preview_)); + } + + void SetLocationBarText(const std::wstring& text) { + ASSERT_TRUE(location_bar_); + location_bar_->location_entry()->SetUserText(text); + ui_test_utils::WaitForNotification( + NotificationType::INSTANT_CONTROLLER_SHOWN); + } + + void SendKey(app::KeyboardCode key) { + ASSERT_TRUE(ui_test_utils::SendKeyPressSync( + browser(), key, false, false, false, false)); + } + + void CheckStringValueFromJavascript( + const std::string& expected, + const std::string& function, + TabContents* tab_contents) { + std::string script = StringPrintf( + "window.domAutomationController.send(%s)", function.c_str()); + std::string result; + ASSERT_TRUE(ui_test_utils::ExecuteJavaScriptAndExtractString( + tab_contents->render_view_host(), + std::wstring(), UTF8ToWide(script), &result)); + EXPECT_EQ(expected, result); + } + + void CheckBoolValueFromJavascript( + bool expected, + const std::string& function, + TabContents* tab_contents) { + std::string script = StringPrintf( + "window.domAutomationController.send(%s)", function.c_str()); + bool result; + ASSERT_TRUE(ui_test_utils::ExecuteJavaScriptAndExtractBool( + tab_contents->render_view_host(), + std::wstring(), UTF8ToWide(script), &result)); + EXPECT_EQ(expected, result); + } + + void CheckIntValueFromJavascript( + int expected, + const std::string& function, + TabContents* tab_contents) { + std::string script = StringPrintf( + "window.domAutomationController.send(%s)", function.c_str()); + int result; + ASSERT_TRUE(ui_test_utils::ExecuteJavaScriptAndExtractInt( + tab_contents->render_view_host(), + std::wstring(), UTF8ToWide(script), &result)); + EXPECT_EQ(expected, result); + } + + protected: + virtual void SetUpCommandLine(CommandLine* command_line) { + command_line->AppendSwitch(switches::kEnablePredictiveInstant); + } + + LocationBar* location_bar_; + TabContents* preview_; +}; + +// TODO(tonyg): Add the following tests: +// 1. Test that setSuggestions() works. +// 2. Test that the search box API is not populated for pages other than the +// default search provider. +// 3. Test resize events. + +#if defined(OS_WIN) +#define MAYBE_OnChangeEvent OnChangeEvent +#else +#define MAYBE_OnChangeEvent DISABLED_OnChangeEvent +#endif +// Verify that the onchange event is dispatched upon typing in the box. +IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE_OnChangeEvent) { + ASSERT_TRUE(test_server()->Start()); + ASSERT_NO_FATAL_FAILURE(SetupInstantProvider("search.html")); + ASSERT_NO_FATAL_FAILURE(SetupLocationBar()); + ASSERT_NO_FATAL_FAILURE(SetupPreview()); + + ASSERT_NO_FATAL_FAILURE(SetLocationBarText(L"abc")); + + // Check that the value is reflected and onchange is called. + EXPECT_NO_FATAL_FAILURE(CheckStringValueFromJavascript( + "abc", "window.chrome.searchBox.value", preview_)); + EXPECT_NO_FATAL_FAILURE(CheckBoolValueFromJavascript( + false, "window.chrome.searchBox.verbatim", preview_)); + EXPECT_NO_FATAL_FAILURE(CheckIntValueFromJavascript( + 1, "window.onchangecalls", preview_)); +} + +#if defined(OS_WIN) +#define MAYBE_OnSubmitEvent OnSubmitEvent +#else +#define MAYBE_OnSubmitEvent DISABLED_OnSubmitEvent +#endif +// Verify that the onsubmit event is dispatched upon pressing enter. +IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE_OnSubmitEvent) { + ASSERT_TRUE(test_server()->Start()); + ASSERT_NO_FATAL_FAILURE(SetupInstantProvider("search.html")); + ASSERT_NO_FATAL_FAILURE(SetupLocationBar()); + ASSERT_NO_FATAL_FAILURE(SetupPreview()); + + ASSERT_NO_FATAL_FAILURE(SetLocationBarText(L"abc")); + ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_RETURN)); + + // Check that the preview contents have been committed. + ASSERT_FALSE(browser()->instant()->GetPreviewContents()); + TabContents* contents = browser()->GetSelectedTabContents(); + ASSERT_TRUE(contents); + + // Check that the value is reflected and onsubmit is called. + EXPECT_NO_FATAL_FAILURE(CheckBoolValueFromJavascript( + true, "window.chrome.sv", contents)); + EXPECT_NO_FATAL_FAILURE(CheckStringValueFromJavascript( + "abc", "window.chrome.searchBox.value", contents)); + EXPECT_NO_FATAL_FAILURE(CheckBoolValueFromJavascript( + true, "window.chrome.searchBox.verbatim", contents)); + EXPECT_NO_FATAL_FAILURE(CheckIntValueFromJavascript( + 1, "window.onsubmitcalls", contents)); +} + +#if defined(OS_WIN) +#define MAYBE_OnCancelEvent OnCancelEvent +#else +#define MAYBE_OnCancelEvent DISABLED_OnCancelEvent +#endif +// Verify that the oncancel event is dispatched upon losing focus. +IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE_OnCancelEvent) { + ASSERT_TRUE(test_server()->Start()); + ASSERT_NO_FATAL_FAILURE(SetupInstantProvider("search.html")); + ASSERT_NO_FATAL_FAILURE(SetupLocationBar()); + ASSERT_NO_FATAL_FAILURE(SetupPreview()); + + ASSERT_NO_FATAL_FAILURE(SetLocationBarText(L"abc")); + ASSERT_NO_FATAL_FAILURE(ui_test_utils::ClickOnView(browser(), + VIEW_ID_TAB_CONTAINER)); + + // Check that the preview contents have been committed. + ASSERT_FALSE(browser()->instant()->GetPreviewContents()); + TabContents* contents = browser()->GetSelectedTabContents(); + ASSERT_TRUE(contents); + + // Check that the value is reflected and oncancel is called. + EXPECT_NO_FATAL_FAILURE(CheckBoolValueFromJavascript( + true, "window.chrome.sv", contents)); + EXPECT_NO_FATAL_FAILURE(CheckStringValueFromJavascript( + "abc", "window.chrome.searchBox.value", contents)); + EXPECT_NO_FATAL_FAILURE(CheckBoolValueFromJavascript( + false, "window.chrome.searchBox.verbatim", contents)); + EXPECT_NO_FATAL_FAILURE(CheckIntValueFromJavascript( + 1, "window.oncancelcalls", contents)); +} diff --git a/chrome/browser/instant/instant_controller.cc b/chrome/browser/instant/instant_controller.cc index 8fd4746..ab6617a 100644 --- a/chrome/browser/instant/instant_controller.cc +++ b/chrome/browser/instant/instant_controller.cc @@ -241,6 +241,11 @@ void InstantController::ShowInstantLoader(InstantLoader* loader) { } else { // The loader supports instant but isn't active yet. Nothing to do. } + + NotificationService::current()->Notify( + NotificationType::INSTANT_CONTROLLER_SHOWN, + Source<InstantController>(this), + NotificationService::NoDetails()); } void InstantController::SetSuggestedTextFor(InstantLoader* loader, diff --git a/chrome/browser/instant/instant_loader.cc b/chrome/browser/instant/instant_loader.cc index b9ba2d3..aec5134 100644 --- a/chrome/browser/instant/instant_loader.cc +++ b/chrome/browser/instant/instant_loader.cc @@ -36,115 +36,19 @@ #include "ipc/ipc_message.h" namespace { - -// Script sent as the user is typing and the provider supports instant. -// Params: -// . the text the user typed. -// TODO: add support for the 2nd and 3rd params. -const char kUserInputScript[] = - "if (window.chrome.userInput) window.chrome.userInput(\"$1\", 0, 0);"; - -// 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. -const char kUserDoneScript[] = - "if (window.chrome.userWantsQuery) " - "window.chrome.userWantsQuery(\"$1\", $2);"; - -// 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). -const char kSetOmniboxBoundsScript[] = - "if (window.chrome.setDropdownDimensions) " - "window.chrome.setDropdownDimensions($1, $2, $3, $4);"; - -// We first send this script down to determine if the page supports instant. -const char kSupportsInstantScript[] = - "if (window.chrome.sv) true; else false;"; - -// If kSupportsInstantScript returns true, we then wait until -// setDropdownDimensions has been registered. This is necessary as -// 'window.chrome.sv' is set an onload time, but not setDropdownDimensions. -const char kIsDropdownDimensionRegisteredScript[] = - "if (window.chrome.setDropdownDimensions) true; else false;"; - // Number of ms to delay before updating the omnibox bounds. This is a bit long // as updating the bounds ends up being quite expensive. const int kUpdateBoundsDelayMS = 500; - -// Number of ms we delay before seeing if the page has registered the instant -// functions. -const int kRegisterDelayMS = 10; - -// Escapes quotes in the |text| so that it be passed to JavaScript as a quoted -// string. -string16 EscapeUserText(const string16& text) { - string16 escaped_text(text); - ReplaceSubstringsAfterOffset(&escaped_text, 0L, ASCIIToUTF16("\""), - ASCIIToUTF16("\\\"")); - return escaped_text; -} - -// Sends the script for when the user commits the preview. |pressed_enter| is -// true if the user pressed enter to commit. -void SendDoneScript(TabContents* tab_contents, - const string16& text, - bool pressed_enter) { - std::vector<string16> params; - params.push_back(EscapeUserText(text)); - params.push_back(pressed_enter ? ASCIIToUTF16("true") : - ASCIIToUTF16("false")); - string16 script = ReplaceStringPlaceholders(ASCIIToUTF16(kUserDoneScript), - params, - NULL); - tab_contents->render_view_host()->ExecuteJavascriptInWebFrame( - std::wstring(), - UTF16ToWide(script)); -} - -// Sends the user input script to |tab_contents|. |text| is the text the user -// input into the omnibox. -void SendUserInputScript(TabContents* tab_contents, const string16& text) { - string16 script = ReplaceStringPlaceholders(ASCIIToUTF16(kUserInputScript), - EscapeUserText(text), - NULL); - tab_contents->render_view_host()->ExecuteJavascriptInWebFrame( - std::wstring(), - UTF16ToWide(script)); -} - -// Sends the script for setting the bounds of the omnibox to |tab_contents|. -void SendOmniboxBoundsScript(TabContents* tab_contents, - const gfx::Rect& bounds) { - std::vector<string16> bounds_vector; - bounds_vector.push_back(base::IntToString16(bounds.x())); - bounds_vector.push_back(base::IntToString16(bounds.y())); - bounds_vector.push_back(base::IntToString16(bounds.width())); - bounds_vector.push_back(base::IntToString16(bounds.height())); - string16 script = ReplaceStringPlaceholders( - ASCIIToUTF16(kSetOmniboxBoundsScript), - bounds_vector, - NULL); - tab_contents->render_view_host()->ExecuteJavascriptInWebFrame( - std::wstring(), - UTF16ToWide(script)); -} - } // namespace -// FrameLoadObserver is responsible for waiting for the TabContents to finish -// loading and when done sending the necessary script down to the page. +// FrameLoadObserver is responsible for determining if the page supports +// instant after it has loaded. class InstantLoader::FrameLoadObserver : public NotificationObserver { public: - FrameLoadObserver(InstantLoader* loader, const string16& text) - : loader_(loader), - tab_contents_(loader->preview_contents()), - unique_id_(tab_contents_->controller().pending_entry()->unique_id()), + FrameLoadObserver(TabContents* tab_contents, const string16& text) + : tab_contents_(tab_contents), text_(text), - initial_text_(text), - execute_js_id_(0), - got_supports_result_(false) { + unique_id_(tab_contents_->controller().pending_entry()->unique_id()) { registrar_.Add(this, NotificationType::LOAD_COMPLETED_MAIN_FRAME, Source<TabContents>(tab_contents_)); } @@ -165,48 +69,10 @@ class InstantLoader::FrameLoadObserver : public NotificationObserver { active_entry->unique_id() != unique_id_) { return; } - - DetermineIfPageSupportsInstant(); + tab_contents_->render_view_host()->DetermineIfPageSupportsInstant( + text_); break; } - - case NotificationType::EXECUTE_JAVASCRIPT_RESULT: { - typedef std::pair<int, Value*> ExecuteDetailType; - ExecuteDetailType* result = - (static_cast<Details<ExecuteDetailType > >(details)).ptr(); - if (result->first != execute_js_id_) - return; - - DCHECK(loader_); - bool bool_result; - if (!result->second || !result->second->IsType(Value::TYPE_BOOLEAN) || - !result->second->GetAsBoolean(&bool_result)) { - DoesntSupportInstant(); - return; - } else if (!got_supports_result_) { - // First evaluation. Result tells us whether page supports instant. - if (!bool_result) { - DoesntSupportInstant(); - // WARNING: we may have been deleted. - return; - } else { - // Page supports instant, but we have to wait until page actually - // registers functions. - got_supports_result_ = true; - SendRegisterScript(); - } - } else { - // Result tells us if instant functions have been registered. - if (bool_result) { - SupportsInstant(); - } else { - // Wait a bit and ask again to see if the page supports instant. - WaitForRegister(); - } - } - return; - } - default: NOTREACHED(); break; @@ -214,83 +80,18 @@ class InstantLoader::FrameLoadObserver : public NotificationObserver { } private: - // Executes the javascript to determine if the page supports script. The - // results are passed back to us by way of NotificationObserver. - void DetermineIfPageSupportsInstant() { - DCHECK_EQ(0, execute_js_id_); - - RenderViewHost* rvh = tab_contents_->render_view_host(); - registrar_.Add(this, NotificationType::EXECUTE_JAVASCRIPT_RESULT, - Source<RenderViewHost>(rvh)); - execute_js_id_ = rvh->ExecuteJavascriptInWebFrameNotifyResult( - string16(), - ASCIIToUTF16(kSupportsInstantScript)); - } - - void WaitForRegister() { - register_timer_.Start(base::TimeDelta::FromMilliseconds(kRegisterDelayMS), - this, &FrameLoadObserver::SendRegisterScript); - } - - void SendRegisterScript() { - RenderViewHost* rvh = tab_contents_->render_view_host(); - execute_js_id_ = rvh->ExecuteJavascriptInWebFrameNotifyResult( - string16(), - ASCIIToUTF16(kIsDropdownDimensionRegisteredScript)); - } - - // Invoked when we determine the page doesn't really support instant. - void DoesntSupportInstant() { - DCHECK(loader_); - - loader_->PageDoesntSupportInstant(text_ != initial_text_); - - // WARNING: we've been deleted. - } - - // Invoked when we determine the page really supports instant. - void SupportsInstant() { - DCHECK(loader_); - - gfx::Rect bounds = loader_->GetOmniboxBoundsInTermsOfPreview(); - loader_->last_omnibox_bounds_ = loader_->omnibox_bounds_; - if (!bounds.IsEmpty()) - SendOmniboxBoundsScript(tab_contents_, bounds); - - SendUserInputScript(tab_contents_, text_); - - loader_->PageFinishedLoading(); - - // WARNING: we've been deleted. - } - - // InstantLoader that created us. - InstantLoader* loader_; - // The TabContents we're listening for changes on. TabContents* tab_contents_; - // unique_id of the NavigationEntry we're waiting on. - const int unique_id_; - // Text to send down to the page. string16 text_; - // Initial text supplied to constructor. - const string16 initial_text_; + // unique_id of the NavigationEntry we're waiting on. + const int unique_id_; // Registers and unregisters us for notifications. NotificationRegistrar registrar_; - // ID of the javascript that was executed to determine if the page supports - // instant. - int execute_js_id_; - - // Timer used to wait for JS functions to be registered. - base::OneShotTimer<FrameLoadObserver> register_timer_; - - bool got_supports_result_; - DISALLOW_COPY_AND_ASSIGN(FrameLoadObserver); }; @@ -323,11 +124,13 @@ class InstantLoader::TabContentsDelegateImpl : public TabContentsDelegate { : loader_(loader), installed_paint_observer_(false), waiting_for_new_page_(true), - is_mouse_down_from_activate_(false) { + is_mouse_down_from_activate_(false), + user_typed_before_load_(false) { } // Invoked prior to loading a new URL. void PrepareForNewLoad() { + user_typed_before_load_ = false; waiting_for_new_page_ = true; add_page_vector_.clear(); } @@ -343,6 +146,8 @@ class InstantLoader::TabContentsDelegateImpl : public TabContentsDelegate { return is_mouse_down_from_activate_; } + void set_user_typed_before_load() { user_typed_before_load_ = true; } + // Commits the currently buffered history. void CommitHistory() { TabContents* tab = loader_->preview_contents(); @@ -509,12 +314,33 @@ class InstantLoader::TabContentsDelegateImpl : public TabContentsDelegate { virtual void UpdatePreferredSize(const gfx::Size& pref_size) {} virtual void ContentTypeChanged(TabContents* source) {} - virtual void OnSetSuggestResult(int32 page_id, const std::string& result) { + virtual void OnSetSuggestions(int32 page_id, + const std::vector<std::string>& suggestions) { TabContents* source = loader_->preview_contents(); + if (!source->controller().GetActiveEntry() || + page_id != source->controller().GetActiveEntry()->page_id()) + return; + // TODO: only allow for default search provider. - if (source->controller().GetActiveEntry() && - page_id == source->controller().GetActiveEntry()->page_id()) { - loader_->SetCompleteSuggestedText(UTF8ToUTF16(result)); + // TODO(sky): Handle multiple suggestions. + if (suggestions.empty()) + loader_->SetCompleteSuggestedText(string16()); + else + loader_->SetCompleteSuggestedText(UTF8ToUTF16(suggestions[0])); + } + + virtual void OnInstantSupportDetermined(int32 page_id, bool result) { + TabContents* source = loader_->preview_contents(); + if (!source->controller().GetActiveEntry() || + page_id != source->controller().GetActiveEntry()->page_id()) + return; + + if (result) { + gfx::Rect bounds = loader_->GetOmniboxBoundsInTermsOfPreview(); + loader_->last_omnibox_bounds_ = loader_->omnibox_bounds_; + loader_->PageFinishedLoading(); + } else { + loader_->PageDoesntSupportInstant(user_typed_before_load_); } } @@ -545,9 +371,12 @@ class InstantLoader::TabContentsDelegateImpl : public TabContentsDelegate { // NEW_PAGE navigation we don't add history items to add_page_vector_. bool waiting_for_new_page_; - // Returns true if the mouse is down from an activate. + // True if the mouse is down from an activate. bool is_mouse_down_from_activate_; + // True if the user typed in the search box before the page loaded. + bool user_typed_before_load_; + DISALLOW_COPY_AND_ASSIGN(TabContentsDelegateImpl); }; @@ -612,9 +441,12 @@ void InstantLoader::Update(TabContents* tab_contents, if (is_waiting_for_load()) { // The page hasn't loaded yet. We'll send the script down when it does. frame_load_observer_->set_text(user_text_); + preview_tab_contents_delegate_->set_user_typed_before_load(); return; } - SendUserInputScript(preview_contents_.get(), user_text_); + preview_contents_->render_view_host()->SearchBoxChange( + user_text_, 0, 0); + string16 complete_suggested_text_lower = l10n_util::ToLower( complete_suggested_text_); string16 user_text_lower = l10n_util::ToLower(user_text_); @@ -638,7 +470,8 @@ void InstantLoader::Update(TabContents* tab_contents, initial_instant_url_ = url; preview_contents_->controller().LoadURL( instant_url, GURL(), transition_type); - frame_load_observer_.reset(new FrameLoadObserver(this, user_text_)); + frame_load_observer_.reset( + new FrameLoadObserver(preview_contents(), user_text_)); } } else { DCHECK(template_url_id_ == 0); @@ -678,9 +511,11 @@ TabContents* InstantLoader::ReleasePreviewContents(InstantCommitType type) { DCHECK(type == INSTANT_COMMIT_DESTROY || !frame_load_observer_.get()); if (type != INSTANT_COMMIT_DESTROY && is_showing_instant()) { - SendDoneScript(preview_contents_.get(), - user_text_, - type == INSTANT_COMMIT_PRESSED_ENTER); + if (type == INSTANT_COMMIT_FOCUS_LOST) + preview_contents_->render_view_host()->SearchBoxCancel(); + else + preview_contents_->render_view_host()->SearchBoxSubmit( + user_text_, type == INSTANT_COMMIT_PRESSED_ENTER); } omnibox_bounds_ = gfx::Rect(); last_omnibox_bounds_ = gfx::Rect(); @@ -781,15 +616,26 @@ void InstantLoader::PageFinishedLoading() { // date by the time we show it. } +// TODO(tonyg): This method only fires when the omnibox bounds change. It also +// needs to fire when the preview bounds change (e.g. open/close info bar). gfx::Rect InstantLoader::GetOmniboxBoundsInTermsOfPreview() { - if (omnibox_bounds_.IsEmpty()) - return omnibox_bounds_; - gfx::Rect preview_bounds(delegate_->GetInstantBounds()); - return gfx::Rect(omnibox_bounds_.x() - preview_bounds.x(), - omnibox_bounds_.y() - preview_bounds.y(), - omnibox_bounds_.width(), - omnibox_bounds_.height()); + gfx::Rect intersection(omnibox_bounds_.Intersect(preview_bounds)); + + // Translate into window's coordinates. + if (!intersection.IsEmpty()) { + intersection.Offset(-preview_bounds.origin().x(), + -preview_bounds.origin().y()); + } + + // In the current Chrome UI, these must always be true so they sanity check + // the above operations. In a future UI, these may be removed or adjusted. + DCHECK_EQ(0, intersection.y()); + DCHECK_LE(0, intersection.x()); + DCHECK_LE(0, intersection.width()); + DCHECK_LE(0, intersection.height()); + + return intersection; } void InstantLoader::PageDoesntSupportInstant(bool needs_reload) { @@ -812,7 +658,7 @@ void InstantLoader::ProcessBoundsChange() { last_omnibox_bounds_ = omnibox_bounds_; if (preview_contents_.get() && is_showing_instant() && !is_waiting_for_load()) { - SendOmniboxBoundsScript(preview_contents_.get(), - GetOmniboxBoundsInTermsOfPreview()); + preview_contents_->render_view_host()->SearchBoxResize( + GetOmniboxBoundsInTermsOfPreview()); } } diff --git a/chrome/browser/renderer_host/render_view_host.cc b/chrome/browser/renderer_host/render_view_host.cc index 35932c0..f5b42ef 100644 --- a/chrome/browser/renderer_host/render_view_host.cc +++ b/chrome/browser/renderer_host/render_view_host.cc @@ -889,7 +889,9 @@ void RenderViewHost::OnMessageReceived(const IPC::Message& msg) { IPC_MESSAGE_HANDLER(ViewHostMsg_WebDatabaseAccessed, OnWebDatabaseAccessed) IPC_MESSAGE_HANDLER(ViewHostMsg_FocusedNodeChanged, OnMsgFocusedNodeChanged) IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateZoomLimits, OnUpdateZoomLimits) - IPC_MESSAGE_HANDLER(ViewHostMsg_SetSuggestResult, OnSetSuggestResult) + IPC_MESSAGE_HANDLER(ViewHostMsg_SetSuggestions, OnSetSuggestions) + IPC_MESSAGE_HANDLER(ViewHostMsg_InstantSupportDetermined, + OnInstantSupportDetermined) IPC_MESSAGE_HANDLER(ViewHostMsg_DetectedPhishingSite, OnDetectedPhishingSite) IPC_MESSAGE_HANDLER(ViewHostMsg_ScriptEvalResponse, OnScriptEvalResponse) @@ -2044,6 +2046,30 @@ void RenderViewHost::DidCancelPopupMenu() { } #endif +void RenderViewHost::SearchBoxChange(const string16& value, + int selection_start, + int selection_end) { + Send(new ViewMsg_SearchBoxChange( + routing_id(), value, selection_start, selection_end)); +} + +void RenderViewHost::SearchBoxSubmit(const string16& value, + bool verbatim) { + Send(new ViewMsg_SearchBoxSubmit(routing_id(), value, verbatim)); +} + +void RenderViewHost::SearchBoxCancel() { + Send(new ViewMsg_SearchBoxCancel(routing_id())); +} + +void RenderViewHost::SearchBoxResize(const gfx::Rect& search_box_bounds) { + Send(new ViewMsg_SearchBoxResize(routing_id(), search_box_bounds)); +} + +void RenderViewHost::DetermineIfPageSupportsInstant(const string16& value) { + Send(new ViewMsg_DetermineIfPageSupportsInstant(routing_id(), value)); +} + void RenderViewHost::OnExtensionPostMessage( int port_id, const std::string& message) { if (process()->profile()->GetExtensionMessageService()) { @@ -2146,13 +2172,22 @@ void RenderViewHost::OnUpdateZoomLimits(int minimum_percent, delegate_->UpdateZoomLimits(minimum_percent, maximum_percent, remember); } -void RenderViewHost::OnSetSuggestResult(int32 page_id, - const std::string& result) { +void RenderViewHost::OnSetSuggestions( + int32 page_id, + const std::vector<std::string>& suggestions) { + RenderViewHostDelegate::BrowserIntegration* integration_delegate = + delegate_->GetBrowserIntegrationDelegate(); + if (!integration_delegate) + return; + integration_delegate->OnSetSuggestions(page_id, suggestions); +} + +void RenderViewHost::OnInstantSupportDetermined(int32 page_id, bool result) { RenderViewHostDelegate::BrowserIntegration* integration_delegate = delegate_->GetBrowserIntegrationDelegate(); if (!integration_delegate) return; - integration_delegate->OnSetSuggestResult(page_id, result); + integration_delegate->OnInstantSupportDetermined(page_id, result); } void RenderViewHost::OnDetectedPhishingSite(const GURL& phishing_url, diff --git a/chrome/browser/renderer_host/render_view_host.h b/chrome/browser/renderer_host/render_view_host.h index 6fd4cd9..47599ea 100644 --- a/chrome/browser/renderer_host/render_view_host.h +++ b/chrome/browser/renderer_host/render_view_host.h @@ -502,6 +502,16 @@ class RenderViewHost : public RenderWidgetHost { void DidCancelPopupMenu(); #endif + // SearchBox notifications. + void SearchBoxChange(const string16& value, + int selection_start, + int selection_end); + void SearchBoxSubmit(const string16& value, + bool verbatim); + void SearchBoxCancel(); + void SearchBoxResize(const gfx::Rect& search_box_bounds); + void DetermineIfPageSupportsInstant(const string16& value); + #if defined(UNIT_TEST) // These functions shouldn't be necessary outside of testing. @@ -704,7 +714,9 @@ class RenderViewHost : public RenderWidgetHost { void OnUpdateZoomLimits(int minimum_percent, int maximum_percent, bool remember); - void OnSetSuggestResult(int32 page_id, const std::string& result); + void OnSetSuggestions(int32 page_id, + const std::vector<std::string>& suggestions); + void OnInstantSupportDetermined(int32 page_id, bool result); void OnDetectedPhishingSite(const GURL& phishing_url, double phishing_score, const SkBitmap& thumbnail); diff --git a/chrome/browser/renderer_host/render_view_host_delegate.h b/chrome/browser/renderer_host/render_view_host_delegate.h index 7f62f7e..55b92a5 100644 --- a/chrome/browser/renderer_host/render_view_host_delegate.h +++ b/chrome/browser/renderer_host/render_view_host_delegate.h @@ -299,8 +299,12 @@ class RenderViewHostDelegate { TranslateErrors::Type error_type) = 0; // Notification that the page has a suggest result. - virtual void OnSetSuggestResult(int32 page_id, - const std::string& result) = 0; + virtual void OnSetSuggestions( + int32 page_id, + const std::vector<std::string>& result) = 0; + + // Notification of whether the page supports instant-style interaction. + virtual void OnInstantSupportDetermined(int32 page_id, bool result) = 0; protected: virtual ~BrowserIntegration() {} diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc index a41fed5..2fb3ccc 100644 --- a/chrome/browser/tab_contents/tab_contents.cc +++ b/chrome/browser/tab_contents/tab_contents.cc @@ -2121,9 +2121,16 @@ void TabContents::OnPageTranslated(int32 page_id, Details<PageTranslatedDetails>(&details)); } -void TabContents::OnSetSuggestResult(int32 page_id, const std::string& result) { +void TabContents::OnSetSuggestions( + int32 page_id, + const std::vector<std::string>& suggestions) { + if (delegate()) + delegate()->OnSetSuggestions(page_id, suggestions); +} + +void TabContents::OnInstantSupportDetermined(int32 page_id, bool result) { if (delegate()) - delegate()->OnSetSuggestResult(page_id, result); + delegate()->OnInstantSupportDetermined(page_id, result); } void TabContents::DidStartProvisionalLoadForFrame( diff --git a/chrome/browser/tab_contents/tab_contents.h b/chrome/browser/tab_contents/tab_contents.h index 448cb3e..64cd526 100644 --- a/chrome/browser/tab_contents/tab_contents.h +++ b/chrome/browser/tab_contents/tab_contents.h @@ -888,7 +888,9 @@ class TabContents : public PageNavigator, const std::string& original_lang, const std::string& translated_lang, TranslateErrors::Type error_type); - virtual void OnSetSuggestResult(int32 page_id, const std::string& result); + virtual void OnSetSuggestions(int32 page_id, + const std::vector<std::string>& suggestions); + virtual void OnInstantSupportDetermined(int32 page_id, bool result); // RenderViewHostDelegate::Resource implementation. virtual void DidStartProvisionalLoadForFrame(RenderViewHost* render_view_host, diff --git a/chrome/browser/tab_contents/tab_contents_delegate.cc b/chrome/browser/tab_contents/tab_contents_delegate.cc index fc52668..25fd157 100644 --- a/chrome/browser/tab_contents/tab_contents_delegate.cc +++ b/chrome/browser/tab_contents/tab_contents_delegate.cc @@ -191,8 +191,13 @@ bool TabContentsDelegate::ShouldEnablePreferredSizeNotifications() { void TabContentsDelegate::UpdatePreferredSize(const gfx::Size& pref_size) { } -void TabContentsDelegate::OnSetSuggestResult(int32 page_id, - const std::string& result) { +void TabContentsDelegate::OnSetSuggestions( + int32 page_id, + const std::vector<std::string>& suggestions) { +} + +void TabContentsDelegate::OnInstantSupportDetermined(int32 page_id, + bool result) { } void TabContentsDelegate::ContentRestrictionsChanged(TabContents* source) { diff --git a/chrome/browser/tab_contents/tab_contents_delegate.h b/chrome/browser/tab_contents/tab_contents_delegate.h index e15e934..63ef115 100644 --- a/chrome/browser/tab_contents/tab_contents_delegate.h +++ b/chrome/browser/tab_contents/tab_contents_delegate.h @@ -7,6 +7,7 @@ #pragma once #include <string> +#include <vector> #include "base/basictypes.h" #include "chrome/browser/automation/automation_resource_routing_delegate.h" @@ -305,7 +306,11 @@ class TabContentsDelegate : public AutomationResourceRoutingDelegate { virtual void UpdatePreferredSize(const gfx::Size& pref_size); // Notifies the delegate that the page has a suggest result. - virtual void OnSetSuggestResult(int32 page_id, const std::string& result); + virtual void OnSetSuggestions(int32 page_id, + const std::vector<std::string>& result); + + // Notifies the delegate whether the page supports instant-style interaction. + virtual void OnInstantSupportDetermined(int32 page_id, bool result); // Notifies the delegate that the content restrictions for this tab has // changed. diff --git a/chrome/chrome_renderer.gypi b/chrome/chrome_renderer.gypi index 063f860..d52341d 100644 --- a/chrome/chrome_renderer.gypi +++ b/chrome/chrome_renderer.gypi @@ -216,6 +216,10 @@ 'renderer/safe_browsing/scorer.h', 'renderer/search_extension.cc', 'renderer/search_extension.h', + 'renderer/searchbox.cc', + 'renderer/searchbox.h', + 'renderer/searchbox_extension.cc', + 'renderer/searchbox_extension.h', 'renderer/speech_input_dispatcher.cc', 'renderer/speech_input_dispatcher.h', 'renderer/spellchecker/spellcheck.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index e0b99dd..00737662 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -298,6 +298,7 @@ 'browser/collected_cookies_uitest.cc', 'browser/debugger/devtools_sanity_unittest.cc', 'browser/gtk/bookmark_bar_gtk_interactive_uitest.cc', + 'browser/instant/instant_browsertest.cc', 'browser/notifications/notifications_interactive_uitest.cc', 'browser/views/bookmark_bar_view_test.cc', 'browser/views/browser_keyboard_accessibility_test_win.cc', @@ -2006,9 +2007,9 @@ 'browser/first_run/first_run_browsertest.cc', 'browser/geolocation/access_token_store_browsertest.cc', 'browser/geolocation/geolocation_browsertest.cc', + 'browser/gtk/view_id_util_browsertest.cc', 'browser/history/history_browsertest.cc', 'browser/idbbindingutilities_browsertest.cc', - 'browser/gtk/view_id_util_browsertest.cc', 'browser/in_process_webkit/indexed_db_browsertest.cc', 'browser/net/cookie_policy_browsertest.cc', 'browser/net/ftp_browsertest.cc', diff --git a/chrome/common/notification_type.h b/chrome/common/notification_type.h index 943e0f5..9fdc50b 100644 --- a/chrome/common/notification_type.h +++ b/chrome/common/notification_type.h @@ -1237,6 +1237,9 @@ class NotificationType { // Sent each time the InstantController is updated. INSTANT_CONTROLLER_UPDATED, + // Sent each time the InstantController shows the InstantLoader. + INSTANT_CONTROLLER_SHOWN, + // Password Store ---------------------------------------------------------- // This notification is sent whenenever login entries stored in the password // store are changed. The detail of this notification is a list of changes diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h index 23e4ed0..14ee339d 100644 --- a/chrome/common/render_messages_internal.h +++ b/chrome/common/render_messages_internal.h @@ -828,6 +828,19 @@ IPC_BEGIN_MESSAGES(View) // Used to instruct the RenderView to send back updates to the preferred size. IPC_MESSAGE_ROUTED1(ViewMsg_EnablePreferredSizeChangedMode, int /*flags*/) + IPC_MESSAGE_ROUTED3(ViewMsg_SearchBoxChange, + string16 /*value*/, + int /*selection_start*/, + int /*selection_end*/) + IPC_MESSAGE_ROUTED2(ViewMsg_SearchBoxSubmit, + string16 /*value*/, + bool /*verbatim*/) + IPC_MESSAGE_ROUTED0(ViewMsg_SearchBoxCancel) + IPC_MESSAGE_ROUTED1(ViewMsg_SearchBoxResize, + gfx::Rect /*search_box_bounds*/) + IPC_MESSAGE_ROUTED1(ViewMsg_DetermineIfPageSupportsInstant, + string16 /*value*/) + // Used to tell the renderer not to add scrollbars with height and // width below a threshold. IPC_MESSAGE_ROUTED1(ViewMsg_DisableScrollbarsForSmallWindows, @@ -3003,9 +3016,13 @@ IPC_BEGIN_MESSAGES(ViewHost) // Suggest results ----------------------------------------------------------- - IPC_MESSAGE_ROUTED2(ViewHostMsg_SetSuggestResult, + IPC_MESSAGE_ROUTED2(ViewHostMsg_SetSuggestions, + int32 /* page_id */, + std::vector<std::string> /* suggestions */) + + IPC_MESSAGE_ROUTED2(ViewHostMsg_InstantSupportDetermined, int32 /* page_id */, - std::string /* suggest */) + bool /* result */) // Client-Side Phishing Detector --------------------------------------------- // Inform the browser that the current URL is phishing according to the diff --git a/chrome/renderer/render_thread.cc b/chrome/renderer/render_thread.cc index cde829c..a43a6ec 100644 --- a/chrome/renderer/render_thread.cc +++ b/chrome/renderer/render_thread.cc @@ -66,6 +66,7 @@ #include "chrome/renderer/renderer_webidbfactory_impl.h" #include "chrome/renderer/renderer_webkitclient_impl.h" #include "chrome/renderer/search_extension.h" +#include "chrome/renderer/searchbox_extension.h" #include "chrome/renderer/spellchecker/spellcheck.h" #include "chrome/renderer/user_script_slave.h" #include "ipc/ipc_channel_handle.h" @@ -853,6 +854,7 @@ void RenderThread::EnsureWebKitInitialized() { RegisterExtension(extensions_v8::LoadTimesExtension::Get(), false); RegisterExtension(extensions_v8::ChromeAppExtension::Get(), false); RegisterExtension(extensions_v8::ExternalExtension::Get(), false); + RegisterExtension(extensions_v8::SearchBoxExtension::Get(), false); v8::Extension* search_extension = extensions_v8::SearchExtension::Get(); // search_extension is null if not enabled. if (search_extension) diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc index 9e3bc83..0b300a1 100644 --- a/chrome/renderer/render_view.cc +++ b/chrome/renderer/render_view.cc @@ -80,6 +80,7 @@ #include "chrome/renderer/render_widget_fullscreen_pepper.h" #include "chrome/renderer/renderer_webapplicationcachehost_impl.h" #include "chrome/renderer/renderer_webstoragenamespace_impl.h" +#include "chrome/renderer/searchbox_extension.h" #include "chrome/renderer/speech_input_dispatcher.h" #include "chrome/renderer/spellchecker/spellcheck.h" #include "chrome/renderer/user_script_idle_scheduler.h" @@ -837,6 +838,12 @@ void RenderView::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER(ViewMsg_SetBackground, OnSetBackground) IPC_MESSAGE_HANDLER(ViewMsg_EnablePreferredSizeChangedMode, OnEnablePreferredSizeChangedMode) + IPC_MESSAGE_HANDLER(ViewMsg_SearchBoxChange, OnSearchBoxChange) + IPC_MESSAGE_HANDLER(ViewMsg_SearchBoxSubmit, OnSearchBoxSubmit) + IPC_MESSAGE_HANDLER(ViewMsg_SearchBoxCancel, OnSearchBoxCancel) + IPC_MESSAGE_HANDLER(ViewMsg_SearchBoxResize, OnSearchBoxResize) + IPC_MESSAGE_HANDLER(ViewMsg_DetermineIfPageSupportsInstant, + OnDetermineIfPageSupportsInstant) IPC_MESSAGE_HANDLER(ViewMsg_DisableScrollbarsForSmallWindows, OnDisableScrollbarsForSmallWindows) IPC_MESSAGE_HANDLER(ViewMsg_SetRendererPrefs, OnSetRendererPrefs) @@ -4330,9 +4337,9 @@ WebFrame* RenderView::GetChildFrame(const std::wstring& xpath) const { return frame; } -void RenderView::SetSuggestResult(const std::string& suggest) { - // Explicitly allow empty strings to be sent to the browser. - Send(new ViewHostMsg_SetSuggestResult(routing_id_, page_id_, suggest)); +void RenderView::SetSuggestions(const std::vector<std::string>& suggestions) { + // Explicitly allow empty vector to be sent to the browser. + Send(new ViewHostMsg_SetSuggestions(routing_id_, page_id_, suggestions)); } void RenderView::EvaluateScript(const string16& frame_xpath, @@ -4570,6 +4577,49 @@ void RenderView::OnEnablePreferredSizeChangedMode(int flags) { } } +void RenderView::OnSearchBoxChange(const string16& value, + int selection_start, + int selection_end) { + search_box_.value = value; + search_box_.selection_start = selection_start; + search_box_.selection_end = selection_end; + if (!webview() || !webview()->mainFrame()) + return; + extensions_v8::SearchBoxExtension::DispatchChange(webview()->mainFrame()); +} + +void RenderView::OnSearchBoxSubmit(const string16& value, bool verbatim) { + search_box_.value = value; + search_box_.verbatim = verbatim; + if (!webview() || !webview()->mainFrame()) + return; + extensions_v8::SearchBoxExtension::DispatchSubmit(webview()->mainFrame()); +} + +void RenderView::OnSearchBoxCancel() { + search_box_.verbatim = false; + if (!webview() || !webview()->mainFrame()) + return; + extensions_v8::SearchBoxExtension::DispatchCancel(webview()->mainFrame()); +} + +void RenderView::OnSearchBoxResize(const gfx::Rect& bounds) { + search_box_.x = bounds.x(); + search_box_.y = bounds.y(); + search_box_.width = bounds.width(); + search_box_.height = bounds.height(); + if (!webview() || !webview()->mainFrame()) + return; + extensions_v8::SearchBoxExtension::DispatchResize(webview()->mainFrame()); +} + +void RenderView::OnDetermineIfPageSupportsInstant(const string16& value) { + search_box_.value = value; + bool result = extensions_v8::SearchBoxExtension::PageSupportsInstant( + webview()->mainFrame()); + Send(new ViewHostMsg_InstantSupportDetermined(routing_id_, page_id_, result)); +} + void RenderView::OnDisableScrollbarsForSmallWindows( const gfx::Size& disable_scrollbar_size_limit) { disable_scrollbars_size_limit_ = disable_scrollbar_size_limit; diff --git a/chrome/renderer/render_view.h b/chrome/renderer/render_view.h index b835293..54a995f 100644 --- a/chrome/renderer/render_view.h +++ b/chrome/renderer/render_view.h @@ -32,6 +32,7 @@ #include "chrome/renderer/pepper_plugin_delegate_impl.h" #include "chrome/renderer/render_widget.h" #include "chrome/renderer/renderer_webcookiejar_impl.h" +#include "chrome/renderer/searchbox.h" #include "chrome/renderer/translate_helper.h" #include "third_party/WebKit/WebKit/chromium/public/WebConsoleMessage.h" #include "third_party/WebKit/WebKit/chromium/public/WebFileSystem.h" @@ -229,6 +230,10 @@ class RenderView : public RenderWidget, disable_scrollbars_size_limit_.height() <= height)); } + const SearchBox& searchbox() const { + return search_box_; + } + // Called from JavaScript window.external.AddSearchProvider() to add a // keyword for a provider described in the given OpenSearch document. void AddSearchProvider(const std::string& url, @@ -239,8 +244,8 @@ class RenderView : public RenderWidget, GetSearchProviderInstallState(WebKit::WebFrame* frame, const std::string& url); - // Sends ViewHostMsg_SetSuggestResult to the browser. - void SetSuggestResult(const std::string& suggest); + // Sends ViewHostMsg_SetSuggestions to the browser. + void SetSuggestions(const std::vector<std::string>& suggestions); // Evaluates a string of JavaScript in a particular frame. void EvaluateScript(const string16& frame_xpath, @@ -823,6 +828,13 @@ class RenderView : public RenderWidget, const gfx::Point& screen_pt, WebKit::WebDragOperationsMask operations_allowed); void OnEnablePreferredSizeChangedMode(int flags); + void OnSearchBoxChange(const string16& value, + int selection_start, + int selection_end); + void OnSearchBoxSubmit(const string16& value, bool verbatim); + void OnSearchBoxCancel(); + void OnSearchBoxResize(const gfx::Rect& bounds); + void OnDetermineIfPageSupportsInstant(const string16& value); void OnEnableViewSourceMode(); void OnExecuteCode(const ViewMsg_ExecuteCode_Params& params); void OnExecuteEditCommand(const std::string& name, const std::string& value); @@ -1235,6 +1247,8 @@ class RenderView : public RenderWidget, // The text selection the last time DidChangeSelection got called. std::string last_selection_; + SearchBox search_box_; + // View ---------------------------------------------------------------------- // Type of view attached with RenderView. See view_types.h diff --git a/chrome/renderer/search_extension.cc b/chrome/renderer/search_extension.cc index 1ce6296..f14cda7 100644 --- a/chrome/renderer/search_extension.cc +++ b/chrome/renderer/search_extension.cc @@ -78,7 +78,9 @@ v8::Handle<v8::Value> SearchExtensionWrapper::SetSuggestResult( RenderView* render_view = GetRenderView(); if (!render_view) return v8::Undefined(); - render_view->SetSuggestResult(std::string(*v8::String::Utf8Value(args[0]))); + std::vector<std::string> suggestions; + suggestions.push_back(std::string(*v8::String::Utf8Value(args[0]))); + render_view->SetSuggestions(suggestions); return v8::Undefined(); } diff --git a/chrome/renderer/searchbox.cc b/chrome/renderer/searchbox.cc new file mode 100644 index 0000000..6766f25 --- /dev/null +++ b/chrome/renderer/searchbox.cc @@ -0,0 +1,18 @@ +// Copyright (c) 2010 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.h" + +SearchBox::SearchBox() + : verbatim(false), + selection_start(0), + selection_end(0), + x(0), + y(0), + width(0), + height(0) { +} + +SearchBox::~SearchBox() { +} diff --git a/chrome/renderer/searchbox.h b/chrome/renderer/searchbox.h new file mode 100644 index 0000000..d09c73a --- /dev/null +++ b/chrome/renderer/searchbox.h @@ -0,0 +1,25 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_RENDERER_SEARCHBOX_H_ +#define CHROME_RENDERER_SEARCHBOX_H_ +#pragma once + +#include "base/string16.h" + +struct SearchBox { + SearchBox(); + ~SearchBox(); + + string16 value; + bool verbatim; + uint32 selection_start; + uint32 selection_end; + uint32 x; + uint32 y; + uint32 width; + uint32 height; +}; + +#endif // CHROME_RENDERER_SEARCHBOX_H_ diff --git a/chrome/renderer/searchbox_extension.cc b/chrome/renderer/searchbox_extension.cc new file mode 100644 index 0000000..40073fa --- /dev/null +++ b/chrome/renderer/searchbox_extension.cc @@ -0,0 +1,381 @@ +// Copyright (c) 2010 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 "base/command_line.h" +#include "base/string_split.h" +#include "base/stringprintf.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/renderer/render_view.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebScriptSource.h" +#include "third_party/WebKit/WebKit/chromium/public/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 kChangeEventName[] = "chrome.searchBox.onchange"; + +static const char kSubmitEventName[] = "chrome.searchBox.onsubmit"; + +static const char kCancelEventName[] = "chrome.searchBox.oncancel"; + +static const char kResizeEventName[] = "chrome.searchBox.onresize"; + +// 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. +// TODO: add support for the 2nd and 3rd params. +static const char kUserInputScript[] = + "if (window.chrome.userInput)" + " window.chrome.userInput(" + " window.chrome.searchBox.value," + " 0," + " 0);"; + +// 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 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 +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 RenderView::FromWebView(webview); +} + +// static +v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetValue( + const v8::Arguments& args) { + RenderView* render_view = GetRenderView(); + if (!render_view) return v8::Undefined(); + return v8::String::New( + reinterpret_cast<const uint16_t*>(render_view->searchbox().value.c_str()), + render_view->searchbox().value.length()); +} + +// static +v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetVerbatim( + const v8::Arguments& args) { + RenderView* render_view = GetRenderView(); + if (!render_view) return v8::Undefined(); + return v8::Boolean::New(render_view->searchbox().verbatim); +} + +// static +v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetSelectionStart( + const v8::Arguments& args) { + RenderView* render_view = GetRenderView(); + if (!render_view) return v8::Undefined(); + return v8::Int32::New(render_view->searchbox().selection_start); +} + +// static +v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetSelectionEnd( + const v8::Arguments& args) { + RenderView* render_view = GetRenderView(); + if (!render_view) return v8::Undefined(); + return v8::Int32::New(render_view->searchbox().selection_end); +} + +// static +v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetX( + const v8::Arguments& args) { + RenderView* render_view = GetRenderView(); + if (!render_view) return v8::Undefined(); + return v8::Int32::New(render_view->searchbox().x); +} + +// static +v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetY( + const v8::Arguments& args) { + RenderView* render_view = GetRenderView(); + if (!render_view) return v8::Undefined(); + return v8::Int32::New(render_view->searchbox().y); +} + +// static +v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetWidth( + const v8::Arguments& args) { + RenderView* render_view = GetRenderView(); + if (!render_view) return v8::Undefined(); + return v8::Int32::New(render_view->searchbox().width); +} + +// static +v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetHeight( + const v8::Arguments& args) { + RenderView* render_view = GetRenderView(); + if (!render_view) return v8::Undefined(); + return v8::Int32::New(render_view->searchbox().height); +} + +// static +v8::Handle<v8::Value> SearchBoxExtensionWrapper::SetSuggestions( + const v8::Arguments& args) { + if (!args.Length() || !args[0]->IsArray()) return v8::Undefined(); + + std::vector<std::string> suggestions; + v8::Array* suggestions_arg = static_cast<v8::Array*>(*args[0]); + uint32_t length = suggestions_arg->Length(); + for (uint32_t i = 0; i < length; i++) { + std::string suggestion = *v8::String::Utf8Value( + suggestions_arg->Get(v8::Integer::New(i))->ToString()); + if (!suggestion.length()) continue; + suggestions.push_back(suggestion); + } + + RenderView* render_view = GetRenderView(); + if (!render_view) return v8::Undefined(); + + render_view->SetSuggestions(suggestions); + return v8::Undefined(); +} + +// static +bool Dispatch(WebFrame* frame, const std::string& event_name) { + DCHECK(frame) << "Dispatch requires frame"; + if (!frame) return false; + + v8::HandleScope handle_scope; + v8::Local<v8::Context> context = frame->mainWorldScriptContext(); + v8::Context::Scope context_scope(context); + + v8::Local<v8::Value> value = + context->Global()->Get(v8::String::New("window")); + std::vector<std::string> components; + base::SplitStringDontTrim(event_name, '.', &components); + for (size_t i = 0; i < components.size(); ++i) { + if (!value.IsEmpty() && value->IsObject()) + value = value->ToObject()->Get(v8::String::New(components[i].c_str())); + } + if (value.IsEmpty() || !value->IsFunction()) + return false; + + v8::Local<v8::Function> function = v8::Local<v8::Function>::Cast(value); + if (function.IsEmpty()) + return false; + + function->Call(v8::Object::New(), 0, NULL); + return true; +} + +// static +void SearchBoxExtension::DispatchChange(WebFrame* frame) { + if (Dispatch(frame, kChangeEventName)) + return; + frame->executeScript(WebScriptSource(kUserInputScript)); +} + +// static +void SearchBoxExtension::DispatchSubmit(WebFrame* frame) { + if (Dispatch(frame, kSubmitEventName)) + return; + frame->executeScript(WebScriptSource(kUserDoneScript)); +} + +// static +void SearchBoxExtension::DispatchCancel(WebFrame* frame) { + if (Dispatch(frame, kCancelEventName)) + return; + frame->executeScript(WebScriptSource(kUserDoneScript)); +} + +// static +void SearchBoxExtension::DispatchResize(WebFrame* frame) { + if (Dispatch(frame, kResizeEventName)) + return; + frame->executeScript(WebScriptSource(kSetOmniboxBoundsScript)); +} + +// static +bool SearchBoxExtension::PageSupportsInstant(WebFrame* frame) { + DCHECK(frame) << "PageSupportsInstant requires frame"; + if (!frame) return false; + + bool supports_deprecated_api = frame->executeScriptAndReturnValue( + WebScriptSource(kSupportsInstantScript))->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. + static 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 diff --git a/chrome/renderer/searchbox_extension.h b/chrome/renderer/searchbox_extension.h new file mode 100644 index 0000000..61d82b7c --- /dev/null +++ b/chrome/renderer/searchbox_extension.h @@ -0,0 +1,42 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_RENDERER_SEARCHBOX_EXTENSION_H_ +#define CHROME_RENDERER_SEARCHBOX_EXTENSION_H_ +#pragma once + +#include "base/basictypes.h" + +namespace v8 { +class Extension; +} + +namespace WebKit { +class WebFrame; +} + +namespace extensions_v8 { + +// Reference implementation of the SearchBox API as described in: +// http://lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2010-October/028818.html +class SearchBoxExtension { + public: + // Returns the v8::Extension object handling searchbox bindings. Returns null + // if match-preview is not enabled. Caller takes ownership of returned object. + static v8::Extension* Get(); + + static void DispatchChange(WebKit::WebFrame* frame); + static void DispatchSubmit(WebKit::WebFrame* frame); + static void DispatchCancel(WebKit::WebFrame* frame); + static void DispatchResize(WebKit::WebFrame* frame); + + static bool PageSupportsInstant(WebKit::WebFrame* frame); + + private: + DISALLOW_COPY_AND_ASSIGN(SearchBoxExtension); +}; + +} // namespace extensions_v8 + +#endif // CHROME_RENDERER_SEARCHBOX_EXTENSION_H_ diff --git a/chrome/test/data/instant/nosearch.html b/chrome/test/data/instant/nosearch.html new file mode 100644 index 0000000..c99868c --- /dev/null +++ b/chrome/test/data/instant/nosearch.html @@ -0,0 +1,35 @@ +<html> +<body> +<h1>No instant support</h1> +<div id=log></div> +<script> +window.chrome.sv = false; + +window.onsubmitcalls = 0; +window.onchangecalls = 0; +window.oncancelcalls = 0; +window.onresizecalls = 0; + +var searchBox = window.chrome.searchBox || {}; + +window.chrome.searchBox.onsubmit = function() { + searchBox.setSuggestions(["abcdef"]); + window.onsubmitcalls++; +}; + +window.chrome.searchBox.onchange = function() { + searchBox.setSuggestions(["abcdef"]); + window.onchangecalls++; +}; + +window.chrome.searchBox.oncancel = function() { + searchBox.setSuggestions(["abcdef"]); + window.oncancelcalls++; +}; + +window.chrome.searchBox.onresize = function() { + window.onresizecalls++; +}; +</script> +</body> +</html> diff --git a/chrome/test/data/instant/search.html b/chrome/test/data/instant/search.html new file mode 100644 index 0000000..45a3269 --- /dev/null +++ b/chrome/test/data/instant/search.html @@ -0,0 +1,35 @@ +<html> +<body> +<h1>Instant</h1> +<div id=log></div> +<script> +window.chrome.sv = true; + +window.onsubmitcalls = 0; +window.onchangecalls = 0; +window.oncancelcalls = 0; +window.onresizecalls = 0; + +var searchBox = window.chrome.searchBox || {}; + +window.chrome.searchBox.onsubmit = function() { + searchBox.setSuggestions(["abcdef"]); + window.onsubmitcalls++; +}; + +window.chrome.searchBox.onchange = function() { + searchBox.setSuggestions(["abcdef"]); + window.onchangecalls++; +}; + +window.chrome.searchBox.oncancel = function() { + searchBox.setSuggestions(["abcdef"]); + window.oncancelcalls++; +}; + +window.chrome.searchBox.onresize = function() { + window.onresizecalls++; +}; +</script> +</body> +</html> |