diff options
author | sreeram@chromium.org <sreeram@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-09 15:27:05 +0000 |
---|---|---|
committer | sreeram@chromium.org <sreeram@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-09 15:27:05 +0000 |
commit | c55e3b884d3ff31f14db933c1b5ff2164b446af6 (patch) | |
tree | 7be3803c0a7c5860a9ab96e8c244d919592fd3ee | |
parent | 12c3ee69a3c5cd105f8c2833b8c4082b38d098e3 (diff) | |
download | chromium_src-c55e3b884d3ff31f14db933c1b5ff2164b446af6.zip chromium_src-c55e3b884d3ff31f14db933c1b5ff2164b446af6.tar.gz chromium_src-c55e3b884d3ff31f14db933c1b5ff2164b446af6.tar.bz2 |
Remove Instant v1 API.
In the process, refactors the Instant code so that all logic is
centralized in the InstantController. InstantLoader now merely
forwards messages between InstantController and the renderer.
BUG=none
R=sky@chromium.org
TEST=Instant works as before when enabled from chrome://settings/.
Review URL: https://chromiumcodereview.appspot.com/10836031
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@150795 0039d316-1c4b-4281-b951-d872f2087c98
43 files changed, 1800 insertions, 3002 deletions
diff --git a/chrome/browser/instant/instant_browsertest.cc b/chrome/browser/instant/instant_browsertest.cc index 58b6207..1c210af 100644 --- a/chrome/browser/instant/instant_browsertest.cc +++ b/chrome/browser/instant/instant_browsertest.cc @@ -3,20 +3,18 @@ // found in the LICENSE file. #include "base/command_line.h" -#include "base/string_util.h" #include "base/stringprintf.h" -#include "base/utf_string_conversions.h" #include "chrome/browser/content_settings/host_content_settings_map.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/history/history_service_factory.h" #include "chrome/browser/instant/instant_controller.h" #include "chrome/browser/instant/instant_loader.h" #include "chrome/browser/prefs/pref_service.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/search_engines/template_url.h" #include "chrome/browser/search_engines/template_url_service.h" #include "chrome/browser/search_engines/template_url_service_factory.h" #include "chrome/browser/task_manager/task_manager.h" #include "chrome/browser/task_manager/task_manager_browsertest_util.h" -#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_instant_controller.h" #include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/browser/ui/browser_window.h" @@ -27,819 +25,803 @@ #include "chrome/common/chrome_notification_types.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" -#include "chrome/common/url_constants.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/ui_test_utils.h" -#include "content/public/browser/navigation_controller.h" #include "content/public/browser/notification_service.h" -#include "content/public/browser/render_view_host.h" -#include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" -#include "content/public/common/content_switches.h" #include "content/public/test/browser_test_utils.h" #include "grit/generated_resources.h" #include "ui/base/l10n/l10n_util.h" -using content::WebContents; - -// Tests are flaky on Linux because of http://crbug.com/80118. -#if defined(OS_LINUX) && !defined(USE_ASH) -#define MAYBE(TestName) DISABLED_ ## TestName -#elif defined(OS_WIN) -#define MAYBE(TestName) FLAKY_ ## TestName -#else -#define MAYBE(TestName) TestName -#endif - class InstantTest : public InProcessBrowserTest { - public: - InstantTest() {} - - void EnableInstant() { - InstantController::Enable(browser()->profile()); + protected: + virtual void SetUpCommandLine(CommandLine* command_line) { + // Do not prelaunch the GPU process because it will show up in the task + // manager, but whether it appears before or after the Instant tab is not + // well defined. This affects the TaskManagerPrefix test below. + command_line->AppendSwitch(switches::kDisableGpuProcessPrelaunch); } - void SetupInstantProvider(const std::string& page) { - Profile* profile = browser()->profile(); - TemplateURLService* model = - TemplateURLServiceFactory::GetForProfile(profile); + void SetupInstant(const std::string& page) { + ASSERT_TRUE(test_server()->Start()); - content::WindowedNotificationObserver observer( - chrome::NOTIFICATION_TEMPLATE_URL_SERVICE_LOADED, - content::NotificationService::AllSources()); - if (!model->loaded()) { - model->Load(); - observer.Wait(); - } + TemplateURLService* service = + TemplateURLServiceFactory::GetForProfile(browser()->profile()); + ui_test_utils::WaitForTemplateURLServiceToLoad(service); TemplateURLData data; - data.short_name = ASCIIToUTF16("foo"); - data.SetKeyword(ASCIIToUTF16("foo")); data.SetURL(base::StringPrintf("http://%s:%d/files/%s?q={searchTerms}", test_server()->host_port_pair().host().c_str(), test_server()->host_port_pair().port(), page.c_str())); data.instant_url = data.url(); - // TemplateURLService takes ownership of this. - TemplateURL* template_url = new TemplateURL(profile, data); - model->Add(template_url); - model->SetDefaultSearchProvider(template_url); - } - // Type a character to get instant to trigger and determine instant support. - void DetermineInstantSupport() { - content::WindowedNotificationObserver observer( - chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED, - content::NotificationService::AllSources()); - // "a" triggers the "about:" provider. "b" begins the "bing.com" keyword. - // "c" might someday trigger a "chrome:" provider. - omnibox()->SetUserText(ASCIIToUTF16("d")); - observer.Wait(); - } - - // Types "def" into the omnibox and waits for the preview to be shown. - void SearchAndWaitForPreviewToShow() { - content::WindowedNotificationObserver observer( - chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN, - content::NotificationService::AllSources()); - omnibox()->SetUserText(ASCIIToUTF16("def")); - observer.Wait(); - } - - // Sends a message to the renderer and waits for the response to come back to - // the browser. Returns true on success. - bool WaitForMessageToBeProcessedByRenderer() { - bool result = false; - return GetBoolFromJavascript(preview()->web_contents(), "true", &result) && - result; - } + TemplateURL* template_url = new TemplateURL(browser()->profile(), data); + service->Add(template_url); // Takes ownership of |template_url|. + service->SetDefaultSearchProvider(template_url); - InstantController* instant() const { - return browser()->instant_controller()->instant(); + browser()->profile()->GetPrefs()->SetBoolean(prefs::kInstantEnabled, true); } OmniboxView* omnibox() const { return browser()->window()->GetLocationBar()->GetLocationEntry(); } - TabContents* preview() const { - return instant()->GetPreviewContents(); - } - - InstantLoader* loader() const { - return instant()->loader_.get(); + InstantController* instant() const { + return browser()->instant_controller()->instant(); } - std::string GetSuggestion() const { - return UTF16ToUTF8(loader()->complete_suggested_text_); + void WaitFor(chrome::NotificationType notification_type) const { + content::WindowedNotificationObserver observer( + notification_type, content::NotificationService::AllSources()); + observer.Wait(); } - bool PressEnter() { - return ui_test_utils::SendKeyPressSync( - browser(), ui::VKEY_RETURN, false, false, false, false); + std::wstring WrapScript(const std::string& script) const { + return UTF8ToWide("domAutomationController.send(" + script + ")"); } - bool SetSuggestionsJavascriptArgument(const std::string& argument) { - std::wstring script = UTF8ToWide(base::StringPrintf( - "window.setSuggestionsArgument = %s;", argument.c_str())); - content::RenderViewHost* rvh = - preview()->web_contents()->GetRenderViewHost(); - return content::ExecuteJavaScript(rvh, std::wstring(), script); + bool GetBoolFromJS(content::RenderViewHost* rvh, + const std::string& script, + bool* result) const WARN_UNUSED_RESULT { + return content::ExecuteJavaScriptAndExtractBool( + rvh, std::wstring(), WrapScript(script), result); } - std::wstring WrapScript(const std::string& script) { - return UTF8ToWide(base::StringPrintf( - "window.domAutomationController.send(%s)", script.c_str())); + bool GetIntFromJS(content::RenderViewHost* rvh, + const std::string& script, + int* result) const WARN_UNUSED_RESULT { + return content::ExecuteJavaScriptAndExtractInt( + rvh, std::wstring(), WrapScript(script), result); } - bool GetStringFromJavascript(WebContents* tab, - const std::string& script, - std::string* result) { + bool GetStringFromJS(content::RenderViewHost* rvh, + const std::string& script, + std::string* result) const WARN_UNUSED_RESULT { return content::ExecuteJavaScriptAndExtractString( - tab->GetRenderViewHost(), std::wstring(), WrapScript(script), result); + rvh, std::wstring(), WrapScript(script), result); } - bool GetIntFromJavascript(WebContents* tab, - const std::string& script, - int* result) { - return content::ExecuteJavaScriptAndExtractInt( - tab->GetRenderViewHost(), std::wstring(), WrapScript(script), result); + bool UpdateSearchState(TabContents* tab) WARN_UNUSED_RESULT { + content::RenderViewHost* rvh = tab->web_contents()->GetRenderViewHost(); + return GetIntFromJS(rvh, "onchangecalls", &onchangecalls_) && + GetIntFromJS(rvh, "onsubmitcalls", &onsubmitcalls_) && + GetIntFromJS(rvh, "oncancelcalls", &oncancelcalls_) && + GetIntFromJS(rvh, "onresizecalls", &onresizecalls_) && + GetStringFromJS(rvh, "value", &value_) && + GetBoolFromJS(rvh, "verbatim", &verbatim_) && + GetIntFromJS(rvh, "height", &height_); } - bool GetBoolFromJavascript(WebContents* tab, - const std::string& script, - bool* result) { - return content::ExecuteJavaScriptAndExtractBool( - tab->GetRenderViewHost(), std::wstring(), WrapScript(script), result); + bool ExecuteScript(const std::string& script) const WARN_UNUSED_RESULT { + return content::ExecuteJavaScript( + instant()->GetPreviewContents()->web_contents()->GetRenderViewHost(), + std::wstring(), ASCIIToWide(script)); } - bool CheckVisibilityIs(WebContents* tab, bool visible) { - bool hidden = visible; - return GetBoolFromJavascript(tab, "document.webkitHidden", &hidden) && - hidden != visible; + void SetOmniboxText(const std::string& text) const { + omnibox()->SetUserText(ASCIIToUTF16(text)); } - // Returns the state of the search box as a string. This consists of the - // following: - // window.chrome.sv - // window.onsubmitcalls - // window.oncancelcalls - // window.onchangecalls - // 'true' if any window.onresize call has been sent, otherwise false. - // window.beforeLoadSearchBox.value - // window.beforeLoadSearchBox.verbatim - // window.chrome.searchBox.value - // window.chrome.searchBox.verbatim - // window.chrome.searchBox.selectionStart - // window.chrome.searchBox.selectionEnd - // If determining any of the values fails, the value is 'fail'. - // - // If |use_last| is true, then the last searchBox values are used instead of - // the current. Set |use_last| to true when testing OnSubmit/OnCancel. - std::string GetSearchStateAsString(WebContents* tab, bool use_last) { - bool sv = false; - int onsubmitcalls = 0; - int oncancelcalls = 0; - int onchangecalls = 0; - int onresizecalls = 0; - int selection_start = 0; - int selection_end = 0; - std::string before_load_value; - bool before_load_verbatim = false; - std::string value; - bool verbatim = false; - - if (!GetBoolFromJavascript(tab, "window.chrome.sv", &sv) || - !GetIntFromJavascript(tab, "window.onsubmitcalls", &onsubmitcalls) || - !GetIntFromJavascript(tab, "window.oncancelcalls", &oncancelcalls) || - !GetIntFromJavascript(tab, "window.onchangecalls", &onchangecalls) || - !GetIntFromJavascript(tab, "window.onresizecalls", &onresizecalls) || - !GetStringFromJavascript(tab, "window.beforeLoadSearchBox.value", - &before_load_value) || - !GetBoolFromJavascript(tab, "window.beforeLoadSearchBox.verbatim", - &before_load_verbatim)) { - return "fail"; - } - - if (use_last && - (!GetStringFromJavascript(tab, "window.lastSearchBox.value", &value) || - !GetBoolFromJavascript(tab, "window.lastSearchBox.verbatim", - &verbatim) || - !GetIntFromJavascript(tab, "window.lastSearchBox.selectionStart", - &selection_start) || - !GetIntFromJavascript(tab, "window.lastSearchBox.selectionEnd", - &selection_end))) { - return "fail"; - } - - if (!use_last && - (!GetStringFromJavascript(tab, "window.chrome.searchBox.value", - &value) || - !GetBoolFromJavascript(tab, "window.chrome.searchBox.verbatim", - &verbatim) || - !GetIntFromJavascript(tab, "window.chrome.searchBox.selectionStart", - &selection_start) || - !GetIntFromJavascript(tab, "window.chrome.searchBox.selectionEnd", - &selection_end))) { - return "fail"; - } - - return base::StringPrintf("%s %d %d %d %s %s %s %s %s %d %d", - sv ? "true" : "false", - onsubmitcalls, - oncancelcalls, - onchangecalls, - onresizecalls ? "true" : "false", - before_load_value.c_str(), - before_load_verbatim ? "true" : "false", - value.c_str(), - verbatim ? "true" : "false", - selection_start, - selection_end); + bool CheckVisibilityIs(TabContents* tab, + bool expected) const WARN_UNUSED_RESULT { + bool actual = !expected; // Purposely start with a mis-match. + // We can only use ASSERT_*() in a method that returns void, hence this + // convoluted check. + return GetBoolFromJS(tab->web_contents()->GetRenderViewHost(), + "!document.webkitHidden", &actual) && + actual == expected; } - protected: - virtual void SetUpCommandLine(CommandLine* command_line) { - // Do not prelaunch the GPU process for these tests because it will show - // up in task manager but whether it appears before or after the new tab - // renderer process is not well defined. - command_line->AppendSwitch(switches::kDisableGpuProcessPrelaunch); - } -}; + int onchangecalls_; + int onsubmitcalls_; + int oncancelcalls_; + int onresizecalls_; -// TODO(tonyg): Add the following tests: -// - Test that the search box API is not populated for pages other than the -// default search provider. -// - Test resize events. - -// 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()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); - SearchAndWaitForPreviewToShow(); - - EXPECT_TRUE(preview()); - EXPECT_TRUE(instant()->is_displayable()); - EXPECT_TRUE(instant()->IsCurrent()); - EXPECT_EQ("defghi", UTF16ToUTF8(omnibox()->GetText())); - - // Make sure the URL that will get committed when we press <Enter> matches - // that of the default search provider. - const TemplateURL* default_turl = - TemplateURLServiceFactory::GetForProfile(browser()->profile())-> - GetDefaultSearchProvider(); - EXPECT_TRUE(default_turl); - EXPECT_EQ(default_turl->url_ref().ReplaceSearchTerms( - TemplateURLRef::SearchTermsArgs(ASCIIToUTF16("defghi"))), - loader()->url().spec()); - - // Check that the value is reflected and onchange is called. - EXPECT_EQ("true 0 0 1 true d false def false 3 3", - GetSearchStateAsString(preview()->web_contents(), false)); -} + std::string value_; + bool verbatim_; + int height_; +}; -// Verify that the onsubmit event is dispatched upon pressing <Enter>. -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(OnSubmitEvent)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); - SearchAndWaitForPreviewToShow(); +// Test that Instant is preloaded when the omnibox is focused. +IN_PROC_BROWSER_TEST_F(InstantTest, OmniboxFocusLoadsInstant) { + // The omnibox gets focus before the test begins. At this time, there's no + // InstantController (which is only created in SetupInstant() below), so no + // preloading has happened yet. + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); + EXPECT_FALSE(instant()->GetPreviewContents()); + + // Explicitly unfocus and refocus the omnibox. Since an InstantController now + // exists, it will preload Instant. + EXPECT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser())); + ui_test_utils::ClickOnView(browser(), VIEW_ID_TAB_CONTAINER); + EXPECT_TRUE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_TAB_CONTAINER)); - EXPECT_TRUE(preview()); - EXPECT_TRUE(instant()->is_displayable()); - EXPECT_TRUE(instant()->IsCurrent()); - EXPECT_EQ("defghi", UTF16ToUTF8(omnibox()->GetText())); + browser()->window()->GetLocationBar()->FocusLocation(false); + EXPECT_FALSE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_TAB_CONTAINER)); - WebContents* preview_tab = preview()->web_contents(); + TabContents* preview_tab = instant()->GetPreviewContents(); EXPECT_TRUE(preview_tab); - ASSERT_TRUE(PressEnter()); - - // Check that the preview has been committed. - EXPECT_FALSE(preview()); - EXPECT_FALSE(instant()->is_displayable()); + // Check that the page supports Instant, but it isn't showing. + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); + EXPECT_TRUE(instant()->loader()->supports_instant()); EXPECT_FALSE(instant()->IsCurrent()); - EXPECT_EQ(preview_tab, chrome::GetActiveWebContents(browser())); + EXPECT_FALSE(instant()->is_showing()); + + // Adding a new tab shouldn't delete or recreate the TabContents; otherwise, + // what's the point of preloading? + AddBlankTabAndShow(browser()); + EXPECT_EQ(preview_tab, instant()->GetPreviewContents()); + + // Unfocusing and refocusing the omnibox should also preserve the preview. + ui_test_utils::ClickOnView(browser(), VIEW_ID_TAB_CONTAINER); + EXPECT_TRUE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_TAB_CONTAINER)); - // We should have two entries. One corresponding to the page the user was - // first on, and one for the search page. - EXPECT_EQ(2, preview_tab->GetController().GetEntryCount()); + browser()->window()->GetLocationBar()->FocusLocation(false); + EXPECT_FALSE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_TAB_CONTAINER)); - // Check that the value is reflected and onsubmit is called. - EXPECT_EQ("true 1 0 1 true d false defghi true 3 3", - GetSearchStateAsString(preview_tab, true)); + EXPECT_EQ(preview_tab, instant()->GetPreviewContents()); - // Make sure the searchbox values were reset. - EXPECT_EQ("true 1 0 1 true d false false 0 0", - GetSearchStateAsString(preview_tab, false)); + // Doing a search should also use the same preloaded page. + SetOmniboxText("query"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); + EXPECT_TRUE(instant()->is_showing()); + EXPECT_EQ(preview_tab, instant()->GetPreviewContents()); } -// Verify that the oncancel event is dispatched upon losing focus. -IN_PROC_BROWSER_TEST_F(InstantTest, DISABLED_OnCancelEvent) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); - SearchAndWaitForPreviewToShow(); +// Test that the onchange event is dispatched upon typing in the omnibox. +IN_PROC_BROWSER_TEST_F(InstantTest, OnChangeEvent) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); + + // Typing "query" into the omnibox causes the first onchange event. + SetOmniboxText("query"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); + + // The page suggested "query suggestion" is inline autocompleted into the + // omnibox, causing the second onchange event. + EXPECT_EQ(ASCIIToUTF16("query suggestion"), omnibox()->GetText()); + EXPECT_TRUE(UpdateSearchState(instant()->GetPreviewContents())); + EXPECT_EQ(2, onchangecalls_); + + // Change the query and confirm that another onchange is sent. Since the new + // query is not a prefix of the hardcoded "query suggestion", no inline + // autocompletion happens, and thus, no fourth onchange event. + SetOmniboxText("search"); + EXPECT_TRUE(UpdateSearchState(instant()->GetPreviewContents())); + EXPECT_EQ(3, onchangecalls_); +} - EXPECT_TRUE(preview()); - EXPECT_TRUE(instant()->is_displayable()); - EXPECT_TRUE(instant()->IsCurrent()); - EXPECT_EQ("defghi", UTF16ToUTF8(omnibox()->GetText())); +// Test that the onsubmit event is dispatched upon pressing Enter. +IN_PROC_BROWSER_TEST_F(InstantTest, OnSubmitEvent) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); + SetOmniboxText("search"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); - WebContents* preview_tab = preview()->web_contents(); + // Stash a reference to the preview, so we can refer to it after commit. + TabContents* preview_tab = instant()->GetPreviewContents(); EXPECT_TRUE(preview_tab); - ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser())); - ui_test_utils::ClickOnView(browser(), VIEW_ID_TAB_CONTAINER); + // The state of the searchbox before the commit. + EXPECT_TRUE(UpdateSearchState(preview_tab)); + EXPECT_EQ("search", value_); + EXPECT_FALSE(verbatim_); + EXPECT_EQ(0, onsubmitcalls_); - // Check that the preview has been committed. - EXPECT_FALSE(preview()); - EXPECT_FALSE(instant()->is_displayable()); - EXPECT_FALSE(instant()->IsCurrent()); - EXPECT_EQ(preview_tab, chrome::GetActiveWebContents(browser())); + // Before the commit, the active tab is the NTP (i.e., not Instant). + TabContents* active_tab = chrome::GetActiveTabContents(browser()); + EXPECT_NE(preview_tab, active_tab); + EXPECT_EQ(1, active_tab->web_contents()->GetController().GetEntryCount()); + EXPECT_EQ(std::string(chrome::kAboutBlankURL), + omnibox()->model()->PermanentURL().spec()); - // Check that the value is reflected and oncancel is called. - EXPECT_EQ("true 0 1 1 true d false def false 3 3", - GetSearchStateAsString(preview_tab, true)); + // Commit the search by pressing Enter. + browser()->window()->GetLocationBar()->AcceptInput(); - // Make sure the searchbox values were reset. - EXPECT_EQ("true 0 1 1 true d false false 0 0", - GetSearchStateAsString(preview_tab, false)); -} + // After the commit, Instant should not be showing, or even have a preview. + EXPECT_FALSE(instant()->GetPreviewContents()); + EXPECT_FALSE(instant()->IsCurrent()); + EXPECT_FALSE(instant()->is_showing()); -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(SetSuggestionsArrayOfStrings)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); + // Check that the current active tab is indeed what was once the preview. + EXPECT_EQ(preview_tab, chrome::GetActiveTabContents(browser())); - ASSERT_TRUE(SetSuggestionsJavascriptArgument("['defg', 'unused']")); - SearchAndWaitForPreviewToShow(); - EXPECT_EQ("defg", GetSuggestion()); -} + // We should have two navigation entries, one for the NTP, and one for the + // Instant search that was committed. + EXPECT_EQ(2, preview_tab->web_contents()->GetController().GetEntryCount()); -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(SetSuggestionsEmptyArray)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); + // Check that the omnibox contains the Instant URL we loaded. + std::string instant_url = TemplateURLServiceFactory::GetForProfile( + browser()->profile())->GetDefaultSearchProvider()->instant_url_ref(). + ReplaceSearchTerms(TemplateURLRef::SearchTermsArgs(string16())); + EXPECT_EQ(instant_url, omnibox()->model()->PermanentURL().spec()); - ASSERT_TRUE(SetSuggestionsJavascriptArgument("[]")); - SearchAndWaitForPreviewToShow(); - EXPECT_EQ("", GetSuggestion()); + // Check that the searchbox API values have been reset. + std::string value; + EXPECT_TRUE(GetStringFromJS(preview_tab->web_contents()->GetRenderViewHost(), + "chrome.searchBox.value", &value)); + EXPECT_EQ("", value); + + // However, the page should've correctly received the committed query. + EXPECT_TRUE(UpdateSearchState(preview_tab)); + EXPECT_EQ("search", value_); + EXPECT_TRUE(verbatim_); + EXPECT_EQ(1, onsubmitcalls_); } -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(SetSuggestionsValidJson)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); +// Test that the oncancel event is dispatched upon clicking on the preview. +IN_PROC_BROWSER_TEST_F(InstantTest, OnCancelEvent) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); + SetOmniboxText("search"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); - ASSERT_TRUE(SetSuggestionsJavascriptArgument( - "{suggestions:[{value:'defg'},{value:'unused'}]}")); - SearchAndWaitForPreviewToShow(); - EXPECT_EQ("defg", GetSuggestion()); -} + // Stash a reference to the preview, so we can refer to it after commit. + TabContents* preview_tab = instant()->GetPreviewContents(); + EXPECT_TRUE(preview_tab); -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(SetSuggestionsInvalidSuggestions)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); + // The state of the searchbox before the commit. + EXPECT_TRUE(UpdateSearchState(preview_tab)); + EXPECT_EQ("search", value_); + EXPECT_FALSE(verbatim_); + EXPECT_EQ(0, oncancelcalls_); + + // Before the commit, the active tab is the NTP (i.e., not Instant). + TabContents* active_tab = chrome::GetActiveTabContents(browser()); + EXPECT_NE(preview_tab, active_tab); + EXPECT_EQ(1, active_tab->web_contents()->GetController().GetEntryCount()); + EXPECT_EQ(std::string(chrome::kAboutBlankURL), + omnibox()->model()->PermanentURL().spec()); + + // Commit the search by clicking on the preview. + EXPECT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser())); + ui_test_utils::ClickOnView(browser(), VIEW_ID_TAB_CONTAINER); - ASSERT_TRUE(SetSuggestionsJavascriptArgument("{suggestions:{value:'defg'}}")); - SearchAndWaitForPreviewToShow(); - EXPECT_EQ("", GetSuggestion()); -} + // After the commit, Instant should not be showing, or even have a preview. + EXPECT_FALSE(instant()->GetPreviewContents()); + EXPECT_FALSE(instant()->IsCurrent()); + EXPECT_FALSE(instant()->is_showing()); -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(SetSuggestionsEmptyJson)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); + // Check that the current active tab is indeed what was once the preview. + EXPECT_EQ(preview_tab, chrome::GetActiveTabContents(browser())); - ASSERT_TRUE(SetSuggestionsJavascriptArgument("{}")); - SearchAndWaitForPreviewToShow(); - EXPECT_EQ("", GetSuggestion()); -} + // We should have two navigation entries, one for the NTP, and one for the + // Instant search that was committed. + EXPECT_EQ(2, preview_tab->web_contents()->GetController().GetEntryCount()); -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(SetSuggestionsEmptySuggestions)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); + // Check that the omnibox contains the Instant URL we loaded. + std::string instant_url = TemplateURLServiceFactory::GetForProfile( + browser()->profile())->GetDefaultSearchProvider()->instant_url_ref(). + ReplaceSearchTerms(TemplateURLRef::SearchTermsArgs(string16())); + EXPECT_EQ(instant_url, omnibox()->model()->PermanentURL().spec()); - ASSERT_TRUE(SetSuggestionsJavascriptArgument("{suggestions:[]}")); - SearchAndWaitForPreviewToShow(); - EXPECT_EQ("", GetSuggestion()); + // Check that the searchbox API values have been reset. + std::string value; + EXPECT_TRUE(GetStringFromJS(preview_tab->web_contents()->GetRenderViewHost(), + "chrome.searchBox.value", &value)); + EXPECT_EQ("", value); + + // However, the page should've correctly received the committed query. + EXPECT_TRUE(UpdateSearchState(preview_tab)); + EXPECT_EQ("search", value_); + EXPECT_TRUE(verbatim_); + EXPECT_EQ(1, oncancelcalls_); } -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(SetSuggestionsEmptySuggestion)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); +// Test that the onreisze event is dispatched upon typing in the omnibox. +IN_PROC_BROWSER_TEST_F(InstantTest, OnResizeEvent) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); - ASSERT_TRUE(SetSuggestionsJavascriptArgument("{suggestions:[{}]}")); - SearchAndWaitForPreviewToShow(); - EXPECT_EQ("", GetSuggestion()); -} + // This makes Instant load the preview, along with an initial onresize() (see + // SearchBoxExtension::PageSupportsInstant() for why). + instant()->OnAutocompleteGotFocus(); + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(InstantCompleteNever)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); + EXPECT_TRUE(instant()->GetPreviewContents()); + EXPECT_TRUE(UpdateSearchState(instant()->GetPreviewContents())); + EXPECT_EQ(1, onresizecalls_); + EXPECT_EQ(0, height_); - ASSERT_TRUE(SetSuggestionsJavascriptArgument( - "{suggestions:[{value:'defg'}],complete_behavior:'never'}")); - SearchAndWaitForPreviewToShow(); - EXPECT_EQ("defg", GetSuggestion()); + // Type a query into the omnibox. This should cause an onresize() event, with + // a valid (non-zero) height. + SetOmniboxText("search"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); - EXPECT_EQ(INSTANT_COMPLETE_NEVER, - omnibox()->model()->instant_complete_behavior()); - EXPECT_EQ("def", UTF16ToUTF8(omnibox()->GetText())); + EXPECT_TRUE(UpdateSearchState(instant()->GetPreviewContents())); + EXPECT_EQ(2, onresizecalls_); + EXPECT_LT(0, height_); } -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(InstantCompleteDelayed)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); +// Test that the INSTANT_COMPLETE_NOW behavior works as expected. +IN_PROC_BROWSER_TEST_F(InstantTest, SuggestionIsCompletedNow) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); + instant()->OnAutocompleteGotFocus(); + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); - ASSERT_TRUE(SetSuggestionsJavascriptArgument( - "{suggestions:[{value:'defg'}],complete_behavior:'delayed'}")); - SearchAndWaitForPreviewToShow(); - EXPECT_EQ("defg", GetSuggestion()); + // Tell the JS to request for the given behavior. + EXPECT_TRUE(ExecuteScript("behavior = 'now'")); - EXPECT_EQ(INSTANT_COMPLETE_DELAYED, - omnibox()->model()->instant_complete_behavior()); - EXPECT_EQ("def", UTF16ToUTF8(omnibox()->GetText())); -} + SetOmniboxText("query"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(InstantCompleteNow)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); + // Get what's showing in the omnibox, and what's highlighted. + string16 text = omnibox()->GetText(); + size_t start = 0, end = 0; + omnibox()->GetSelectionBounds(&start, &end); + if (start > end) + std::swap(start, end); - ASSERT_TRUE(SetSuggestionsJavascriptArgument( - "{suggestions:[{value:'defg'}],complete_behavior:'now'}")); - SearchAndWaitForPreviewToShow(); - EXPECT_EQ("defg", GetSuggestion()); + EXPECT_EQ(ASCIIToUTF16("query suggestion"), text); + EXPECT_EQ(ASCIIToUTF16(" suggestion"), text.substr(start, end - start)); + EXPECT_EQ(ASCIIToUTF16(""), omnibox()->GetInstantSuggestion()); +} - EXPECT_EQ(INSTANT_COMPLETE_NOW, - omnibox()->model()->instant_complete_behavior()); - EXPECT_EQ("defg", UTF16ToUTF8(omnibox()->GetText())); +// The "delayed" completion behavior is not implemented on the Mac. Strange. +#if !defined(OS_MACOSX) +// Test that the INSTANT_COMPLETE_DELAYED behavior works as expected. +IN_PROC_BROWSER_TEST_F(InstantTest, SuggestionIsCompletedDelayed) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); + instant()->OnAutocompleteGotFocus(); + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); + + // Tell the JS to request for the given behavior. + EXPECT_TRUE(ExecuteScript("behavior = 'delayed'")); + + SetOmniboxText("query"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); + + // Get what's showing in the omnibox, and what's highlighted. + string16 text = omnibox()->GetText(); + size_t start = 0, end = 0; + omnibox()->GetSelectionBounds(&start, &end); + if (start > end) + std::swap(start, end); + + EXPECT_EQ(ASCIIToUTF16("query"), text); + EXPECT_EQ(ASCIIToUTF16(""), text.substr(start, end - start)); + EXPECT_EQ(ASCIIToUTF16(" suggestion"), omnibox()->GetInstantSuggestion()); + + // Wait for the animation to complete, which causes the omnibox to update. + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED); + + text = omnibox()->GetText(); + omnibox()->GetSelectionBounds(&start, &end); + if (start > end) + std::swap(start, end); + + EXPECT_EQ(ASCIIToUTF16("query suggestion"), text); + EXPECT_EQ(ASCIIToUTF16(" suggestion"), text.substr(start, end - start)); + EXPECT_EQ(ASCIIToUTF16(""), omnibox()->GetInstantSuggestion()); } +#endif -// Verifies that instant previews aren't shown for crash URLs. -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(CrashUrlCancelsInstant)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); +// Test that the INSTANT_COMPLETE_NEVER behavior works as expected. +IN_PROC_BROWSER_TEST_F(InstantTest, SuggestionIsCompletedNever) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); + instant()->OnAutocompleteGotFocus(); + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); - omnibox()->SetUserText(ASCIIToUTF16(chrome::kChromeUICrashURL)); - EXPECT_FALSE(preview()); -} + // Tell the JS to request for the given behavior. + EXPECT_TRUE(ExecuteScript("behavior = 'never'")); -// Tests that instant doesn't fire for intranet paths that look like searches. -// http://crbug.com/99836 -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(IntranetPathLooksLikeSearch)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); + SetOmniboxText("query"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); - // Unfocus the omnibox. This should delete any existing preview contents. - ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser())); - ui_test_utils::ClickOnView(browser(), VIEW_ID_TAB_CONTAINER); - EXPECT_FALSE(preview()); + // Get what's showing in the omnibox, and what's highlighted. + string16 text = omnibox()->GetText(); + size_t start = 0, end = 0; + omnibox()->GetSelectionBounds(&start, &end); + if (start > end) + std::swap(start, end); - // Navigate to a URL that looks like a search (when the scheme is stripped). - // It's okay if the host is bogus or the navigation fails, since we only care - // that instant doesn't act on it. - ui_test_utils::NavigateToURL(browser(), GURL("http://baby/beluga")); - EXPECT_EQ("baby/beluga", UTF16ToUTF8(omnibox()->GetText())); - EXPECT_FALSE(preview()); + EXPECT_EQ(ASCIIToUTF16("query"), text); + EXPECT_EQ(ASCIIToUTF16(""), text.substr(start, end - start)); + EXPECT_EQ(ASCIIToUTF16(" suggestion"), omnibox()->GetInstantSuggestion()); } -// Verifies that instant previews aren't shown for non-search URLs. -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(ShowPreviewNonSearch)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); +// Test that a valid suggestion is accepted. +IN_PROC_BROWSER_TEST_F(InstantTest, SuggestionIsValidObject) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); + instant()->OnAutocompleteGotFocus(); + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); - GURL url(test_server()->GetURL("files/empty.html")); - omnibox()->SetUserText(UTF8ToUTF16(url.spec())); - EXPECT_FALSE(preview()); + // Tell the JS to use the given suggestion. + EXPECT_TRUE(ExecuteScript("suggestion = [ { value: 'query completion' } ]")); + + SetOmniboxText("query"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); + + EXPECT_EQ(ASCIIToUTF16("query completion"), omnibox()->GetText()); } -// Transition from non-search to search and make sure everything works. -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(NonSearchToSearch)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - - // Load a non-search URL. - GURL url(test_server()->GetURL("files/empty.html")); - omnibox()->SetUserText(UTF8ToUTF16(url.spec())); - EXPECT_FALSE(preview()); - - // Now type in some search text. - DetermineInstantSupport(); - - // We should now have a preview, but it shouldn't be showing yet, because we - // haven't gotten back suggestions. - EXPECT_TRUE(preview()); - EXPECT_FALSE(loader()->ready()); - EXPECT_FALSE(instant()->is_displayable()); - EXPECT_FALSE(instant()->IsCurrent()); +// Test that an invalid suggestion is rejected. +IN_PROC_BROWSER_TEST_F(InstantTest, SuggestionIsInvalidObject) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); + instant()->OnAutocompleteGotFocus(); + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); - // Reset the user text so that the page is told the text changed. - // - // Typing into the omnibox sends onchange() to the page, which responds with - // suggestions, which causes the preview to be shown. However, when we called - // DetermineInstantSupport(), the resulting onchange was dropped on the floor - // because the page wasn't loaded yet. This is fine (the user may type before - // the page loads too). To handle this, we explicitly call onchange after the - // page loads (see initScript in searchbox_extension.cc). The search provider - // used in this test (instant.html) doesn't support initScript, so we have to - // trigger an onchange ourselves. - SearchAndWaitForPreviewToShow(); - - // We should now be showing the preview. - EXPECT_TRUE(preview()); - EXPECT_TRUE(loader()->ready()); - EXPECT_TRUE(instant()->is_displayable()); - EXPECT_TRUE(instant()->IsCurrent()); + // Tell the JS to use the given suggestion. + EXPECT_TRUE(ExecuteScript("suggestion = { value: 'query completion' }")); - content::RenderWidgetHostView* rwhv = - preview()->web_contents()->GetRenderWidgetHostView(); - EXPECT_TRUE(rwhv); - EXPECT_TRUE(rwhv->IsShowing()); + SetOmniboxText("query"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); + + EXPECT_EQ(ASCIIToUTF16("query"), omnibox()->GetText()); } -// Transition from search to non-search and make sure instant isn't displayable. -// See bug http://crbug.com/100368 for details. -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(SearchToNonSearch)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); +// Test that various forms of empty suggestions are rejected. +IN_PROC_BROWSER_TEST_F(InstantTest, SuggestionIsEmpty) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); + instant()->OnAutocompleteGotFocus(); + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); - content::WindowedNotificationObserver instant_support_observer( - chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED, - content::NotificationService::AllSources()); + EXPECT_TRUE(ExecuteScript("suggestion = {}")); + SetOmniboxText("query1"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); + EXPECT_EQ(ASCIIToUTF16("query1"), omnibox()->GetText()); + + instant()->Hide(); - // Type in some search text. - omnibox()->SetUserText(ASCIIToUTF16("def")); + EXPECT_TRUE(ExecuteScript("suggestion = []")); + SetOmniboxText("query2"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); + EXPECT_EQ(ASCIIToUTF16("query2"), omnibox()->GetText()); - // Load a non search URL. Don't wait for the preview to navigate. It'll still - // end up loading in the background. - GURL url(test_server()->GetURL("files/empty.html")); - omnibox()->SetUserText(UTF8ToUTF16(url.spec())); + instant()->Hide(); + + EXPECT_TRUE(ExecuteScript("suggestion = [{}]")); + SetOmniboxText("query3"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); + EXPECT_EQ(ASCIIToUTF16("query3"), omnibox()->GetText()); +} - instant_support_observer.Wait(); +// Test that Instant doesn't process URLs. +IN_PROC_BROWSER_TEST_F(InstantTest, RejectsURLs) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); - // We should now have a preview, but it shouldn't be showing yet. - EXPECT_TRUE(preview()); - EXPECT_FALSE(loader()->ready()); - EXPECT_FALSE(instant()->is_displayable()); + // Note that we are not actually navigating to these URLs yet. We are just + // typing them into the omnibox (without pressing Enter) and checking that + // Instant doesn't try to process them. + SetOmniboxText(chrome::kChromeUICrashURL); + EXPECT_FALSE(instant()->GetPreviewContents()); EXPECT_FALSE(instant()->IsCurrent()); + EXPECT_FALSE(instant()->is_showing()); - // Send onchange so that the page sends up suggestions. See the comments in - // NonSearchToSearch for why this is needed. - ASSERT_TRUE(content::ExecuteJavaScript( - preview()->web_contents()->GetRenderViewHost(), std::wstring(), - L"window.chrome.searchBox.onchange();")); - ASSERT_TRUE(WaitForMessageToBeProcessedByRenderer()); - - // Instant should be active, but not displaying. - EXPECT_TRUE(preview()); - EXPECT_TRUE(loader()->ready()); - EXPECT_FALSE(instant()->is_displayable()); + // Let's try again after creating the preview. + instant()->OnAutocompleteGotFocus(); + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); + + SetOmniboxText(chrome::kChromeUIHangURL); + EXPECT_TRUE(instant()->GetPreviewContents()); EXPECT_FALSE(instant()->IsCurrent()); -} + EXPECT_FALSE(instant()->is_showing()); -// Makes sure that if the server doesn't support the instant API we don't show -// anything. -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(SearchServerDoesntSupportInstant)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("empty.html"); + SetOmniboxText(chrome::kChromeUIKillURL); + EXPECT_TRUE(instant()->GetPreviewContents()); + EXPECT_FALSE(instant()->IsCurrent()); + EXPECT_FALSE(instant()->is_showing()); - content::WindowedNotificationObserver tab_closed_observer( - content::NOTIFICATION_WEB_CONTENTS_DESTROYED, - content::NotificationService::AllSources()); + // Make sure that the URLs were never sent to the preview page. + EXPECT_TRUE(UpdateSearchState(instant()->GetPreviewContents())); + EXPECT_EQ(0, onchangecalls_); + EXPECT_EQ("", value_); +} - omnibox()->SetUserText(ASCIIToUTF16("d")); - EXPECT_TRUE(preview()); +// Test that Instant doesn't fire for intranet paths that look like searches. +// http://crbug.com/99836 +IN_PROC_BROWSER_TEST_F(InstantTest, IntranetPathLooksLikeSearch) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); - // When the response comes back that the page doesn't support instant the tab - // should be closed. - tab_closed_observer.Wait(); - EXPECT_FALSE(preview()); + // Navigate to a URL that looks like a search (when the scheme is stripped). + // It's okay if the host is bogus or the navigation fails, since we only care + // that Instant doesn't act on it. + ui_test_utils::NavigateToURL(browser(), GURL("http://baby/beluga")); + EXPECT_EQ(ASCIIToUTF16("baby/beluga"), omnibox()->GetText()); + EXPECT_FALSE(instant()->GetPreviewContents()); } -// Verifies transitioning from loading a non-search string to a search string -// with the provider not supporting instant works (meaning we don't display -// anything). -IN_PROC_BROWSER_TEST_F(InstantTest, - MAYBE(NonSearchToSearchDoesntSupportInstant)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("empty.html"); - - GURL url(test_server()->GetURL("files/empty.html")); - omnibox()->SetUserText(UTF8ToUTF16(url.spec())); - EXPECT_FALSE(preview()); - - content::WindowedNotificationObserver tab_closed_observer( - content::NOTIFICATION_WEB_CONTENTS_DESTROYED, - content::NotificationService::AllSources()); +// Test that transitions between searches and non-searches work as expected. +IN_PROC_BROWSER_TEST_F(InstantTest, TransitionsBetweenSearchAndURL) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); - // Now type in some search text. - omnibox()->SetUserText(ASCIIToUTF16("d")); - EXPECT_TRUE(preview()); + // Type a search, but without waiting for the page to load, type a URL. + SetOmniboxText("query"); + SetOmniboxText("http://monstrous/nightmare"); - // When the response comes back that the page doesn't support instant the tab - // should be closed. - tab_closed_observer.Wait(); - EXPECT_FALSE(preview()); -} + // Wait for the page to load. + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); + EXPECT_TRUE(instant()->GetPreviewContents()); + EXPECT_FALSE(instant()->IsCurrent()); + EXPECT_FALSE(instant()->is_showing()); + EXPECT_TRUE(UpdateSearchState(instant()->GetPreviewContents())); + EXPECT_EQ(1, onchangecalls_); + EXPECT_EQ("query", value_); + + // Type a search. Instant should show. + SetOmniboxText("search"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); + EXPECT_TRUE(instant()->GetPreviewContents()); + EXPECT_TRUE(instant()->IsCurrent()); + EXPECT_TRUE(instant()->is_showing()); + EXPECT_TRUE(UpdateSearchState(instant()->GetPreviewContents())); + EXPECT_EQ(2, onchangecalls_); -// Verifies the page was told a non-zero height. -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(ValidHeight)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); - SearchAndWaitForPreviewToShow(); - - int height = -1; - - // searchBox height is not yet set during initial load. - ASSERT_TRUE(GetIntFromJavascript(preview()->web_contents(), - "window.beforeLoadSearchBox.height", &height)); - EXPECT_EQ(0, height); - - // searchBox height is available by the time the page loads. - ASSERT_TRUE(GetIntFromJavascript(preview()->web_contents(), - "window.chrome.searchBox.height", &height)); - EXPECT_GT(height, 0); + // Type another URL. The preview should be hidden. + SetOmniboxText("http://terrible/terror"); + EXPECT_TRUE(instant()->GetPreviewContents()); + EXPECT_FALSE(instant()->IsCurrent()); + EXPECT_FALSE(instant()->is_showing()); + EXPECT_TRUE(UpdateSearchState(instant()->GetPreviewContents())); + EXPECT_EQ(2, onchangecalls_); + + // Type the same search as before. The preview should show, but no onchange() + // is sent, since query hasn't changed. + SetOmniboxText("search"); + EXPECT_TRUE(instant()->GetPreviewContents()); + EXPECT_TRUE(instant()->IsCurrent()); + EXPECT_TRUE(instant()->is_showing()); + EXPECT_TRUE(UpdateSearchState(instant()->GetPreviewContents())); + EXPECT_EQ(2, onchangecalls_); } -// Make sure the renderer doesn't crash if javascript is blocked. -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(DontCrashOnBlockedJS)) { - browser()->profile()->GetHostContentSettingsMap()->SetDefaultContentSetting( - CONTENT_SETTINGS_TYPE_JAVASCRIPT, CONTENT_SETTING_BLOCK); - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); +// Test that Instant can't be fooled into committing a URL. +IN_PROC_BROWSER_TEST_F(InstantTest, DoesNotCommitURLs) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); + EXPECT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser())); - // Wait for notification that the instant API has been determined. As long as - // we get the notification we're good (the renderer didn't crash). - DetermineInstantSupport(); -} + // Type a URL. No Instant. + SetOmniboxText("http://deadly/nadder"); + EXPECT_FALSE(instant()->GetPreviewContents()); -// Makes sure window.chrome.searchbox doesn't persist when a new page is loaded. -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(DontPersistSearchbox)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); - SearchAndWaitForPreviewToShow(); + // Unfocus and refocus the omnibox. + ui_test_utils::ClickOnView(browser(), VIEW_ID_TAB_CONTAINER); + EXPECT_TRUE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_TAB_CONTAINER)); + browser()->window()->GetLocationBar()->FocusLocation(false); - std::string value; - ASSERT_TRUE(GetStringFromJavascript(preview()->web_contents(), - "window.chrome.searchBox.value", &value)); - EXPECT_EQ("def", value); + // The omnibox text hasn't changed, but the preview should've preloaded due + // to the omnibox getting focus. It still shouldn't be showing. + EXPECT_EQ(ASCIIToUTF16("http://deadly/nadder"), omnibox()->GetText()); + TabContents* preview_tab = instant()->GetPreviewContents(); + EXPECT_TRUE(preview_tab); + EXPECT_FALSE(instant()->IsCurrent()); + EXPECT_FALSE(instant()->is_showing()); - // Commit the preview. - ASSERT_TRUE(PressEnter()); - EXPECT_FALSE(preview()); + // Commit the URL. The omnibox should reflect the URL minus the scheme. + browser()->window()->GetLocationBar()->AcceptInput(); + TabContents* active_tab = chrome::GetActiveTabContents(browser()); + EXPECT_NE(preview_tab, active_tab); + EXPECT_EQ(ASCIIToUTF16("deadly/nadder"), omnibox()->GetText()); - // The searchBox actually gets cleared on commit. - ASSERT_TRUE(GetStringFromJavascript(chrome::GetActiveWebContents(browser()), - "window.chrome.searchBox.value", &value)); - EXPECT_EQ("", value); + // Instant shouldn't have done anything. + EXPECT_EQ(preview_tab, instant()->GetPreviewContents()); + EXPECT_FALSE(instant()->IsCurrent()); + EXPECT_FALSE(instant()->is_showing()); - // Navigate to a new URL. The searchBox values should stay cleared. - ui_test_utils::NavigateToURL( - browser(), test_server()->GetURL("files/empty.html")); + // Let's try again, using a slightly different approach. - ASSERT_TRUE(GetStringFromJavascript(chrome::GetActiveWebContents(browser()), - "window.chrome.searchBox.value", &value)); - EXPECT_EQ("", value); -} + // Type a query. This causes the preview to be shown. + browser()->window()->GetLocationBar()->FocusLocation(false); + SetOmniboxText("query"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); -// Tests that instant search is preloaded whenever the omnibox gets focus. -// PreloadsInstant fails on linux_chromeos trybots all the time, possibly -// because of http://crbug.com/80118. -#if defined(OS_CHROMEOS) || defined(OS_MACOSX) -IN_PROC_BROWSER_TEST_F(InstantTest, DISABLED_PreloadsInstant) { -#else -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(PreloadsInstant)) { -#endif - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - - // The omnibox gets focus before the test begins. At that time, there was no - // instant controller (which was only created after EnableInstant()), so no - // preloading happened. Unfocus the omnibox with ClickOnView(), so that when - // we focus it again, the controller will preload instant search. - ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser())); - ui_test_utils::ClickOnView(browser(), VIEW_ID_TAB_CONTAINER); + // Type a URL. This causes the preview to be hidden. + SetOmniboxText("http://hideous/zippleback"); + EXPECT_FALSE(instant()->is_showing()); - // Verify that there is no preview. - EXPECT_FALSE(preview()); + // Pretend the omnibox got focus. It already had focus, so we are just trying + // to tickle a different code path. + instant()->OnAutocompleteGotFocus(); - // Focusing the omnibox should cause instant to be preloaded. - content::WindowedNotificationObserver instant_support_observer( - chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED, - content::NotificationService::AllSources()); - browser()->window()->GetLocationBar()->FocusLocation(false); - instant_support_observer.Wait(); + // Commit the URL. As before, check that Instant wasn't committed. + browser()->window()->GetLocationBar()->AcceptInput(); + EXPECT_EQ(active_tab, chrome::GetActiveTabContents(browser())); + EXPECT_NE(preview_tab, active_tab); + EXPECT_EQ(ASCIIToUTF16("hideous/zippleback"), omnibox()->GetText()); - // Instant should have a preview, but not display it. - EXPECT_TRUE(preview()); - EXPECT_FALSE(instant()->is_displayable()); + // As before, Instant shouldn't have done anything. + EXPECT_EQ(preview_tab, instant()->GetPreviewContents()); EXPECT_FALSE(instant()->IsCurrent()); - ASSERT_TRUE(CheckVisibilityIs(preview()->web_contents(), false)); + EXPECT_FALSE(instant()->is_showing()); +} - // Adding a new tab shouldn't delete (or recreate) the TabContents. - TabContents* preview_tab = preview(); - AddBlankTabAndShow(browser()); - EXPECT_EQ(preview_tab, preview()); +// Test that a non-Instant search provider shows no previews. +IN_PROC_BROWSER_TEST_F(InstantTest, NonInstantSearchProvider) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("empty.html")); - // Doing a search should still use the same loader for the preview. - SearchAndWaitForPreviewToShow(); - EXPECT_EQ(preview_tab, preview()); + // Type a query. Instant will load the search provider. + SetOmniboxText("query"); + EXPECT_TRUE(instant()->GetPreviewContents()); - // Verify that the preview is in fact showing instant search. - EXPECT_TRUE(instant()->is_displayable()); - EXPECT_TRUE(instant()->IsCurrent()); - ASSERT_TRUE(CheckVisibilityIs(preview()->web_contents(), true)); + // When the response comes back, Instant will destroy the non-Instant page. + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); + EXPECT_FALSE(instant()->GetPreviewContents()); } -// Tests that the instant search page's visibility is set correctly. -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(PageVisibilityTest)) { - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); +// Test that the renderer doesn't crash if JavaScript is blocked. +IN_PROC_BROWSER_TEST_F(InstantTest, NoCrashOnBlockedJS) { + browser()->profile()->GetHostContentSettingsMap()->SetDefaultContentSetting( + CONTENT_SETTINGS_TYPE_JAVASCRIPT, CONTENT_SETTING_BLOCK); + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); - // Initially navigate to the empty page which should be visible. - ui_test_utils::NavigateToURL(browser(), test_server()->GetURL("")); - WebContents* initial_contents = chrome::GetActiveWebContents(browser()); + // Wait for notification that the Instant API has been determined. As long as + // we get the notification we're good (the renderer didn't crash). + instant()->OnAutocompleteGotFocus(); + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); +} - ASSERT_TRUE(CheckVisibilityIs(initial_contents, true)); +// Test that the preview and active tab's visibility states are set correctly. +IN_PROC_BROWSER_TEST_F(InstantTest, PageVisibility) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); + instant()->OnAutocompleteGotFocus(); + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); - // Type a search term and wait for the preview to appear. - browser()->window()->GetLocationBar()->FocusLocation(false); - DetermineInstantSupport(); - SearchAndWaitForPreviewToShow(); - WebContents* preview_contents = preview()->web_contents(); + TabContents* active_tab = chrome::GetActiveTabContents(browser()); + TabContents* preview_tab = instant()->GetPreviewContents(); - ASSERT_TRUE(CheckVisibilityIs(preview_contents, true)); - ASSERT_TRUE(CheckVisibilityIs(initial_contents, false)); + // Inititally, the active tab is showing; the preview is not. + EXPECT_TRUE(CheckVisibilityIs(active_tab, true)); + EXPECT_TRUE(CheckVisibilityIs(preview_tab, false)); - // Deleting the user text should hide the preview. - omnibox()->SetUserText(string16()); - ASSERT_TRUE(CheckVisibilityIs(preview_contents, false)); - ASSERT_TRUE(CheckVisibilityIs(initial_contents, true)); + // Type a query and wait for Instant to show. + SetOmniboxText("search"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); + EXPECT_TRUE(CheckVisibilityIs(active_tab, false)); + EXPECT_TRUE(CheckVisibilityIs(preview_tab, true)); - // Set the user text back and we should see the preview again. - omnibox()->SetUserText(ASCIIToUTF16("def")); - ASSERT_TRUE(CheckVisibilityIs(preview_contents, true)); - ASSERT_TRUE(CheckVisibilityIs(initial_contents, false)); + // Deleting the omnibox text should hide the preview. + SetOmniboxText(""); + EXPECT_TRUE(CheckVisibilityIs(active_tab, true)); + EXPECT_TRUE(CheckVisibilityIs(preview_tab, false)); + + // Typing a query should show the preview again. + SetOmniboxText("search"); + EXPECT_TRUE(CheckVisibilityIs(active_tab, false)); + EXPECT_TRUE(CheckVisibilityIs(preview_tab, true)); // Commit the preview. - ASSERT_TRUE(PressEnter()); - EXPECT_EQ(preview_contents, chrome::GetActiveWebContents(browser())); - ASSERT_TRUE(CheckVisibilityIs(preview_contents, true)); + browser()->window()->GetLocationBar()->AcceptInput(); + EXPECT_EQ(preview_tab, chrome::GetActiveTabContents(browser())); + EXPECT_TRUE(CheckVisibilityIs(preview_tab, true)); } -// Tests that the task manager identifies instant's preview tab correctly. -IN_PROC_BROWSER_TEST_F(InstantTest, MAYBE(TaskManagerPrefix)) { +// Test that the task manager identifies Instant's preview tab correctly. +IN_PROC_BROWSER_TEST_F(InstantTest, TaskManagerPrefix) { // The browser starts with one new tab, so the task manager should have two // rows initially, one for the browser process and one for tab's renderer. TaskManagerModel* task_manager = TaskManager::GetInstance()->model(); task_manager->StartUpdating(); TaskManagerBrowserTestUtil::WaitForResourceChange(2); - ASSERT_TRUE(test_server()->Start()); - EnableInstant(); - SetupInstantProvider("instant.html"); - DetermineInstantSupport(); - SearchAndWaitForPreviewToShow(); + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); + instant()->OnAutocompleteGotFocus(); + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); - // Now there should be three rows, the third being the instant preview. + // Now there should be three rows, the third being the Instant preview. TaskManagerBrowserTestUtil::WaitForResourceChange(3); string16 prefix = l10n_util::GetStringFUTF16( IDS_TASK_MANAGER_INSTANT_PREVIEW_PREFIX, string16()); string16 title = task_manager->GetResourceTitle(2); EXPECT_TRUE(StartsWith(title, prefix, true)) << title << " vs " << prefix; } + +void HistoryQueryDone(base::RunLoop* run_loop, + bool* result, + HistoryService::Handle /* handle */, + bool success, + const history::URLRow* /* urlrow */, + history::VisitVector* /* visitvector */) { + *result = success; + run_loop->Quit(); +} + +void KeywordQueryDone(base::RunLoop* run_loop, + std::vector<string16>* result, + HistoryService::Handle /* handle */, + std::vector<history::KeywordSearchTermVisit>* terms) { + for (size_t i = 0; i < terms->size(); ++i) + result->push_back((*terms)[i].term); + run_loop->Quit(); +} + +// Test that the Instant page load is not added to history. +IN_PROC_BROWSER_TEST_F(InstantTest, History) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); + instant()->OnAutocompleteGotFocus(); + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); + + const TemplateURL* template_url = TemplateURLServiceFactory::GetForProfile( + browser()->profile())->GetDefaultSearchProvider(); + + // |instant_url| is the URL Instant loads. |search_url| is the fake URL we + // enter into history for search terms extraction to work correctly. + std::string search_url = template_url->url_ref().ReplaceSearchTerms( + TemplateURLRef::SearchTermsArgs(ASCIIToUTF16("search"))); + std::string instant_url = template_url->instant_url_ref().ReplaceSearchTerms( + TemplateURLRef::SearchTermsArgs(string16())); + + HistoryService* history = HistoryServiceFactory::GetForProfile( + browser()->profile(), Profile::EXPLICIT_ACCESS); + ui_test_utils::WaitForHistoryToLoad(history); + + // Perform a search. + SetOmniboxText("search"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); + EXPECT_EQ(instant_url, instant()->loader()->instant_url()); + + // Commit the search. + browser()->window()->GetLocationBar()->AcceptInput(); + + bool found = false; + CancelableRequestConsumer consumer; + + // The fake search URL should be in history. + base::RunLoop run_loop1; + history->QueryURL(GURL(search_url), false, &consumer, + base::Bind(&HistoryQueryDone, &run_loop1, &found)); + run_loop1.Run(); + EXPECT_TRUE(found); + + // The Instant URL should not be in history. + base::RunLoop run_loop2; + history->QueryURL(GURL(instant_url), false, &consumer, + base::Bind(&HistoryQueryDone, &run_loop2, &found)); + run_loop2.Run(); + EXPECT_FALSE(found); + + // The search terms should have been extracted into history. + base::RunLoop run_loop3; + std::vector<string16> queries; + history->GetMostRecentKeywordSearchTerms(template_url->id(), + ASCIIToUTF16("s"), 1, &consumer, + base::Bind(&KeywordQueryDone, &run_loop3, &queries)); + run_loop3.Run(); + ASSERT_TRUE(queries.size()); + EXPECT_EQ(ASCIIToUTF16("search"), queries[0]); +} + +// On Windows, the call to NewEmptyWindow() fails the "GetBackingStore called +// while hidden" DCHECK(). It's not clear why; maybe because the active tab is +// in a hidden state when the Instant preview is showing, and somebody's trying +// to get its backing store? +#if !defined(OS_WIN) +// Test that creating a new window hides any currently showing Instant preview. +IN_PROC_BROWSER_TEST_F(InstantTest, NewWindowDismissesInstant) { + ASSERT_NO_FATAL_FAILURE(SetupInstant("instant.html")); + instant()->OnAutocompleteGotFocus(); + WaitFor(chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED); + + EXPECT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser())); + SetOmniboxText("search"); + WaitFor(chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN); + EXPECT_TRUE(instant()->GetPreviewContents()); + EXPECT_TRUE(instant()->IsCurrent()); + EXPECT_TRUE(instant()->is_showing()); + + content::WindowedNotificationObserver observer( + chrome::NOTIFICATION_INSTANT_CONTROLLER_HIDDEN, + content::NotificationService::AllSources()); + chrome::NewEmptyWindow(browser()->profile()); + observer.Wait(); + EXPECT_TRUE(instant()->GetPreviewContents()); + EXPECT_FALSE(instant()->IsCurrent()); + EXPECT_FALSE(instant()->is_showing()); +} +#endif diff --git a/chrome/browser/instant/instant_commit_type.h b/chrome/browser/instant/instant_commit_type.h index 980670d..dfefcf6 100644 --- a/chrome/browser/instant/instant_commit_type.h +++ b/chrome/browser/instant/instant_commit_type.h @@ -7,15 +7,12 @@ // Reason why the Instant preview is committed (merged into a tab). enum InstantCommitType { - // The commit is due to the user pressing enter or tab from the omnibox. + // The commit is due to the user pressing Enter from the omnibox. INSTANT_COMMIT_PRESSED_ENTER, // The commit is due to the omnibox losing focus, usually due to the user // clicking on the preview. INSTANT_COMMIT_FOCUS_LOST, - - // Used internally by InstantController. - INSTANT_COMMIT_DESTROY }; #endif // CHROME_BROWSER_INSTANT_INSTANT_COMMIT_TYPE_H_ diff --git a/chrome/browser/instant/instant_controller.cc b/chrome/browser/instant/instant_controller.cc index 51b26cc..2eefd06 100644 --- a/chrome/browser/instant/instant_controller.cc +++ b/chrome/browser/instant/instant_controller.cc @@ -4,58 +4,110 @@ #include "chrome/browser/instant/instant_controller.h" -#include "base/bind.h" #include "base/command_line.h" -#include "base/message_loop.h" +#include "base/i18n/case_conversion.h" #include "base/metrics/histogram.h" -#include "build/build_config.h" #include "chrome/browser/autocomplete/autocomplete_match.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/history/history_service_factory.h" +#include "chrome/browser/history/history_tab_helper.h" #include "chrome/browser/instant/instant_controller_delegate.h" #include "chrome/browser/instant/instant_loader.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/prefs/pref_service.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/search_engines/template_url.h" #include "chrome/browser/search_engines/template_url_service.h" #include "chrome/browser/search_engines/template_url_service_factory.h" -#include "chrome/browser/ui/blocked_content/blocked_content_tab_helper.h" #include "chrome/browser/ui/tab_contents/tab_contents.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" +#include "content/public/browser/favicon_status.h" +#include "content/public/browser/navigation_entry.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" +#include "ui/gfx/codec/png_codec.h" #if defined(TOOLKIT_VIEWS) -#include "ui/views/focus/focus_manager.h" -#include "ui/views/view.h" #include "ui/views/widget/widget.h" #endif +namespace { + +enum PreviewUsageType { + PREVIEW_CREATED = 0, + PREVIEW_DELETED, + PREVIEW_LOADED, + PREVIEW_SHOWED, + PREVIEW_COMMITTED, + PREVIEW_NUM_TYPES, +}; + +// An artificial delay (in milliseconds) we introduce before telling the Instant +// page about the new omnibox bounds, in cases where the bounds shrink. This is +// to avoid the page jumping up/down very fast in response to bounds changes. +const int kUpdateBoundsDelayMS = 1000; + +// The maximum number of times we'll load a non-Instant-supporting search engine +// before we give up and blacklist it for the rest of the browsing session. +const int kMaxInstantSupportFailures = 10; + +std::string ModeToString(InstantController::Mode mode) { + switch (mode) { + case InstantController::INSTANT: return "_Instant"; + case InstantController::SUGGEST: return "_Suggest"; + case InstantController::HIDDEN: return "_Hidden"; + case InstantController::SILENT: return "_Silent"; + } + + NOTREACHED(); + return std::string(); +} + +void AddPreviewUsageForHistogram(InstantController::Mode mode, + PreviewUsageType usage) { + DCHECK(0 <= usage && usage < PREVIEW_NUM_TYPES) << usage; + base::Histogram* histogram = base::LinearHistogram::FactoryGet( + "Instant.Previews" + ModeToString(mode), 1, PREVIEW_NUM_TYPES, + PREVIEW_NUM_TYPES + 1, base::Histogram::kUmaTargetedHistogramFlag); + histogram->Add(usage); +} + +void AddSessionStorageHistogram(InstantController::Mode mode, + const TabContents* tab1, + const TabContents* tab2) { + base::Histogram* histogram = base::BooleanHistogram::FactoryGet( + "Instant.SessionStorageNamespace" + ModeToString(mode), + base::Histogram::kUmaTargetedHistogramFlag); + histogram->AddBoolean( + tab1->web_contents()->GetController().GetSessionStorageNamespace() == + tab2->web_contents()->GetController().GetSessionStorageNamespace()); +} + +} // namespace + InstantController::InstantController(InstantControllerDelegate* delegate, Mode mode) : delegate_(delegate), - is_displayable_(false), - is_out_of_date_(true), - commit_on_pointer_release_(false), + mode_(mode), + last_active_tab_(NULL), + last_verbatim_(false), + last_complete_behavior_(INSTANT_COMPLETE_NOW), last_transition_type_(content::PAGE_TRANSITION_LINK), - ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), - mode_(mode) { - DCHECK(mode_ == INSTANT || mode_ == SUGGEST || mode_ == HIDDEN || - mode_ == SILENT); + is_showing_(false), + loader_processed_last_update_(false) { } InstantController::~InstantController() { + if (GetPreviewContents()) + AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED); } // static void InstantController::RegisterUserPrefs(PrefService* prefs) { - prefs->RegisterBooleanPref(prefs::kInstantConfirmDialogShown, - false, + prefs->RegisterBooleanPref(prefs::kInstantConfirmDialogShown, false, PrefService::SYNCABLE_PREF); - prefs->RegisterBooleanPref(prefs::kInstantEnabled, - false, + prefs->RegisterBooleanPref(prefs::kInstantEnabled, false, PrefService::SYNCABLE_PREF); // TODO(jamescook): Move this to search controller. @@ -65,214 +117,214 @@ void InstantController::RegisterUserPrefs(PrefService* prefs) { } // static -void InstantController::RecordMetrics(Profile* profile) { - UMA_HISTOGRAM_ENUMERATION("Instant.Status", IsEnabled(profile), 2); -} - -// static bool InstantController::IsEnabled(Profile* profile) { - const PrefService* prefs = profile->GetPrefs(); + const PrefService* prefs = profile ? profile->GetPrefs() : NULL; return prefs && prefs->GetBoolean(prefs::kInstantEnabled); } -// static -void InstantController::Enable(Profile* profile) { - PrefService* prefs = profile->GetPrefs(); - if (!prefs) - return; - - prefs->SetBoolean(prefs::kInstantEnabled, true); - prefs->SetBoolean(prefs::kInstantConfirmDialogShown, true); - UMA_HISTOGRAM_ENUMERATION("Instant.Preference", 1, 2); -} - -// static -void InstantController::Disable(Profile* profile) { - PrefService* prefs = profile->GetPrefs(); - if (!prefs) - return; - - prefs->SetBoolean(prefs::kInstantEnabled, false); - UMA_HISTOGRAM_ENUMERATION("Instant.Preference", 0, 2); -} - bool InstantController::Update(const AutocompleteMatch& match, const string16& user_text, bool verbatim, - string16* suggested_text) { - suggested_text->clear(); + string16* suggested_text, + InstantCompleteBehavior* complete_behavior) { + const TabContents* active_tab = delegate_->GetActiveTabContents(); - is_out_of_date_ = false; - commit_on_pointer_release_ = false; - last_transition_type_ = match.transition; - last_url_ = match.destination_url; - last_user_text_ = user_text; + // We could get here with no active tab if the Browser is closing. + if (!active_tab) { + Hide(); + return false; + } + + std::string instant_url; + Profile* profile = active_tab->profile(); - TabContents* tab_contents = delegate_->GetInstantHostTabContents(); - if (!tab_contents) { + // If the match's TemplateURL isn't valid, it is likely not a query. + if (!GetInstantURL(match.GetTemplateURL(profile), &instant_url)) { Hide(); return false; } - Profile* profile = tab_contents->profile(); - const TemplateURL* template_url = match.GetTemplateURL(profile); - const TemplateURL* default_t_url = - TemplateURLServiceFactory::GetForProfile(profile) - ->GetDefaultSearchProvider(); - if (!IsValidInstantTemplateURL(template_url) || !default_t_url || - template_url->id() != default_t_url->id()) { + string16 full_text = user_text + *suggested_text; + + if (full_text.empty()) { Hide(); return false; } - if (!loader_.get() || loader_->template_url_id() != template_url->id()) - loader_.reset(new InstantLoader(this, template_url->id(), std::string())); + // The presence of any suggested_text implies verbatim. + DCHECK(suggested_text->empty() || verbatim) + << user_text << "|" << *suggested_text; - if (mode_ == SILENT) { - // For the SILENT mode, we process |user_text| at commit time. - loader_->MaybeLoadInstantURL(tab_contents, template_url); + ResetLoader(instant_url, active_tab); + last_active_tab_ = active_tab; + + // Track the non-Instant search URL for this query. + url_for_history_ = match.destination_url; + last_transition_type_ = match.transition; + + last_user_text_ = user_text; + + // Don't send an update to the loader if the query text hasn't changed. + if (full_text == last_full_text_ && verbatim == last_verbatim_) { + // Since we are updating |suggested_text|, shouldn't we also update + // |last_full_text_|? No. There's no guarantee that our suggestion will + // actually be inline autocompleted. For example, it may get trumped by + // a history suggestion. If our suggestion does make it, the omnibox will + // call Update() again, at which time we'll update |last_full_text_|. + *suggested_text = last_suggestion_; + *complete_behavior = last_complete_behavior_; + + // We need to call Show() here because of this: + // 1. User has typed a query (say Q). Instant overlay is showing results. + // 2. User arrows-down to a URL entry or erases all omnibox text. Both of + // these cause the overlay to Hide(). + // 3. User arrows-up to Q or types Q again. The last text we processed is + // still Q, so we don't Update() the loader, but we do need to Show(). + if (loader_processed_last_update_ && mode_ == INSTANT) + Show(); return true; } - UpdateLoader(tab_contents, template_url, match.destination_url, - match.transition, user_text, verbatim, suggested_text); + last_full_text_ = full_text; + last_verbatim_ = verbatim; + loader_processed_last_update_ = false; + + // Reset the last suggestion, as it's no longer valid. + suggested_text->clear(); + last_suggestion_.clear(); + *complete_behavior = last_complete_behavior_ = INSTANT_COMPLETE_NOW; + + if (mode_ != SILENT) { + loader_->Update(last_full_text_, last_verbatim_); + + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, + content::Source<InstantController>(this), + content::NotificationService::NoDetails()); + } - content::NotificationService::current()->Notify( - chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, - content::Source<InstantController>(this), - content::NotificationService::NoDetails()); return true; } +// 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). void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) { - if (omnibox_bounds_ == bounds) + if (omnibox_bounds_ == bounds || mode_ != INSTANT) return; - // Always track the omnibox bounds. That way if Update is later invoked the - // bounds are in sync. omnibox_bounds_ = bounds; - - if (loader_.get() && !is_out_of_date_ && mode_ == INSTANT) - loader_->SetOmniboxBounds(bounds); -} - -void InstantController::DestroyPreviewContents() { - if (!loader_.get()) { - // We're not showing anything, nothing to do. - return; + if (omnibox_bounds_.height() > last_omnibox_bounds_.height()) { + update_bounds_timer_.Stop(); + SendBoundsToPage(); + } else if (!update_bounds_timer_.IsRunning()) { + update_bounds_timer_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), this, + &InstantController::SendBoundsToPage); } +} - if (is_displayable_) { - is_displayable_ = false; - delegate_->HideInstant(); - } - delete ReleasePreviewContents(INSTANT_COMMIT_DESTROY, NULL); +TabContents* InstantController::GetPreviewContents() const { + return loader_.get() ? loader_->preview_contents() : NULL; } void InstantController::Hide() { - is_out_of_date_ = true; - commit_on_pointer_release_ = false; - if (is_displayable_) { - is_displayable_ = false; + last_active_tab_ = NULL; + if (is_showing_) { + is_showing_ = false; delegate_->HideInstant(); } } bool InstantController::IsCurrent() const { - // TODO(mmenke): See if we can do something more intelligent in the - // navigation pending case. - return is_displayable_ && !loader_->IsNavigationPending() && - !loader_->needs_reload(); -} - -bool InstantController::PrepareForCommit() { - if (is_out_of_date_ || !loader_.get()) - return false; - - // If we are in the visible (INSTANT) mode, return the status of the preview. - if (mode_ == INSTANT) - return IsCurrent(); - - TabContents* tab_contents = delegate_->GetInstantHostTabContents(); - if (!tab_contents) - return false; - - const TemplateURL* template_url = - TemplateURLServiceFactory::GetForProfile(tab_contents->profile()) - ->GetDefaultSearchProvider(); - if (!IsValidInstantTemplateURL(template_url) || - loader_->template_url_id() != template_url->id() || - loader_->IsNavigationPending() || - loader_->is_determining_if_page_supports_instant()) { - return false; - } - - // In the SUGGEST and HIDDEN modes, we must have sent an Update() by now, so - // check if the loader failed to process it. - if ((mode_ == SUGGEST || mode_ == HIDDEN) - && (!loader_->ready() || !loader_->http_status_ok())) { - return false; - } - - // Ignore the suggested text, as we are about to commit the verbatim query. - string16 suggested_text; - UpdateLoader(tab_contents, template_url, last_url_, last_transition_type_, - last_user_text_, true, &suggested_text); - return true; + DCHECK(IsOutOfDate() || GetPreviewContents()); + return !IsOutOfDate() && GetPreviewContents() && loader_->supports_instant(); } TabContents* InstantController::CommitCurrentPreview(InstantCommitType type) { - DCHECK(loader_.get()); - TabContents* tab_contents = delegate_->GetInstantHostTabContents(); - DCHECK(tab_contents); - TabContents* preview = ReleasePreviewContents(type, tab_contents); + const TabContents* active_tab = delegate_->GetActiveTabContents(); + TabContents* preview = ReleasePreviewContents(type); + AddSessionStorageHistogram(mode_, active_tab, preview); preview->web_contents()->GetController().CopyStateFromAndPrune( - &tab_contents->web_contents()->GetController()); + &active_tab->web_contents()->GetController()); delegate_->CommitInstant(preview); - CompleteRelease(preview); return preview; } -bool InstantController::CommitIfCurrent() { - if (IsCurrent()) { - CommitCurrentPreview(INSTANT_COMMIT_PRESSED_ENTER); - return true; +TabContents* InstantController::ReleasePreviewContents(InstantCommitType type) { + TabContents* preview = loader_->ReleasePreviewContents(type, last_full_text_); + + // If the preview page has navigated since the last Update(), we need to add + // the navigation to history ourselves. Else, the page will navigate after + // commit, and it will be added to history in the usual manner. + scoped_refptr<history::HistoryAddPageArgs> last_navigation = + loader_->last_navigation(); + if (last_navigation != NULL) { + content::NavigationEntry* entry = + preview->web_contents()->GetController().GetActiveEntry(); + DCHECK_EQ(last_navigation->url, entry->GetURL()); + + // Add the page to history. + preview->history_tab_helper()->UpdateHistoryForNavigation(last_navigation); + + // Update the page title. + preview->history_tab_helper()->UpdateHistoryPageTitle(*entry); + + // Update the favicon. + FaviconService* favicon_service = + preview->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS); + if (favicon_service && entry->GetFavicon().valid && + entry->GetFavicon().image.IsEmpty()) { + std::vector<unsigned char> image_data; + // TODO: Add all variants once the history service supports it. + gfx::PNGCodec::EncodeBGRASkBitmap( + entry->GetFavicon().image.AsBitmap(), false, &image_data); + favicon_service->SetFavicon(entry->GetURL(), + entry->GetFavicon().url, + image_data, + history::FAVICON); + } } - return false; -} -void InstantController::SetCommitOnPointerRelease() { - commit_on_pointer_release_ = true; -} + // Add a fake history entry with a non-Instant search URL, so that search + // terms extraction (for autocomplete history matches) works. + HistoryService* history = HistoryServiceFactory::GetForProfile( + preview->profile(), Profile::EXPLICIT_ACCESS); + if (history) { + history->AddPage(url_for_history_, NULL, 0, GURL(), last_transition_type_, + history::RedirectList(), history::SOURCE_BROWSED, false); + } -bool InstantController::IsPointerDownFromActivate() { - DCHECK(loader_.get()); - return loader_->IsPointerDownFromActivate(); -} + AddPreviewUsageForHistogram(mode_, PREVIEW_COMMITTED); -#if defined(OS_MACOSX) -void InstantController::OnAutocompleteLostFocus( - gfx::NativeView view_gaining_focus) { - // If |IsPointerDownFromActivate()| returns false, the RenderWidgetHostView - // did not receive a mouseDown event. Therefore, we should destroy the - // preview. Otherwise, the RWHV was clicked, so we commit the preview. - if (!IsCurrent() || !IsPointerDownFromActivate()) - DestroyPreviewContents(); - else - SetCommitOnPointerRelease(); + // We may have gotten here from CommitInstant(), which means the loader may + // still be on the stack. So, schedule a destruction for later. + MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release()); + + // This call is here to hide the preview and reset view state. It won't + // actually delete |loader_| because it was just released to DeleteSoon(). + DeleteLoader(); + + return preview; } -#else + +// TODO(sreeram): Since we never delete the loader except when committing +// Instant, the loader may have a very stale page. Reload it when stale. void InstantController::OnAutocompleteLostFocus( gfx::NativeView view_gaining_focus) { - if (!IsCurrent()) { - DestroyPreviewContents(); + DCHECK(!is_showing_ || GetPreviewContents()); + + // If the preview is not showing, nothing to do. + if (!is_showing_ || !GetPreviewContents()) return; - } +#if defined(OS_MACOSX) + if (!loader_->IsPointerDownFromActivate()) + Hide(); +#else content::RenderWidgetHostView* rwhv = GetPreviewContents()->web_contents()->GetRenderWidgetHostView(); if (!view_gaining_focus || !rwhv) { - DestroyPreviewContents(); + Hide(); return; } @@ -295,20 +347,16 @@ void InstantController::OnAutocompleteLostFocus( gfx::NativeView tab_view = GetPreviewContents()->web_contents()->GetNativeView(); + // Focus is going to the renderer. if (rwhv->GetNativeView() == view_gaining_focus || tab_view == view_gaining_focus) { - if (!IsPointerDownFromActivate()) { - // If the mouse is not down, focus is not going to the renderer. Someone - // else moved focus and we shouldn't commit. - DestroyPreviewContents(); - return; - } - // We're showing instant results. As instant results may shift when - // committing we commit on the mouse up. This way a slow click still works - // fine. - SetCommitOnPointerRelease(); + // If the mouse is not down, focus is not going to the renderer. Someone + // else moved focus and we shouldn't commit. + if (!loader_->IsPointerDownFromActivate()) + Hide(); + return; } @@ -328,184 +376,214 @@ void InstantController::OnAutocompleteLostFocus( return; } - DestroyPreviewContents(); -} + Hide(); #endif +} void InstantController::OnAutocompleteGotFocus() { - TabContents* tab_contents = delegate_->GetInstantHostTabContents(); - if (!tab_contents) - return; + const TabContents* active_tab = delegate_->GetActiveTabContents(); - const TemplateURL* template_url = - TemplateURLServiceFactory::GetForProfile(tab_contents->profile()) - ->GetDefaultSearchProvider(); - if (!IsValidInstantTemplateURL(template_url)) + // We could get here with no active tab if the Browser is closing. + if (!active_tab) return; - if (!loader_.get() || loader_->template_url_id() != template_url->id()) - loader_.reset(new InstantLoader(this, template_url->id(), std::string())); - loader_->MaybeLoadInstantURL(tab_contents, template_url); -} - -TabContents* InstantController::ReleasePreviewContents( - InstantCommitType type, - TabContents* current_tab) { - if (!loader_.get()) - return NULL; - - TabContents* tab = loader_->ReleasePreviewContents(type, current_tab); - ClearBlacklist(); - is_out_of_date_ = true; - is_displayable_ = false; - commit_on_pointer_release_ = false; - omnibox_bounds_ = gfx::Rect(); - loader_.reset(); - return tab; -} + // Since we don't have any autocomplete match to work with, we'll just use + // the default search provider's Instant URL. + const TemplateURL* template_url = + TemplateURLServiceFactory::GetForProfile(active_tab->profile())-> + GetDefaultSearchProvider(); -void InstantController::CompleteRelease(TabContents* tab) { - tab->blocked_content_tab_helper()->SetAllContentsBlocked(false); -} + std::string instant_url; + if (!GetInstantURL(template_url, &instant_url)) + return; -TabContents* InstantController::GetPreviewContents() const { - return loader_.get() ? loader_->preview_contents() : NULL; + ResetLoader(instant_url, active_tab); } -void InstantController::InstantStatusChanged(InstantLoader* loader) { - DCHECK(loader_.get()); - UpdateIsDisplayable(); +bool InstantController::commit_on_pointer_release() const { + return GetPreviewContents() && loader_->IsPointerDownFromActivate(); } -void InstantController::SetSuggestedTextFor( +void InstantController::SetSuggestions( InstantLoader* loader, - const string16& text, + const std::vector<string16>& suggestions, InstantCompleteBehavior behavior) { - if (is_out_of_date_) + DCHECK_EQ(loader_.get(), loader); + if (loader_ != loader || IsOutOfDate() || mode_ == SILENT || mode_ == HIDDEN) return; - if (mode_ == INSTANT || mode_ == SUGGEST) - delegate_->SetSuggestedText(text, behavior); -} - -gfx::Rect InstantController::GetInstantBounds() { - return delegate_->GetInstantBounds(); -} + loader_processed_last_update_ = true; -bool InstantController::ShouldCommitInstantOnPointerRelease() { - return commit_on_pointer_release_; -} + string16 suggestion; + if (!suggestions.empty()) + suggestion = suggestions[0]; -void InstantController::CommitInstantLoader(InstantLoader* loader) { - if (loader_.get() && loader_.get() == loader) { - CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST); + string16 suggestion_lower = base::i18n::ToLower(suggestion); + string16 user_text_lower = base::i18n::ToLower(last_user_text_); + if (user_text_lower.size() >= suggestion_lower.size() || + suggestion_lower.compare(0, user_text_lower.size(), user_text_lower)) { + suggestion.clear(); } else { - // This can happen if the mouse was down, we swapped out the preview and - // the mouse was released. Generally this shouldn't happen, but if it does - // revert. - DestroyPreviewContents(); + suggestion.erase(0, last_user_text_.size()); } + + last_suggestion_ = suggestion; + last_complete_behavior_ = behavior; + if (!last_verbatim_) + delegate_->SetSuggestedText(suggestion, behavior); + + if (mode_ != SUGGEST) + Show(); } -void InstantController::InstantLoaderDoesntSupportInstant( - InstantLoader* loader) { - VLOG(1) << "provider does not support instant"; +void InstantController::CommitInstantLoader(InstantLoader* loader) { + DCHECK_EQ(loader_.get(), loader); + DCHECK(is_showing_ && !IsOutOfDate()) << is_showing_; + if (loader_ != loader || !is_showing_ || IsOutOfDate()) + return; + + CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST); +} - // Don't attempt to use instant for this search engine again. - BlacklistFromInstant(); +void InstantController::InstantLoaderPreviewLoaded(InstantLoader* loader) { + DCHECK_EQ(loader_.get(), loader); + AddPreviewUsageForHistogram(mode_, PREVIEW_LOADED); } -void InstantController::AddToBlacklist(InstantLoader* loader, const GURL& url) { - // Don't attempt to use instant for this search engine again. - BlacklistFromInstant(); +void InstantController::InstantSupportDetermined(InstantLoader* loader, + bool supports_instant) { + DCHECK_EQ(loader_.get(), loader); + if (supports_instant) { + blacklisted_urls_.erase(loader->instant_url()); + } else { + ++blacklisted_urls_[loader->instant_url()]; + if (loader_ == loader) { + if (GetPreviewContents()) + AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED); + + // Because of the state of the stack, we can't destroy the loader now. + MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release()); + DeleteLoader(); + } + } + + content::Details<const bool> details(&supports_instant); + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED, + content::NotificationService::AllSources(), + details); } void InstantController::SwappedTabContents(InstantLoader* loader) { - if (is_displayable_) - delegate_->ShowInstant(loader->preview_contents()); + DCHECK_EQ(loader_.get(), loader); + if (loader_ == loader && is_showing_) + delegate_->ShowInstant(); } -void InstantController::InstantLoaderContentsFocused() { +void InstantController::InstantLoaderContentsFocused(InstantLoader* loader) { + DCHECK_EQ(loader_.get(), loader); + DCHECK(is_showing_ && !IsOutOfDate()) << is_showing_; #if defined(USE_AURA) // On aura the omnibox only receives a focus lost if we initiate the focus // change. This does that. - if (mode_ == INSTANT) + if (is_showing_ && !IsOutOfDate()) delegate_->InstantPreviewFocused(); #endif } -void InstantController::UpdateIsDisplayable() { - bool displayable = !is_out_of_date_ && loader_.get() && loader_->ready() && - loader_->http_status_ok(); - if (displayable == is_displayable_ || mode_ != INSTANT) - return; +void InstantController::ResetLoader(const std::string& instant_url, + const TabContents* active_tab) { + if (GetPreviewContents() && loader_->instant_url() != instant_url) + DeleteLoader(); - is_displayable_ = displayable; - if (!is_displayable_) { - delegate_->HideInstant(); - } else { - delegate_->ShowInstant(loader_->preview_contents()); - content::NotificationService::current()->Notify( - chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN, - content::Source<InstantController>(this), - content::NotificationService::NoDetails()); + if (!GetPreviewContents()) { + loader_.reset(new InstantLoader(this, instant_url, active_tab)); + loader_->Init(); + AddPreviewUsageForHistogram(mode_, PREVIEW_CREATED); } } -void InstantController::UpdateLoader(TabContents* tab_contents, - const TemplateURL* template_url, - const GURL& url, - content::PageTransition transition_type, - const string16& user_text, - bool verbatim, - string16* suggested_text) { - if (mode_ == INSTANT) - loader_->SetOmniboxBounds(omnibox_bounds_); - loader_->Update(tab_contents, template_url, url, transition_type, user_text, - verbatim, suggested_text); - UpdateIsDisplayable(); - // For the HIDDEN and SILENT modes, don't send back suggestions. - if (mode_ == HIDDEN || mode_ == SILENT) - suggested_text->clear(); +void InstantController::DeleteLoader() { + Hide(); + last_full_text_.clear(); + last_user_text_.clear(); + last_verbatim_ = false; + last_suggestion_.clear(); + last_complete_behavior_ = INSTANT_COMPLETE_NOW; + last_transition_type_ = content::PAGE_TRANSITION_LINK; + last_omnibox_bounds_ = gfx::Rect(); + url_for_history_ = GURL(); + if (GetPreviewContents()) + AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED); + loader_.reset(); } -// Returns true if |template_url| is a valid TemplateURL for use by instant. -bool InstantController::IsValidInstantTemplateURL( - const TemplateURL* template_url) { - return template_url && template_url->id() && - template_url->instant_url_ref().SupportsReplacement() && - !IsBlacklistedFromInstant(template_url->id()); +void InstantController::Show() { + if (!is_showing_) { + is_showing_ = true; + delegate_->ShowInstant(); + AddPreviewUsageForHistogram(mode_, PREVIEW_SHOWED); + } } -void InstantController::BlacklistFromInstant() { - if (!loader_.get()) +void InstantController::SendBoundsToPage() { + if (last_omnibox_bounds_ == omnibox_bounds_ || IsOutOfDate() || + !GetPreviewContents() || loader_->IsPointerDownFromActivate()) { return; + } - DCHECK(loader_->template_url_id()); - blacklisted_ids_.insert(loader_->template_url_id()); + last_omnibox_bounds_ = omnibox_bounds_; + gfx::Rect preview_bounds = delegate_->GetInstantBounds(); + gfx::Rect intersection = omnibox_bounds_.Intersect(preview_bounds); - // Because of the state of the stack we can't destroy the loader now. - ScheduleDestroy(loader_.release()); - UpdateIsDisplayable(); -} + // Translate into window coordinates. + if (!intersection.IsEmpty()) { + intersection.Offset(-preview_bounds.origin().x(), + -preview_bounds.origin().y()); + } -bool InstantController::IsBlacklistedFromInstant(TemplateURLID id) { - return blacklisted_ids_.count(id) > 0; -} + // 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. + // There is no point in sanity-checking |intersection.y()| because the omnibox + // can be placed anywhere vertically relative to the preview (for example, in + // Mac fullscreen mode, the omnibox is fully enclosed by the preview bounds). + DCHECK_LE(0, intersection.x()); + DCHECK_LE(0, intersection.width()); + DCHECK_LE(0, intersection.height()); -void InstantController::ClearBlacklist() { - blacklisted_ids_.clear(); + loader_->SetOmniboxBounds(intersection); } -void InstantController::ScheduleDestroy(InstantLoader* loader) { - loaders_to_destroy_.push_back(loader); - if (!weak_factory_.HasWeakPtrs()) { - MessageLoop::current()->PostTask( - FROM_HERE, base::Bind(&InstantController::DestroyLoaders, - weak_factory_.GetWeakPtr())); +bool InstantController::GetInstantURL(const TemplateURL* template_url, + std::string* instant_url) const { + CommandLine* command_line = CommandLine::ForCurrentProcess(); + if (command_line->HasSwitch(switches::kInstantURL)) { + *instant_url = command_line->GetSwitchValueASCII(switches::kInstantURL); + return true; + } + + if (!template_url) + return false; + + const TemplateURLRef& instant_url_ref = template_url->instant_url_ref(); + if (!instant_url_ref.IsValid() || !instant_url_ref.SupportsReplacement()) + return false; + + *instant_url = instant_url_ref.ReplaceSearchTerms( + TemplateURLRef::SearchTermsArgs(string16())); + + std::map<std::string, int>::const_iterator iter = + blacklisted_urls_.find(*instant_url); + if (iter != blacklisted_urls_.end() && + iter->second > kMaxInstantSupportFailures) { + instant_url->clear(); + return false; } + + return true; } -void InstantController::DestroyLoaders() { - loaders_to_destroy_.clear(); +bool InstantController::IsOutOfDate() const { + return !last_active_tab_ || + last_active_tab_ != delegate_->GetActiveTabContents(); } diff --git a/chrome/browser/instant/instant_controller.h b/chrome/browser/instant/instant_controller.h index b6ad248..4d551ff 100644 --- a/chrome/browser/instant/instant_controller.h +++ b/chrome/browser/instant/instant_controller.h @@ -5,17 +5,17 @@ #ifndef CHROME_BROWSER_INSTANT_INSTANT_CONTROLLER_H_ #define CHROME_BROWSER_INSTANT_INSTANT_CONTROLLER_H_ -#include <set> +#include <map> #include <string> +#include <vector> #include "base/basictypes.h" +#include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" -#include "base/memory/scoped_vector.h" -#include "base/memory/weak_ptr.h" #include "base/string16.h" +#include "base/timer.h" #include "chrome/browser/instant/instant_commit_type.h" #include "chrome/browser/instant/instant_loader_delegate.h" -#include "chrome/browser/search_engines/template_url_id.h" #include "chrome/common/instant_types.h" #include "content/public/common/page_transition_types.h" #include "googleurl/src/gurl.h" @@ -25,31 +25,32 @@ struct AutocompleteMatch; class InstantControllerDelegate; class InstantLoader; -class InstantTest; class PrefService; class Profile; class TabContents; class TemplateURL; -// InstantController maintains a WebContents that is intended to give a preview -// of a URL. InstantController is owned by Browser. +// InstantController maintains a WebContents that is intended to give a +// preview of search results. InstantController is owned by Browser via +// BrowserInstantController. // -// At any time the WebContents maintained by InstantController may be destroyed -// by way of |DestroyPreviewContents|, which results in |HideInstant| being -// invoked on the delegate. Similarly the preview may be committed at any time -// by invoking |CommitCurrentPreview|, which results in |CommitInstant| -// being invoked on the delegate. Also see |PrepareForCommit| below. +// At any time the WebContents maintained by InstantController may be hidden +// from view by way of Hide(), which may result in HideInstant() being invoked +// on the delegate. Similarly the preview may be committed at any time by +// invoking CommitCurrentPreview(), which results in CommitInstant() being +// invoked on the delegate. class InstantController : public InstantLoaderDelegate { public: - // Amount of time to wait before starting the instant animation. - static const int kAutoCommitPauseTimeMS = 1000; - // Duration of the instant animation in which the colors change. - static const int kAutoCommitFadeInTimeMS = 300; + // Amount of time to wait before starting the animation for suggested text. + static const int kInlineAutocompletePauseTimeMS = 1000; + + // Duration of the suggested text animation in which the colors change. + static const int kInlineAutocompleteFadeInTimeMS = 300; // InstantController may operate in one of these modes: // INSTANT: The default search engine is preloaded when the omnibox gets // focus. Queries are issued as the user types. Predicted queries are - // are inline autocompleted into the omnibox. Result previews are shown. + // inline autocompleted into the omnibox. Result previews are shown. // SUGGEST: Same as INSTANT, without visible previews. // HIDDEN: Same as SUGGEST, without the inline autocompletion. // SILENT: Same as HIDDEN, without issuing queries as the user types. The @@ -58,216 +59,180 @@ class InstantController : public InstantLoaderDelegate { INSTANT, SUGGEST, HIDDEN, - SILENT + SILENT, }; InstantController(InstantControllerDelegate* delegate, Mode mode); virtual ~InstantController(); - // Registers instant related preferences. + // Registers Instant related preferences. static void RegisterUserPrefs(PrefService* prefs); - // Records instant metrics. - static void RecordMetrics(Profile* profile); - - // Returns true if instant is enabled in the given |profile|'s preferences. + // Returns true if Instant is enabled for the given |profile|. static bool IsEnabled(Profile* profile); - // Enables instant. - static void Enable(Profile* profile); - - // Disables instant. - static void Disable(Profile* profile); - - // Invoked as the user types in the omnibox with the url to navigate to. If - // the url is valid and a preview WebContents has not been created, it is - // created. If |verbatim| is true search results are shown for |user_text| - // rather than the best guess as to what the search thought the user meant. - // |verbatim| only matters if the AutocompleteMatch is for a search engine - // that supports instant. Returns true if the attempt to update does not - // result in the preview WebContents being destroyed. + // Invoked as the user types into the omnibox. |user_text| is what the user + // has typed. |suggested_text| is the current inline autocomplete text. It + // may be replaced by Instant's autocomplete suggestion, if any. If |verbatim| + // is true, search results are shown for |user_text| rather than the best + // guess as to what Instant thinks the user means. Returns true if the update + // is processed by Instant (i.e., if |match| is a search rather than a URL). bool Update(const AutocompleteMatch& match, const string16& user_text, bool verbatim, - string16* suggested_text); + string16* suggested_text, + InstantCompleteBehavior* complete_behavior); - // Sets the bounds of the omnibox (in screen coordinates). The bounds are - // remembered until the preview is committed or destroyed. This is only used - // when showing results for a search provider that supports instant. + // Sets the bounds of the omnibox dropdown, in screen coordinates. void SetOmniboxBounds(const gfx::Rect& bounds); - // Notifies the delegate to hide the preview and destroys the preview - // WebContents. Does nothing if the preview WebContents has not been created. - void DestroyPreviewContents(); + // The preview TabContents. May be NULL if ReleasePreviewContents() has been + // called, with no subsequent successful call to Update(). InstantController + // retains ownership of the object. + TabContents* GetPreviewContents() const; - // Notifies the delegate to hide the preview but leaves it around in hopes it - // can be subsequently used. The preview will not be used until Update() (with - // valid parameters) is invoked. + // Hides the preview, but doesn't destroy it, in hopes it can be subsequently + // reused. The preview will not be used until a call to Update() succeeds. void Hide(); - // Returns true if we're showing the last URL passed to |Update|. If this is - // false a commit does not result in committing the last url passed to update. - // A return value of false happens if we're in the process of determining if - // the page supports instant. + // Returns true if the Instant preview can be committed now. This can be true + // even if the preview is not showing yet, because we can commit as long as + // we've processed the last Update() and we know the loader supports Instant. bool IsCurrent() const; - // Returns true if the caller should proceed with committing the preview. A - // return value of false means that there is no valid preview to commit. This - // is used by Browser, when the user presses <Enter>, to decide whether to - // load the omnibox contents through Instant or otherwise. This is needed - // because calls to |Update| don't necessarily result in a preview being - // shown, such as in the HIDDEN and SILENT modes. - bool PrepareForCommit(); - - // Invoked when the user does some gesture that should trigger making the - // current previewed page the permanent page. Returns the TabContents that - // contains the committed preview. + // Unconditionally commits the preview. Returns the TabContents that contains + // the committed preview. TabContents* CommitCurrentPreview(InstantCommitType type); - // Accepts the currently showing instant preview, if any, and returns true. - // Returns false if there is no instant preview showing. - bool CommitIfCurrent(); - - // Sets InstantController so that when the mouse is released or the - // touch-gesture ends, the preview is committed. - void SetCommitOnPointerRelease(); - - bool commit_on_pointer_release() const { return commit_on_pointer_release_; } - - // Calls through to method of same name on loader. - bool IsPointerDownFromActivate(); + // Releases the preview WebContents passing ownership to the caller. This is + // intended to be called when the preview WebContents is committed. This does + // not notify the delegate. + TabContents* ReleasePreviewContents( + InstantCommitType type) WARN_UNUSED_RESULT; - // The autocomplete edit that was initiating the current instant session has + // The autocomplete edit that was initiating the current Instant session has // lost focus. Commit or discard the preview accordingly. void OnAutocompleteLostFocus(gfx::NativeView view_gaining_focus); - // The autocomplete edit has gained focus. Preload the instant URL of the + // The autocomplete edit has gained focus. Preload the Instant URL of the // default search engine, in anticipation of the user typing a query. void OnAutocompleteGotFocus(); - // Releases the preview WebContents passing ownership to the caller. This is - // intended to be called when the preview WebContents is committed. This does - // not notify the delegate. |tab_contents| is the underlying tab onto which - // the preview will be committed. It can be NULL when the underlying tab is - // irrelevant, for example when |type| is INSTANT_COMMIT_DESTROY. - // WARNING: be sure and invoke CompleteRelease after adding the returned - // WebContents to a tabstrip. - TabContents* ReleasePreviewContents(InstantCommitType type, - TabContents* tab_contents); - - // Does cleanup after the preview contents has been added to the tabstrip. - // Invoke this if you explicitly invoke ReleasePreviewContents. - void CompleteRelease(TabContents* tab); - - // The preview TabContents; may be null. - TabContents* GetPreviewContents() const; - - // Returns true if the preview TabContents is ready to be displayed. In - // some situations this may return false yet GetPreviewContents() returns - // non-NULL. - bool is_displayable() const { return is_displayable_; } + // Returns whether the preview will be committed when the mouse or touch + // pointer is released. + bool commit_on_pointer_release() const; // Returns the transition type of the last AutocompleteMatch passed to Update. content::PageTransition last_transition_type() const { return last_transition_type_; } - // InstantLoaderDelegate - virtual void InstantStatusChanged(InstantLoader* loader) OVERRIDE; - virtual void SetSuggestedTextFor(InstantLoader* loader, - const string16& text, - InstantCompleteBehavior behavior) OVERRIDE; - virtual gfx::Rect GetInstantBounds() OVERRIDE; - virtual bool ShouldCommitInstantOnPointerRelease() OVERRIDE; + // InstantLoaderDelegate: + virtual void SetSuggestions(InstantLoader* loader, + const std::vector<string16>& suggestions, + InstantCompleteBehavior behavior) OVERRIDE; virtual void CommitInstantLoader(InstantLoader* loader) OVERRIDE; - virtual void InstantLoaderDoesntSupportInstant( - InstantLoader* loader) OVERRIDE; - virtual void AddToBlacklist(InstantLoader* loader, - const GURL& url) OVERRIDE; + virtual void InstantLoaderPreviewLoaded(InstantLoader* loader) OVERRIDE; + virtual void InstantSupportDetermined(InstantLoader* loader, + bool supports_instant) OVERRIDE; virtual void SwappedTabContents(InstantLoader* loader) OVERRIDE; - virtual void InstantLoaderContentsFocused() OVERRIDE; + virtual void InstantLoaderContentsFocused(InstantLoader* loader) OVERRIDE; + +#if defined(UNIT_TEST) + // Accessors used only in tests. + bool is_showing() const { return is_showing_; } + InstantLoader* loader() const { return loader_.get(); } +#endif private: - friend class InstantTest; + // Creates a new loader if necessary (for example, if the |instant_url| has + // changed since the last time we created the loader). + void ResetLoader(const std::string& instant_url, + const TabContents* active_tab); - typedef std::set<std::string> HostBlacklist; + // Destroys the |loader_| and its preview contents. + void DeleteLoader(); - // Updates |is_displayable_| and if necessary notifies the delegate. - void UpdateIsDisplayable(); + // Counterpart to Hide(). Asks the |delegate_| to display the preview. + void Show(); - // Updates InstantLoaderManager and its current InstantLoader. This is invoked - // internally from Update. - void UpdateLoader(TabContents* tab_contents, - const TemplateURL* template_url, - const GURL& url, - content::PageTransition transition_type, - const string16& user_text, - bool verbatim, - string16* suggested_text); + // Send the omnibox dropdown bounds to the page. + void SendBoundsToPage(); - // Returns true if |template_url| is a valid TemplateURL for use by instant. - bool IsValidInstantTemplateURL(const TemplateURL* template_url); + // If |template_url| is a valid TemplateURL for use with Instant, fills in + // |instant_url| and returns true; returns false otherwise. + // Note: If the command-line switch kInstantURL is set, this method uses its + // value for |instant_url| and returns true without examining |template_url|. + bool GetInstantURL(const TemplateURL* template_url, + std::string* instant_url) const; - // Marks the loader as not supporting instant. - void BlacklistFromInstant(); + // Returns true if the preview is no longer relevant, say because the last + // Update() was for a URL and not a search query, or the user switched tabs. + bool IsOutOfDate() const; - // Returns true if the specified id has been blacklisted from supporting - // instant. - bool IsBlacklistedFromInstant(TemplateURLID id); + InstantControllerDelegate* const delegate_; - // Clears the set of search engines blacklisted. - void ClearBlacklist(); + scoped_ptr<InstantLoader> loader_; - // Deletes |loader| after a delay. At the time we determine a site doesn't - // want to participate in instant we can't destroy the loader (because - // destroying the loader destroys the WebContents and the WebContents is on - // the stack). Instead we place the loader in |loaders_to_destroy_| and - // schedule a task. - void ScheduleDestroy(InstantLoader* loader); + // See the enum description above. + const Mode mode_; - // Destroys all loaders scheduled for destruction in |ScheduleForDestroy|. - void DestroyLoaders(); + // The active tab at the time of the last Update(). Used by IsOutOfDate() to + // know whether the user switched tabs. ***NEVER DEREFERENCE THIS POINTER.*** + // It may be a dangling pointer to a freed object. Should only be used for + // pointer comparisons. + const void* last_active_tab_; - InstantControllerDelegate* delegate_; + // The most recent full omnibox query text known to us. If this is empty, it + // could also mean that the omnibox text was a URL (or something else that + // we shouldn't be processing). + string16 last_full_text_; - // True if |loader_| is ready to be displayed. - bool is_displayable_; + // The most recent user_text passed to Update(). + string16 last_user_text_; - // Set to true in Hide() and false in Update(). Used when we persist the - // |loader_|, but it isn't up to date. - bool is_out_of_date_; + // The most recent verbatim passed to Update(). + bool last_verbatim_; - scoped_ptr<InstantLoader> loader_; + // The most recent suggestion received from the page, minus any prefix that + // the user has typed. + string16 last_suggestion_; - // See description above setter. - gfx::Rect omnibox_bounds_; + // The most recent autocomplete behavior for |last_suggestion_|. + InstantCompleteBehavior last_complete_behavior_; - // See descritopn above for SetCommitOnPointerRelease. - bool commit_on_pointer_release_; - - // See description above getter. + // See comments on the getter above. content::PageTransition last_transition_type_; - // The IDs of any search engines that don't support instant. We assume all - // search engines support instant, but if we determine an engine doesn't - // support instant it is added to this list. The list is cleared out on every - // reset/commit. - std::set<TemplateURLID> blacklisted_ids_; + // True if the preview is currently being displayed. Guaranteed to be false + // if IsOutOfDate() is true. + bool is_showing_; - // Used by ScheduleForDestroy; see it for details. - base::WeakPtrFactory<InstantController> weak_factory_; + // True if we've received a response from the loader for the last Update(), + // thus indicating that the page is ready to be shown. + bool loader_processed_last_update_; - // List of InstantLoaders to destroy. See ScheduleForDestroy for details. - ScopedVector<InstantLoader> loaders_to_destroy_; + // Current omnibox bounds. + gfx::Rect omnibox_bounds_; - // The URL of the most recent match passed to |Update|. - GURL last_url_; + // Last bounds passed to the page. + gfx::Rect last_omnibox_bounds_; - // The most recent user_text passed to |Update|. - string16 last_user_text_; + // Timer used to update the bounds of the omnibox. + base::OneShotTimer<InstantController> update_bounds_timer_; - // See the enum description above. - const Mode mode_; + // For each key K => value N, the map says that we found that the search + // engine identified by Instant URL K didn't support the Instant API in each + // of the last N times that we loaded it. If an Instant URL isn't present in + // the map at all or has a value 0, it means that search engine supports the + // Instant API (or we assume it does, since we haven't determined it doesn't). + std::map<std::string, int> blacklisted_urls_; + + // Search terms extraction (for autocomplete history matches) doesn't work + // on Instant URLs. So, whenever the user commits an Instant search, we add + // an equivalent non-Instant search URL to history, so that the search shows + // up in autocomplete history matches. + GURL url_for_history_; DISALLOW_COPY_AND_ASSIGN(InstantController); }; diff --git a/chrome/browser/instant/instant_controller_delegate.h b/chrome/browser/instant/instant_controller_delegate.h index 306cd07..908dd7e 100644 --- a/chrome/browser/instant/instant_controller_delegate.h +++ b/chrome/browser/instant/instant_controller_delegate.h @@ -14,22 +14,23 @@ namespace gfx { class Rect; } -// |InstantController| calls these methods on its delegate (usually |Browser|) -// to ask for the Instant preview to be shown, hidden, etc. In the following -// methods, if a |TabContents| argument is explicitly supplied, the delegate -// MUST use it. Otherwise, the preview TabContents can be gotten by calling -// |InstantController::GetPreviewContents| (note that it may return NULL). +// InstantController calls these methods on its delegate (Browser, via +// BrowserInstantController) to ask for the Instant preview to be shown, +// hidden, etc. In the following methods, if a TabContents argument is +// explicitly supplied, the delegate MUST use it. Otherwise, the preview +// TabContents can be gotten by calling InstantController::GetPreviewContents() +// (note that it may return NULL). class InstantControllerDelegate { public: // Show the preview. - virtual void ShowInstant(TabContents* preview_contents) = 0; + virtual void ShowInstant() = 0; // Hide any preview currently being shown. virtual void HideInstant() = 0; - // Commit the preview by merging it into the active tab. Delegate takes - // ownership of |preview_contents|. - virtual void CommitInstant(TabContents* preview_contents) = 0; + // Commit the |preview| by merging it into the active tab. Delegate takes + // ownership of |preview|. + virtual void CommitInstant(TabContents* preview) = 0; // Autocomplete the Instant suggested |text| into the omnibox, using the // specified |behavior| (see instant_types.h for details). @@ -44,7 +45,7 @@ class InstantControllerDelegate { virtual void InstantPreviewFocused() = 0; // Return the currently active tab, over which any preview would be shown. - virtual TabContents* GetInstantHostTabContents() const = 0; + virtual TabContents* GetActiveTabContents() const = 0; protected: virtual ~InstantControllerDelegate() {} diff --git a/chrome/browser/instant/instant_loader.cc b/chrome/browser/instant/instant_loader.cc index 41c9c85..1f08888 100644 --- a/chrome/browser/instant/instant_loader.cc +++ b/chrome/browser/instant/instant_loader.cc @@ -4,24 +4,10 @@ #include "chrome/browser/instant/instant_loader.h" -#include <algorithm> -#include <string> -#include <utility> -#include <vector> - -#include "base/command_line.h" -#include "base/i18n/case_conversion.h" -#include "base/metrics/histogram.h" -#include "base/string_number_conversions.h" -#include "base/timer.h" -#include "base/utf_string_conversions.h" -#include "base/values.h" -#include "chrome/browser/favicon/favicon_service.h" -#include "chrome/browser/history/history_marshaling.h" -#include "chrome/browser/history/history_tab_helper.h" +#include "chrome/browser/content_settings/tab_specific_content_settings.h" +#include "chrome/browser/history/history_types.h" #include "chrome/browser/instant/instant_loader_delegate.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/browser/search_engines/template_url.h" #include "chrome/browser/tab_contents/thumbnail_generator.h" #include "chrome/browser/ui/blocked_content/blocked_content_tab_helper.h" #include "chrome/browser/ui/constrained_window_tab_helper.h" @@ -29,213 +15,41 @@ #include "chrome/browser/ui/tab_contents/core_tab_helper.h" #include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h" #include "chrome/browser/ui/tab_contents/tab_contents.h" -#include "chrome/common/chrome_notification_types.h" -#include "chrome/common/chrome_switches.h" #include "chrome/common/render_messages.h" -#include "content/public/browser/favicon_status.h" -#include "content/public/browser/navigation_controller.h" -#include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" -#include "content/public/browser/notification_details.h" -#include "content/public/browser/notification_observer.h" -#include "content/public/browser/notification_registrar.h" -#include "content/public/browser/notification_service.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/render_view_host.h" -#include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" -#include "content/public/browser/session_storage_namespace.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_delegate.h" -#include "content/public/browser/web_contents_view.h" -#include "net/http/http_util.h" -#include "ui/base/l10n/l10n_util.h" -#include "ui/gfx/codec/png_codec.h" - -using content::NavigationController; -using content::NavigationEntry; -using content::RenderViewHost; -using content::RenderWidgetHost; -using content::SessionStorageNamespace; -using content::WebContents; - -namespace { - -// Number of ms to delay before updating the omnibox bounds. This is only used -// when the bounds of the omnibox shrinks. If the bounds grows, we update -// immediately. -const int kUpdateBoundsDelayMS = 1000; - -// If this status code is seen instant is disabled for the specified host. -const int kHostBlacklistStatusCode = 403; - -enum PreviewUsageType { - PREVIEW_CREATED, - PREVIEW_DELETED, - PREVIEW_LOADED, - PREVIEW_SHOWN, - PREVIEW_COMMITTED, - PREVIEW_NUM_TYPES, -}; - -void AddPreviewUsageForHistogram(TemplateURLID template_url_id, - PreviewUsageType usage, - const std::string& group) { - DCHECK(0 <= usage && usage < PREVIEW_NUM_TYPES); - // Only track the histogram for the instant loaders, for now. - if (template_url_id) { - base::Histogram* histogram = base::LinearHistogram::FactoryGet( - "Instant.Previews" + group, 1, PREVIEW_NUM_TYPES, PREVIEW_NUM_TYPES + 1, - base::Histogram::kUmaTargetedHistogramFlag); - histogram->Add(usage); - } -} - -SessionStorageNamespace* GetSessionStorageNamespace(TabContents* tab) { - return tab->web_contents()->GetController().GetSessionStorageNamespace(); -} - -} // namespace - -// static -const char* const InstantLoader::kInstantHeader = "X-Purpose"; -// static -const char* const InstantLoader::kInstantHeaderValue = "instant"; - -// FrameLoadObserver is responsible for determining if the page supports -// instant after it has loaded. -class InstantLoader::FrameLoadObserver : public content::NotificationObserver { - public: - FrameLoadObserver(InstantLoader* loader, - WebContents* web_contents, - const string16& text, - bool verbatim) - : loader_(loader), - web_contents_(web_contents), - text_(text), - verbatim_(verbatim), - unique_id_( - web_contents_->GetController().GetPendingEntry()->GetUniqueID()) { - registrar_.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME, - content::Source<WebContents>(web_contents_)); - } - - // Sets the text to send to the page. - void set_text(const string16& text) { text_ = text; } - - // Sets whether verbatim results are obtained rather than predictive. - void set_verbatim(bool verbatim) { verbatim_ = verbatim; } - - // content::NotificationObserver: - virtual void Observe(int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) OVERRIDE; - - private: - InstantLoader* loader_; - - // The WebContents we're listening for changes on. - WebContents* web_contents_; - - // Text to send down to the page. - string16 text_; - - // Whether verbatim results are obtained. - bool verbatim_; - - // unique_id of the NavigationEntry we're waiting on. - const int unique_id_; - - // Registers and unregisters us for notifications. - content::NotificationRegistrar registrar_; - - DISALLOW_COPY_AND_ASSIGN(FrameLoadObserver); -}; - -void InstantLoader::FrameLoadObserver::Observe( - int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) { - switch (type) { - case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME: { - int page_id = *(content::Details<int>(details).ptr()); - NavigationEntry* active_entry = - web_contents_->GetController().GetActiveEntry(); - if (!active_entry || active_entry->GetPageID() != page_id || - active_entry->GetUniqueID() != unique_id_) { - return; - } - loader_->SendBoundsToPage(true); - // TODO: support real cursor position. - int text_length = static_cast<int>(text_.size()); - RenderViewHost* host = web_contents_->GetRenderViewHost(); - host->Send(new ChromeViewMsg_DetermineIfPageSupportsInstant( - host->GetRoutingID(), text_, verbatim_, text_length, text_length)); - break; - } - default: - NOTREACHED(); - break; - } -} // WebContentsDelegateImpl ----------------------------------------------------- class InstantLoader::WebContentsDelegateImpl - : public content::WebContentsDelegate, + : public ConstrainedWindowTabHelperDelegate, public CoreTabHelperDelegate, - public ConstrainedWindowTabHelperDelegate, - public content::NotificationObserver, + public content::WebContentsDelegate, public content::WebContentsObserver { public: explicit WebContentsDelegateImpl(InstantLoader* loader); - // Invoked prior to loading a new URL. - void PrepareForNewLoad(); - - // Invoked when the preview paints. Invokes PreviewPainted on the loader. - void PreviewPainted(); - bool is_pointer_down_from_activate() const { return is_pointer_down_from_activate_; } - void set_user_typed_before_load() { user_typed_before_load_ = true; } - - // Sets the last URL that will be added to history when CommitHistory is - // invoked and removes all but the first navigation. - void SetLastHistoryURLAndPrune(const GURL& url); - - // Commits the currently buffered history. - void CommitHistory(bool supports_instant); - - void RegisterForPaintNotifications(RenderWidgetHost* render_widget_host); - - void UnregisterForPaintNotifications(); + // ConstrainedWindowTabHelperDelegate: + virtual bool ShouldFocusConstrainedWindow() OVERRIDE; - // NotificationObserver: - virtual void Observe(int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) OVERRIDE; + // CoreTabHelperDelegate: + virtual void SwapTabContents(TabContents* old_tc, + TabContents* new_tc) OVERRIDE; // content::WebContentsDelegate: - virtual void NavigationStateChanged(const WebContents* source, - unsigned changed_flags) OVERRIDE; - virtual void AddNavigationHeaders(const GURL& url, - std::string* headers) OVERRIDE; virtual bool ShouldSuppressDialogs() OVERRIDE; - virtual void BeforeUnloadFired(content::WebContents* tab, - bool proceed, - bool* proceed_to_fire_unload) OVERRIDE; - virtual void SetFocusToLocationBar(bool select_all) OVERRIDE; virtual bool ShouldFocusPageAfterCrash() OVERRIDE; - virtual void WebContentsFocused(WebContents* contents) OVERRIDE; virtual void LostCapture() OVERRIDE; - // If the user drags, we won't get a mouse up (at least on Linux). Commit the - // instant result when the drag ends, so that during the drag the page won't - // move around. - virtual void DragEnded() OVERRIDE; + virtual void WebContentsFocused(content::WebContents* contents) OVERRIDE; virtual bool CanDownload(content::RenderViewHost* render_view_host, int request_id, const std::string& request_method) OVERRIDE; @@ -243,276 +57,90 @@ class InstantLoader::WebContentsDelegateImpl virtual void HandlePointerActivate() OVERRIDE; virtual void HandleGestureBegin() OVERRIDE; virtual void HandleGestureEnd() OVERRIDE; + virtual void DragEnded() OVERRIDE; virtual bool OnGoToEntryOffset(int offset) OVERRIDE; virtual bool ShouldAddNavigationToHistory( const history::HistoryAddPageArgs& add_page_args, content::NavigationType navigation_type) OVERRIDE; - // CoreTabHelperDelegate: - virtual void SwapTabContents(TabContents* old_tc, - TabContents* new_tc) OVERRIDE; - - // ConstrainedWindowTabHelperDelegate: - virtual void WillShowConstrainedWindow(TabContents* source) OVERRIDE; - virtual bool ShouldFocusConstrainedWindow() OVERRIDE; - // content::WebContentsObserver: - virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; - virtual void DidFailProvisionalLoad( + virtual void DidFinishLoad( int64 frame_id, - bool is_main_frame, const GURL& validated_url, - int error_code, - const string16& error_description, + bool is_main_frame, content::RenderViewHost* render_view_host) OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; private: - typedef std::vector<scoped_refptr<history::HistoryAddPageArgs> > - AddPageVector; - // Message from renderer indicating the page has suggestions. - void OnSetSuggestions( - int32 page_id, - const std::vector<std::string>& suggestions, - InstantCompleteBehavior behavior); - - // Messages from the renderer when we've determined whether the page supports - // instant. - void OnInstantSupportDetermined(int32 page_id, bool result); + void OnSetSuggestions(int page_id, + const std::vector<string16>& suggestions, + InstantCompleteBehavior behavior); - // Commits, if applicable, on mouse is released, or a gesture ends. - void CommitOnPointerReleaseIfNecessary(); + // Message from the renderer determining whether it supports the Instant API. + void OnInstantSupportDetermined(int page_id, bool result); - InstantLoader* loader_; + void CommitFromPointerReleaseIfNecessary(); + void MaybeSetAndNotifyInstantSupportDetermined(bool supports_instant); - content::NotificationRegistrar registrar_; + InstantLoader* const loader_; - // If we are registered for paint notifications on a RenderWidgetHost this - // will contain a pointer to it. - RenderWidgetHost* registered_render_widget_host_; - - // Used to cache data that needs to be added to history. Normally entries are - // added to history as the user types, but for instant we only want to add the - // items to history if the user commits instant. So, we cache them here and if - // committed then add the items to history. - AddPageVector add_page_vector_; - - // Are we we waiting for a NavigationType of NEW_PAGE? If we're waiting for - // NEW_PAGE navigation we don't add history items to add_page_vector_. - bool waiting_for_new_page_; - - // True if mouse-pointer or a touch-pointer is down from an activate. + // True if the mouse or a touch pointer is down from an activate. bool is_pointer_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(WebContentsDelegateImpl); }; InstantLoader::WebContentsDelegateImpl::WebContentsDelegateImpl( InstantLoader* loader) - : content::WebContentsObserver(loader->preview_contents()->web_contents()), + : content::WebContentsObserver(loader->preview_contents_->web_contents()), loader_(loader), - registered_render_widget_host_(NULL), - waiting_for_new_page_(true), - is_pointer_down_from_activate_(false), - user_typed_before_load_(false) { - DCHECK(loader->preview_contents()); - registrar_.Add(this, content::NOTIFICATION_INTERSTITIAL_ATTACHED, - content::Source<WebContents>(loader->preview_contents()->web_contents())); -} - -void InstantLoader::WebContentsDelegateImpl::PrepareForNewLoad() { - user_typed_before_load_ = false; - waiting_for_new_page_ = true; - add_page_vector_.clear(); - UnregisterForPaintNotifications(); -} - -void InstantLoader::WebContentsDelegateImpl::PreviewPainted() { - loader_->PreviewPainted(); + is_pointer_down_from_activate_(false) { } -void InstantLoader::WebContentsDelegateImpl::SetLastHistoryURLAndPrune( - const GURL& url) { - if (add_page_vector_.empty()) - return; - - history::HistoryAddPageArgs* args = add_page_vector_.front().get(); - args->url = url; - args->redirects.clear(); - args->redirects.push_back(url); - - // Prune all but the first entry. - add_page_vector_.erase(add_page_vector_.begin() + 1, - add_page_vector_.end()); -} - -void InstantLoader::WebContentsDelegateImpl::CommitHistory( - bool supports_instant) { - TabContents* tab = loader_->preview_contents(); - if (tab->profile()->IsOffTheRecord()) - return; - - for (size_t i = 0; i < add_page_vector_.size(); ++i) { - tab->history_tab_helper()->UpdateHistoryForNavigation( - add_page_vector_[i].get()); - } - - NavigationEntry* active_entry = - tab->web_contents()->GetController().GetActiveEntry(); - if (!active_entry) { - // It appears to be possible to get here with no active entry. This seems - // to be possible with an auth dialog, but I can't narrow down the - // circumstances. If you hit this, file a bug with the steps you did and - // assign it to me (sky). - NOTREACHED(); - return; - } - tab->history_tab_helper()->UpdateHistoryPageTitle(*active_entry); - - FaviconService* favicon_service = - tab->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS); - - if (favicon_service && active_entry->GetFavicon().valid && - !active_entry->GetFavicon().image.IsEmpty()) { - std::vector<unsigned char> image_data; - // TODO: Add all variants once the history service supports it. - gfx::PNGCodec::EncodeBGRASkBitmap( - active_entry->GetFavicon().image.AsBitmap(), false, &image_data); - favicon_service->SetFavicon(active_entry->GetURL(), - active_entry->GetFavicon().url, - image_data, - history::FAVICON); - if (supports_instant && !add_page_vector_.empty()) { - // If we're using the instant API, then we've tweaked the url that is - // going to be added to history. We need to also set the favicon for the - // url we're adding to history (see comment in ReleasePreviewContents - // for details). - favicon_service->SetFavicon(add_page_vector_.back()->url, - active_entry->GetFavicon().url, - image_data, - history::FAVICON); - } - } -} - -void InstantLoader::WebContentsDelegateImpl::RegisterForPaintNotifications( - RenderWidgetHost* render_widget_host) { - DCHECK(registered_render_widget_host_ == NULL); - registered_render_widget_host_ = render_widget_host; - content::Source<RenderWidgetHost> source = - content::Source<RenderWidgetHost>(registered_render_widget_host_); - registrar_.Add( - this, - content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, - source); - registrar_.Add(this, content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED, - source); -} - -void InstantLoader::WebContentsDelegateImpl::UnregisterForPaintNotifications() { - if (registered_render_widget_host_) { - content::Source<RenderWidgetHost> source = - content::Source<RenderWidgetHost>(registered_render_widget_host_); - registrar_.Remove( - this, - content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, - source); - registrar_.Remove(this, content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED, - source); - registered_render_widget_host_ = NULL; - } -} - -void InstantLoader::WebContentsDelegateImpl::Observe( - int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) { - switch (type) { - case content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE: - UnregisterForPaintNotifications(); - PreviewPainted(); - break; - case content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED: - UnregisterForPaintNotifications(); - break; - case content::NOTIFICATION_INTERSTITIAL_ATTACHED: - PreviewPainted(); - break; - default: - NOTREACHED() << "Got a notification we didn't register for."; - } -} - -void InstantLoader::WebContentsDelegateImpl::NavigationStateChanged( - const WebContents* source, - unsigned changed_flags) { - if (!loader_->ready() && !registered_render_widget_host_ && - source->GetController().GetEntryCount()) { - // The load has been committed. Install an observer that waits for the - // first paint then makes the preview active. We wait for the load to be - // committed before waiting on paint as there is always an initial paint - // when a new renderer is created from the resize so that if we showed the - // preview after the first paint we would end up with a white rect. - content::RenderWidgetHostView *rwhv = source->GetRenderWidgetHostView(); - if (rwhv) - RegisterForPaintNotifications(rwhv->GetRenderWidgetHost()); - } else if (source->IsCrashed()) { - PreviewPainted(); - } +bool InstantLoader::WebContentsDelegateImpl::ShouldFocusConstrainedWindow() { + // Return false so that constrained windows are not initially focused. If we + // did otherwise the preview would prematurely get committed when focus goes + // to the constrained window. + return false; } -void InstantLoader::WebContentsDelegateImpl::AddNavigationHeaders( - const GURL& url, - std::string* headers) { - net::HttpUtil::AppendHeaderIfMissing(kInstantHeader, kInstantHeaderValue, - headers); +void InstantLoader::WebContentsDelegateImpl::SwapTabContents( + TabContents* old_tc, + TabContents* new_tc) { + // If this is being called, something is swapping in to our + // |preview_contents_| before we've added it to the tab strip. + loader_->ReplacePreviewContents(old_tc, new_tc); } bool InstantLoader::WebContentsDelegateImpl::ShouldSuppressDialogs() { - // Any message shown during instant cancels instant, so we suppress them. + // Any message shown during Instant cancels Instant, so we suppress them. return true; } -void InstantLoader::WebContentsDelegateImpl::BeforeUnloadFired( - WebContents* tab, - bool proceed, - bool* proceed_to_fire_unload) { -} - -void InstantLoader::WebContentsDelegateImpl::SetFocusToLocationBar( - bool select_all) { -} - bool InstantLoader::WebContentsDelegateImpl::ShouldFocusPageAfterCrash() { return false; } -void InstantLoader::WebContentsDelegateImpl::WebContentsFocused( - WebContents* contents) { - loader_->delegate_->InstantLoaderContentsFocused(); -} - void InstantLoader::WebContentsDelegateImpl::LostCapture() { - CommitOnPointerReleaseIfNecessary(); + CommitFromPointerReleaseIfNecessary(); } -void InstantLoader::WebContentsDelegateImpl::DragEnded() { - CommitOnPointerReleaseIfNecessary(); +void InstantLoader::WebContentsDelegateImpl::WebContentsFocused( + content::WebContents* contents) { + loader_->loader_delegate_->InstantLoaderContentsFocused(loader_); } bool InstantLoader::WebContentsDelegateImpl::CanDownload( - RenderViewHost* render_view_host, - int request_id, - const std::string& request_method) { + content::RenderViewHost* render_view_host, + int request_id, + const std::string& request_method) { // Downloads are disabled. return false; } void InstantLoader::WebContentsDelegateImpl::HandleMouseUp() { - CommitOnPointerReleaseIfNecessary(); + CommitFromPointerReleaseIfNecessary(); } void InstantLoader::WebContentsDelegateImpl::HandlePointerActivate() { @@ -523,7 +151,14 @@ void InstantLoader::WebContentsDelegateImpl::HandleGestureBegin() { } void InstantLoader::WebContentsDelegateImpl::HandleGestureEnd() { - CommitOnPointerReleaseIfNecessary(); + CommitFromPointerReleaseIfNecessary(); +} + +void InstantLoader::WebContentsDelegateImpl::DragEnded() { + // If the user drags, we won't get a mouse up (at least on Linux). Commit the + // Instant result when the drag ends, so that during the drag the page won't + // move around. + CommitFromPointerReleaseIfNecessary(); } bool InstantLoader::WebContentsDelegateImpl::OnGoToEntryOffset(int offset) { @@ -533,40 +168,19 @@ bool InstantLoader::WebContentsDelegateImpl::OnGoToEntryOffset(int offset) { bool InstantLoader::WebContentsDelegateImpl::ShouldAddNavigationToHistory( const history::HistoryAddPageArgs& add_page_args, content::NavigationType navigation_type) { - if (waiting_for_new_page_ && - navigation_type == content::NAVIGATION_TYPE_NEW_PAGE) { - waiting_for_new_page_ = false; - } - - if (!waiting_for_new_page_) { - add_page_vector_.push_back( - scoped_refptr<history::HistoryAddPageArgs>(add_page_args.Clone())); - } - return false; -} - -// If this is being called, something is swapping in to our preview_contents_ -// before we've added it to the tab strip. -void InstantLoader::WebContentsDelegateImpl::SwapTabContents( - TabContents* old_tc, - TabContents* new_tc) { - loader_->ReplacePreviewContents(old_tc, new_tc); -} - -bool InstantLoader::WebContentsDelegateImpl::ShouldFocusConstrainedWindow() { - // Return false so that constrained windows are not initially focused. If - // we did otherwise the preview would prematurely get committed when focus - // goes to the constrained window. + loader_->last_navigation_ = add_page_args.Clone(); return false; } -void InstantLoader::WebContentsDelegateImpl::WillShowConstrainedWindow( - TabContents* source) { - if (!loader_->ready()) { - // A constrained window shown for an auth may not paint. Show the preview - // contents. - UnregisterForPaintNotifications(); - loader_->ShowPreview(); +void InstantLoader::WebContentsDelegateImpl::DidFinishLoad( + int64 frame_id, + const GURL& validated_url, + bool is_main_frame, + content::RenderViewHost* render_view_host) { + if (is_main_frame) { + if (!loader_->supports_instant_) + Send(new ChromeViewMsg_DetermineIfPageSupportsInstant(routing_id())); + loader_->loader_delegate_->InstantLoaderPreviewLoaded(loader_); } } @@ -582,320 +196,114 @@ bool InstantLoader::WebContentsDelegateImpl::OnMessageReceived( return handled; } -void InstantLoader::WebContentsDelegateImpl::DidFailProvisionalLoad( - int64 frame_id, - bool is_main_frame, - const GURL& validated_url, - int error_code, - const string16& error_description, - content::RenderViewHost* render_view_host) { - if (validated_url == loader_->url_) { - // This typically happens with downloads (which are disabled with - // instant active). To ensure the download happens when the user presses - // enter we set needs_reload_ to true, which triggers a reload. - loader_->needs_reload_ = true; - } -} - void InstantLoader::WebContentsDelegateImpl::OnSetSuggestions( - int32 page_id, - const std::vector<std::string>& suggestions, + int page_id, + const std::vector<string16>& suggestions, InstantCompleteBehavior behavior) { - TabContents* source = loader_->preview_contents(); - NavigationEntry* entry = - source->web_contents()->GetController().GetActiveEntry(); - if (!entry || page_id != entry->GetPageID()) - return; - - if (suggestions.empty()) - loader_->SetCompleteSuggestedText(string16(), behavior); - else - loader_->SetCompleteSuggestedText(UTF8ToUTF16(suggestions[0]), behavior); + content::NavigationEntry* entry = loader_->preview_contents_->web_contents()-> + GetController().GetActiveEntry(); + if (entry && page_id == entry->GetPageID()) { + MaybeSetAndNotifyInstantSupportDetermined(true); + loader_->loader_delegate_->SetSuggestions(loader_, suggestions, behavior); + } } void InstantLoader::WebContentsDelegateImpl::OnInstantSupportDetermined( - int32 page_id, + int page_id, bool result) { - WebContents* source = loader_->preview_contents()->web_contents(); - if (!source->GetController().GetActiveEntry() || - page_id != source->GetController().GetActiveEntry()->GetPageID()) - return; + content::NavigationEntry* entry = loader_->preview_contents_->web_contents()-> + GetController().GetActiveEntry(); + if (entry && page_id == entry->GetPageID()) + MaybeSetAndNotifyInstantSupportDetermined(result); +} - content::Details<const bool> details(&result); - content::NotificationService::current()->Notify( - chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED, - content::NotificationService::AllSources(), - details); - if (result) - loader_->PageFinishedLoading(); - else - loader_->PageDoesntSupportInstant(user_typed_before_load_); +void InstantLoader::WebContentsDelegateImpl + ::CommitFromPointerReleaseIfNecessary() { + if (is_pointer_down_from_activate_) { + is_pointer_down_from_activate_ = false; + loader_->loader_delegate_->CommitInstantLoader(loader_); + } } void InstantLoader::WebContentsDelegateImpl - ::CommitOnPointerReleaseIfNecessary() { - bool was_down = is_pointer_down_from_activate_; - is_pointer_down_from_activate_ = false; - if (was_down && loader_->ShouldCommitInstantOnPointerRelease()) - loader_->CommitInstantLoader(); + ::MaybeSetAndNotifyInstantSupportDetermined(bool supports_instant) { + // If we already determined that the loader supports Instant, nothing to do. + if (loader_->supports_instant_) + return; + + loader_->supports_instant_ = supports_instant; + loader_->loader_delegate_->InstantSupportDetermined(loader_, + supports_instant); + + // If the page doesn't support the Instant API, InstantController schedules + // the loader for destruction. Stop sending the controller any more messages, + // by severing the connection from the WebContents to us (its delegate). + if (!supports_instant) + loader_->preview_contents_->web_contents()->SetDelegate(NULL); } // InstantLoader --------------------------------------------------------------- InstantLoader::InstantLoader(InstantLoaderDelegate* delegate, - TemplateURLID id, - const std::string& group) - : delegate_(delegate), - template_url_id_(id), - ready_(false), - http_status_ok_(true), - last_transition_type_(content::PAGE_TRANSITION_LINK), - verbatim_(false), - needs_reload_(false), - group_(group) { + const std::string& instant_url, + const TabContents* tab_contents) + : loader_delegate_(delegate), + preview_contents_(new TabContents(content::WebContents::Create( + tab_contents->profile(), NULL, MSG_ROUTING_NONE, + tab_contents->web_contents(), + tab_contents->web_contents()->GetController(). + GetSessionStorageNamespace()))), + preview_delegate_(new WebContentsDelegateImpl( + ALLOW_THIS_IN_INITIALIZER_LIST(this))), + supports_instant_(false), + instant_url_(instant_url) { } InstantLoader::~InstantLoader() { - registrar_.RemoveAll(); - - // Delete the TabContents before the delegate as the TabContents - // holds a reference to the delegate. if (preview_contents()) - AddPreviewUsageForHistogram(template_url_id_, PREVIEW_DELETED, group_); - preview_contents_.reset(); + preview_contents_->web_contents()->SetDelegate(NULL); } -bool InstantLoader::Update(TabContents* tab_contents, - const TemplateURL* template_url, - const GURL& url, - content::PageTransition transition_type, - const string16& user_text, - bool verbatim, - string16* suggested_text) { - DCHECK(!url.is_empty() && url.is_valid()); - - // Strip leading ?. - string16 new_user_text = - !user_text.empty() && (UTF16ToWide(user_text)[0] == L'?') ? - user_text.substr(1) : user_text; - - // We should preserve the transition type regardless of whether we're already - // showing the url. - last_transition_type_ = transition_type; - - // If state hasn't changed, reuse the last suggestion. There are two cases: - // 1. If no template url (not using instant API), then we only care if the url - // changes. - // 2. Template url (using instant API) then the important part is if the - // user_text changes. - // We have to be careful in checking user_text as in some situations - // InstantController passes in an empty string (when it knows the user_text - // won't matter). - if ((!template_url_id_ && url_ == url) || - (template_url_id_ && - (new_user_text.empty() || user_text_ == new_user_text))) { - suggested_text->assign(last_suggestion_); - // Track the url even if we're not going to update. This is important as - // when we get the suggest text we set user_text_ to the new suggest text, - // but yet the url is much different. - url_ = url; - return false; - } - - url_ = url; - user_text_ = new_user_text; - verbatim_ = verbatim; - last_suggestion_.clear(); - needs_reload_ = false; - - bool created_preview_contents = preview_contents_.get() == NULL; - if (created_preview_contents) - CreatePreviewContents(tab_contents); - - // Carry over the user agent override setting to the new entry. - content::NavigationEntry* entry = - tab_contents->web_contents()->GetController().GetActiveEntry(); - bool override_user_agent = entry && entry->GetIsOverridingUserAgent(); - - if (template_url) { - DCHECK(template_url_id_ == template_url->id()); - if (!created_preview_contents) { - if (is_determining_if_page_supports_instant()) { - // The page hasn't loaded yet. Note it, but send down the text anyway. - frame_load_observer_->set_text(user_text_); - frame_load_observer_->set_verbatim(verbatim); - preview_tab_contents_delegate_->set_user_typed_before_load(); - } - // TODO: support real cursor position. - int text_length = static_cast<int>(user_text_.size()); - RenderViewHost* host = - preview_contents_->web_contents()->GetRenderViewHost(); - host->Send(new ChromeViewMsg_SearchBoxChange( - host->GetRoutingID(), - user_text_, - verbatim, - text_length, - text_length)); - - string16 complete_suggested_text_lower = base::i18n::ToLower( - complete_suggested_text_); - string16 user_text_lower = base::i18n::ToLower(user_text_); - if (!verbatim && - complete_suggested_text_lower.size() > user_text_lower.size() && - !complete_suggested_text_lower.compare(0, user_text_lower.size(), - user_text_lower)) { - *suggested_text = last_suggestion_ = - complete_suggested_text_.substr(user_text_.size()); - } - } else { - LoadInstantURL(template_url, transition_type, user_text_, verbatim, - override_user_agent); - } - } else { - DCHECK(template_url_id_ == 0); - preview_tab_contents_delegate_->PrepareForNewLoad(); - frame_load_observer_.reset(NULL); - - preview_contents_->web_contents()->GetController(). - LoadURLWithUserAgentOverride(url_, content::Referrer(), transition_type, - false, std::string(), override_user_agent); - } - return true; +void InstantLoader::Init() { + SetupPreviewContents(); + // This HTTP header and value are set on loads that originate from instant. + const char* const kInstantHeader = "X-Purpose: Instant"; + preview_contents_->web_contents()->GetController().LoadURL(GURL(instant_url_), + content::Referrer(), content::PAGE_TRANSITION_GENERATED, kInstantHeader); + preview_contents_->web_contents()->WasHidden(); } -void InstantLoader::SetOmniboxBounds(const gfx::Rect& bounds) { - if (omnibox_bounds_ == bounds) - return; - - // Don't update the page while the mouse is down. http://crbug.com/71952 - if (IsPointerDownFromActivate()) - return; - - omnibox_bounds_ = bounds; - if (preview_contents_.get() && is_showing_instant() && - !is_determining_if_page_supports_instant()) { - // Updating the bounds is rather expensive, and because of the async nature - // of the omnibox the bounds can dance around a bit. Delay the update in - // hopes of things settling down. To avoid hiding results we grow - // immediately, but delay shrinking. - update_bounds_timer_.Stop(); - if (omnibox_bounds_.height() > last_omnibox_bounds_.height()) { - SendBoundsToPage(false); - } else { - update_bounds_timer_.Start(FROM_HERE, - base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), - this, &InstantLoader::ProcessBoundsChange); - } - } +void InstantLoader::Update(const string16& user_text, bool verbatim) { + // TODO: Support real cursor position. + last_navigation_ = NULL; + content::RenderViewHost* rvh = + preview_contents_->web_contents()->GetRenderViewHost(); + rvh->Send(new ChromeViewMsg_SearchBoxChange(rvh->GetRoutingID(), user_text, + verbatim, user_text.size(), user_text.size())); } -bool InstantLoader::IsPointerDownFromActivate() { - return preview_tab_contents_delegate_.get() && - preview_tab_contents_delegate_->is_pointer_down_from_activate(); +void InstantLoader::SetOmniboxBounds(const gfx::Rect& bounds) { + content::RenderViewHost* rvh = + preview_contents_->web_contents()->GetRenderViewHost(); + rvh->Send(new ChromeViewMsg_SearchBoxResize(rvh->GetRoutingID(), bounds)); } -TabContents* InstantLoader::ReleasePreviewContents( - InstantCommitType type, - TabContents* tab_contents) { - if (!preview_contents_.get()) - return NULL; - - // FrameLoadObserver is only used for instant results, and instant results are - // only committed if active (when the FrameLoadObserver isn't installed). - DCHECK(type == INSTANT_COMMIT_DESTROY || !frame_load_observer_.get()); - - if (type != INSTANT_COMMIT_DESTROY && is_showing_instant()) { - RenderViewHost* host = - preview_contents_->web_contents()->GetRenderViewHost(); - if (type == INSTANT_COMMIT_FOCUS_LOST) { - host->Send(new ChromeViewMsg_SearchBoxCancel(host->GetRoutingID())); - } else { - host->Send(new ChromeViewMsg_SearchBoxSubmit( - host->GetRoutingID(), user_text_, - type == INSTANT_COMMIT_PRESSED_ENTER)); - } - } - omnibox_bounds_ = gfx::Rect(); - last_omnibox_bounds_ = gfx::Rect(); - GURL url; - url.Swap(&url_); - user_text_.clear(); - complete_suggested_text_.clear(); - if (preview_contents_.get()) { - if (type != INSTANT_COMMIT_DESTROY) { - if (template_url_id_) { - // The URL used during instant is mostly gibberish, and not something - // we'll parse and match as a past search. Set it to something we can - // parse. - preview_tab_contents_delegate_->SetLastHistoryURLAndPrune(url); - } - preview_tab_contents_delegate_->CommitHistory(template_url_id_ != 0); - } - if (preview_contents_->web_contents()->GetRenderWidgetHostView()) { -#if defined(OS_MACOSX) - preview_contents_->web_contents()->GetRenderWidgetHostView()-> - SetTakesFocusOnlyOnMouseDown(false); - registrar_.Remove( - this, - content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED, - content::Source<NavigationController>( - &preview_contents_->web_contents()->GetController())); -#endif - } - preview_contents_->web_contents()->SetDelegate(NULL); - ready_ = false; - } - update_bounds_timer_.Stop(); - AddPreviewUsageForHistogram(template_url_id_, - type == INSTANT_COMMIT_DESTROY ? PREVIEW_DELETED : PREVIEW_COMMITTED, - group_); - if (type != INSTANT_COMMIT_DESTROY) { - base::Histogram* histogram = base::LinearHistogram::FactoryGet( - "Instant.SessionStorageNamespace" + group_, 1, 2, 3, - base::Histogram::kUmaTargetedHistogramFlag); - histogram->Add(tab_contents == NULL || session_storage_namespace_ == - GetSessionStorageNamespace(tab_contents)); - // Now that the ownership is being passed to the caller, the thumbnailer - // needs to resume taking thumbnails. - if (preview_contents_->thumbnail_generator()) - preview_contents_->thumbnail_generator()->set_enabled(true); +TabContents* InstantLoader::ReleasePreviewContents(InstantCommitType type, + const string16& text) { + content::RenderViewHost* rvh = + preview_contents_->web_contents()->GetRenderViewHost(); + if (type == INSTANT_COMMIT_PRESSED_ENTER) { + rvh->Send(new ChromeViewMsg_SearchBoxSubmit(rvh->GetRoutingID(), text)); + } else { + rvh->Send(new ChromeViewMsg_SearchBoxCancel(rvh->GetRoutingID(), text)); } - session_storage_namespace_ = NULL; + CleanupPreviewContents(); return preview_contents_.release(); } -bool InstantLoader::ShouldCommitInstantOnPointerRelease() { - return delegate_->ShouldCommitInstantOnPointerRelease(); -} - -void InstantLoader::CommitInstantLoader() { - delegate_->CommitInstantLoader(this); -} - -void InstantLoader::MaybeLoadInstantURL(TabContents* tab_contents, - const TemplateURL* template_url) { - DCHECK(template_url_id_ == template_url->id()); - - // If we already have a |preview_contents_|, future search queries will be - // issued into it (see the "if (!created_preview_contents)" block in |Update| - // above), so there is no need to load the |template_url|'s instant URL. - if (preview_contents_.get()) - return; - - // Carry over the user agent override setting to the new entry. - content::NavigationEntry* entry = - tab_contents->web_contents()->GetController().GetActiveEntry(); - bool override_user_agent = entry && entry->GetIsOverridingUserAgent(); - - CreatePreviewContents(tab_contents); - LoadInstantURL(template_url, content::PAGE_TRANSITION_GENERATED, string16(), - true, override_user_agent); -} - -bool InstantLoader::IsNavigationPending() const { - return preview_contents_.get() && - preview_contents_->web_contents()->GetController().GetPendingEntry(); +bool InstantLoader::IsPointerDownFromActivate() const { + return preview_delegate_->is_pointer_down_from_activate(); } void InstantLoader::Observe(int type, @@ -903,301 +311,75 @@ void InstantLoader::Observe(int type, const content::NotificationDetails& details) { #if defined(OS_MACOSX) if (type == content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED) { - if (preview_contents_->web_contents()->GetRenderWidgetHostView()) { - preview_contents_->web_contents()->GetRenderWidgetHostView()-> - SetTakesFocusOnlyOnMouseDown(true); + if (content::RenderWidgetHostView* rwhv = + preview_contents_->web_contents()->GetRenderWidgetHostView()) { + rwhv->SetTakesFocusOnlyOnMouseDown(true); } return; } + NOTREACHED(); #endif - if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED) { - content::LoadCommittedDetails* load_details = - content::Details<content::LoadCommittedDetails>(details).ptr(); - if (load_details->is_main_frame) { - if (load_details->http_status_code == kHostBlacklistStatusCode) { - delegate_->AddToBlacklist(this, load_details->entry->GetURL()); - } else { - SetHTTPStatusOK(load_details->http_status_code == 200); - } - } - return; - } - - NOTREACHED() << "Got a notification we didn't register for."; } -void InstantLoader::SetCompleteSuggestedText( - const string16& complete_suggested_text, - InstantCompleteBehavior behavior) { - if (!is_showing_instant()) { - // We're not trying to use the instant API with this page. Ignore it. - return; - } - - ShowPreview(); - - if (complete_suggested_text == complete_suggested_text_) - return; +void InstantLoader::SetupPreviewContents() { + content::WebContents* new_contents = preview_contents_->web_contents(); + WebContentsDelegateImpl* new_delegate = preview_delegate_.get(); + new_contents->SetDelegate(new_delegate); - if (verbatim_) { - // Don't show suggest results for verbatim queries. - return; - } - - string16 user_text_lower = base::i18n::ToLower(user_text_); - string16 complete_suggested_text_lower = base::i18n::ToLower( - complete_suggested_text); - last_suggestion_.clear(); - if (user_text_lower.compare(0, user_text_lower.size(), - complete_suggested_text_lower, - 0, user_text_lower.size())) { - // The user text no longer contains the suggested text, ignore it. - complete_suggested_text_.clear(); - delegate_->SetSuggestedTextFor(this, string16(), behavior); - return; - } - - complete_suggested_text_ = complete_suggested_text; - if (behavior == INSTANT_COMPLETE_NOW) { - // We are effectively showing complete_suggested_text_ now. Update - // user_text_ so we don't notify the page again if Update happens to be - // invoked (which is more than likely if this callback completes before the - // omnibox is done). - string16 suggestion = complete_suggested_text_.substr(user_text_.size()); - user_text_ = complete_suggested_text_; - delegate_->SetSuggestedTextFor(this, suggestion, behavior); - } else { - DCHECK((behavior == INSTANT_COMPLETE_DELAYED) || - (behavior == INSTANT_COMPLETE_NEVER)); - last_suggestion_ = complete_suggested_text_.substr(user_text_.size()); - delegate_->SetSuggestedTextFor(this, last_suggestion_, behavior); - } -} - -void InstantLoader::PreviewPainted() { - // If instant is supported then we wait for the first suggest result before - // showing the page. - if (!template_url_id_) - ShowPreview(); -} - -void InstantLoader::SetHTTPStatusOK(bool is_ok) { - if (is_ok == http_status_ok_) - return; - - http_status_ok_ = is_ok; - if (ready_) - delegate_->InstantStatusChanged(this); -} - -void InstantLoader::ShowPreview() { - if (!ready_) { - ready_ = true; - delegate_->InstantStatusChanged(this); - AddPreviewUsageForHistogram(template_url_id_, PREVIEW_SHOWN, group_); - } -} - -void InstantLoader::PageFinishedLoading() { - frame_load_observer_.reset(); - - // Send the bounds of the omnibox down now. - SendBoundsToPage(false); - - // Wait for the user input before showing, this way the page should be up to - // date by the time we show it. - AddPreviewUsageForHistogram(template_url_id_, PREVIEW_LOADED, group_); -} - -// 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() { - gfx::Rect preview_bounds(delegate_->GetInstantBounds()); - gfx::Rect intersection(omnibox_bounds_.Intersect(preview_bounds)); + // Disable popups and such (mainly to avoid losing focus and reverting the + // preview prematurely). + preview_contents_->blocked_content_tab_helper()->SetAllContentsBlocked(true); + preview_contents_->constrained_window_tab_helper()->set_delegate( + new_delegate); + preview_contents_->content_settings()->SetPopupsBlocked(true); + preview_contents_->core_tab_helper()->set_delegate(new_delegate); + if (ThumbnailGenerator* tg = preview_contents_->thumbnail_generator()) + tg->set_enabled(false); - // Translate into window's coordinates. - if (!intersection.IsEmpty()) { - intersection.Offset(-preview_bounds.origin().x(), - -preview_bounds.origin().y()); +#if defined(OS_MACOSX) + // If |preview_contents_| does not currently have a RWHV, we will call + // SetTakesFocusOnlyOnMouseDown() as a result of the RENDER_VIEW_HOST_CHANGED + // notification. + if (content::RenderWidgetHostView* rwhv = + new_contents->GetRenderWidgetHostView()) { + rwhv->SetTakesFocusOnlyOnMouseDown(true); } - - // 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. - // There is no point in sanity-checking |intersection.y()| because the omnibox - // can be placed anywhere vertically relative to the preview (for example, in - // Mac fullscreen mode, the omnibox is entirely enclosed by the preview - // bounds). - DCHECK_LE(0, intersection.x()); - DCHECK_LE(0, intersection.width()); - DCHECK_LE(0, intersection.height()); - - return intersection; -} - -void InstantLoader::PageDoesntSupportInstant(bool needs_reload) { - frame_load_observer_.reset(NULL); - - delegate_->InstantLoaderDoesntSupportInstant(this); - - AddPreviewUsageForHistogram(template_url_id_, PREVIEW_LOADED, group_); + registrar_.Add(this, content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED, + content::Source<content::NavigationController>( + &new_contents->GetController())); +#endif } -void InstantLoader::ProcessBoundsChange() { - SendBoundsToPage(false); -} +void InstantLoader::CleanupPreviewContents() { + content::WebContents* old_contents = preview_contents_->web_contents(); + old_contents->SetDelegate(NULL); -void InstantLoader::SendBoundsToPage(bool force_if_waiting) { - if (last_omnibox_bounds_ == omnibox_bounds_) - return; + preview_contents_->blocked_content_tab_helper()->SetAllContentsBlocked(false); + preview_contents_->constrained_window_tab_helper()->set_delegate(NULL); + preview_contents_->content_settings()->SetPopupsBlocked(false); + preview_contents_->core_tab_helper()->set_delegate(NULL); + if (ThumbnailGenerator* tg = preview_contents_->thumbnail_generator()) + tg->set_enabled(true); - if (preview_contents_.get() && is_showing_instant() && - (force_if_waiting || !is_determining_if_page_supports_instant())) { - last_omnibox_bounds_ = omnibox_bounds_; - RenderViewHost* host = - preview_contents_->web_contents()->GetRenderViewHost(); - host->Send(new ChromeViewMsg_SearchBoxResize( - host->GetRoutingID(), GetOmniboxBoundsInTermsOfPreview())); +#if defined(OS_MACOSX) + if (content::RenderWidgetHostView* rwhv = + old_contents->GetRenderWidgetHostView()) { + rwhv->SetTakesFocusOnlyOnMouseDown(false); } + registrar_.Remove(this, content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED, + content::Source<content::NavigationController>( + &old_contents->GetController())); +#endif } void InstantLoader::ReplacePreviewContents(TabContents* old_tc, TabContents* new_tc) { DCHECK(old_tc == preview_contents_); - // We release here without deleting so that the caller still has reponsibility - // for deleting the TabContents. + CleanupPreviewContents(); + // We release here without deleting so that the caller still has the + // responsibility for deleting the TabContents. ignore_result(preview_contents_.release()); preview_contents_.reset(new_tc); - session_storage_namespace_ = GetSessionStorageNamespace(new_tc); - - // Make sure the new preview contents acts like the old one. - SetupPreviewContents(old_tc); - - // Cleanup the old preview contents. - old_tc->constrained_window_tab_helper()->set_delegate(NULL); - old_tc->core_tab_helper()->set_delegate(NULL); - old_tc->web_contents()->SetDelegate(NULL); - -#if defined(OS_MACOSX) - registrar_.Remove( - this, - content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED, - content::Source<NavigationController>( - &old_tc->web_contents()->GetController())); -#endif - registrar_.Remove( - this, - content::NOTIFICATION_NAV_ENTRY_COMMITTED, - content::Source<NavigationController>( - &old_tc->web_contents()->GetController())); - - // We prerendered so we should be ready to show. If we're ready, swap in - // immediately, otherwise show the preview as normal. - if (ready_) - delegate_->SwappedTabContents(this); - else - ShowPreview(); -} - -void InstantLoader::SetupPreviewContents(TabContents* tab_contents) { - preview_contents_->web_contents()->SetDelegate( - preview_tab_contents_delegate_.get()); - preview_contents_->blocked_content_tab_helper()->SetAllContentsBlocked(true); - preview_contents_->constrained_window_tab_helper()->set_delegate( - preview_tab_contents_delegate_.get()); - preview_contents_->core_tab_helper()->set_delegate( - preview_tab_contents_delegate_.get()); - // Disables thumbnailing while the web contents is shown as preview to avoid - // generating unnecessary thumbnails. - if (preview_contents_->thumbnail_generator()) - preview_contents_->thumbnail_generator()->set_enabled(false); - -#if defined(OS_MACOSX) - // If |preview_contents_| does not currently have a RWHV, we will call - // SetTakesFocusOnlyOnMouseDown() as a result of the - // RENDER_VIEW_HOST_CHANGED notification. - if (preview_contents_->web_contents()->GetRenderWidgetHostView()) { - preview_contents_->web_contents()->GetRenderWidgetHostView()-> - SetTakesFocusOnlyOnMouseDown(true); - } - registrar_.Add( - this, - content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED, - content::Source<NavigationController>( - &preview_contents_->web_contents()->GetController())); -#endif - - registrar_.Add( - this, - content::NOTIFICATION_NAV_ENTRY_COMMITTED, - content::Source<NavigationController>( - &preview_contents_->web_contents()->GetController())); - - gfx::Rect tab_bounds; - tab_contents->web_contents()->GetView()->GetContainerBounds(&tab_bounds); - preview_contents_->web_contents()->GetView()->SizeContents(tab_bounds.size()); - - // Carry over the user agent override string. - const std::string& override = - tab_contents->web_contents()->GetUserAgentOverride(); - preview_contents_->web_contents()->SetUserAgentOverride(override); -} - -void InstantLoader::CreatePreviewContents(TabContents* tab_contents) { - WebContents* new_contents = WebContents::Create( - tab_contents->profile(), NULL, MSG_ROUTING_NONE, NULL, NULL); - preview_contents_.reset(new TabContents(new_contents)); - AddPreviewUsageForHistogram(template_url_id_, PREVIEW_CREATED, group_); - session_storage_namespace_ = GetSessionStorageNamespace(tab_contents); - preview_tab_contents_delegate_.reset(new WebContentsDelegateImpl(this)); - SetupPreviewContents(tab_contents); - - // TODO(beng): investigate if we can avoid this and instead rely on the - // visibility of the WebContentsView - preview_contents_->web_contents()->WasShown(); -} - -void InstantLoader::LoadInstantURL(const TemplateURL* template_url, - content::PageTransition transition_type, - const string16& user_text, - bool verbatim, - bool override_user_agent) { - preview_tab_contents_delegate_->PrepareForNewLoad(); - - // Load the instant URL. We don't reflect the url we load in url() as - // callers expect that we're loading the URL they tell us to. - // - // This uses an empty string for the replacement text as the url doesn't - // really have the search params, but we need to use the replace - // functionality so that embeded tags (like {google:baseURL}) are escaped - // correctly. - // TODO(sky): having to use a replaceable url is a bit of a hack here. - GURL instant_url(template_url->instant_url_ref().ReplaceSearchTerms( - TemplateURLRef::SearchTermsArgs(string16()))); - CommandLine* cl = CommandLine::ForCurrentProcess(); - if (cl->HasSwitch(switches::kInstantURL)) - instant_url = GURL(cl->GetSwitchValueASCII(switches::kInstantURL)); - - preview_contents_->web_contents()->GetController(). - LoadURLWithUserAgentOverride(instant_url, content::Referrer(), - transition_type, false, std::string(), override_user_agent); - - RenderViewHost* host = preview_contents_->web_contents()->GetRenderViewHost(); - preview_contents_->web_contents()->WasHidden(); - - // If user_text is empty, this must be a preload of the search homepage. In - // that case, send down a SearchBoxResize message, which will switch the page - // to "search results" UI. This avoids flicker when the page is shown with - // results. In addition, we don't want the page accidentally causing the - // preloaded page to be displayed yet (by calling setSuggestions), so don't - // send a SearchBoxChange message. - if (user_text.empty()) { - host->Send(new ChromeViewMsg_SearchBoxResize( - host->GetRoutingID(), GetOmniboxBoundsInTermsOfPreview())); - } else { - host->Send(new ChromeViewMsg_SearchBoxChange( - host->GetRoutingID(), user_text, verbatim, 0, 0)); - } - - frame_load_observer_.reset(new FrameLoadObserver( - this, preview_contents()->web_contents(), user_text, verbatim)); + SetupPreviewContents(); + loader_delegate_->SwappedTabContents(this); } diff --git a/chrome/browser/instant/instant_loader.h b/chrome/browser/instant/instant_loader.h index 43f74fb..7ec5f26 100644 --- a/chrome/browser/instant/instant_loader.h +++ b/chrome/browser/instant/instant_loader.h @@ -8,256 +8,124 @@ #include <string> #include "base/basictypes.h" +#include "base/compiler_specific.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/string16.h" -#include "base/timer.h" #include "chrome/browser/instant/instant_commit_type.h" -#include "chrome/browser/search_engines/template_url_id.h" -#include "chrome/common/instant_types.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" -#include "content/public/common/page_transition_types.h" -#include "googleurl/src/gurl.h" -#include "ui/gfx/rect.h" class InstantLoaderDelegate; -class InstantLoaderManagerTest; class TabContents; -class TemplateURL; namespace content { -class SessionStorageNamespace; +class NotificationDetails; +class NotificationSource; } -// InstantLoader does the loading of a particular URL for InstantController. -// InstantLoader notifies its delegate, which is typically InstantController, of -// all interesting events. -// -// InstantLoader is created with a TemplateURLID. If non-zero InstantLoader -// first determines if the site actually supports instant. If it doesn't, the -// delegate is notified by way of |InstantLoaderDoesntSupportInstant|. -// -// If the TemplateURLID supplied to the constructor is zero, then the url is -// loaded as is. +namespace gfx { +class Rect; +} + +namespace history { +class HistoryAddPageArgs; +} + +// InstantLoader is created with an "Instant URL". It loads the URL and tells +// its delegate (usually InstantController) of all interesting events. For +// example, it determines if the page actually supports the Instant API +// (http://dev.chromium.org/searchbox) and forwards messages (such as queries +// and autocomplete suggestions) between the page and the delegate. class InstantLoader : public content::NotificationObserver { public: - // Header and value set on loads that originate from instant. - static const char* const kInstantHeader; - static const char* const kInstantHeaderValue; - - // |group| is an identifier suffixed to histograms to distinguish field trial - // statistics from regular operation; can be a blank string. + // Creates a new empty WebContents. Use Init() to actually load |instant_url|. + // |tab_contents| is the page the preview will be shown on top of and + // potentially replace. |instant_url| is typically the instant_url field of + // the default search engine's TemplateURL, with the "{searchTerms}" parameter + // replaced with an empty string. InstantLoader(InstantLoaderDelegate* delegate, - TemplateURLID id, - const std::string& group); + const std::string& instant_url, + const TabContents* tab_contents); virtual ~InstantLoader(); - // Invoked to load a URL. |tab_contents| is the TabContents the preview - // is going to be shown on top of and potentially replace. Returns true if the - // arguments differ from the last call to |Update|. - bool Update(TabContents* tab_contents, - const TemplateURL* template_url, - const GURL& url, - content::PageTransition transition_type, - const string16& user_text, - bool verbatim, - string16* suggested_text); + // Initializes |preview_contents_| and loads |instant_url_|. + void Init(); - // Sets the bounds of the omnibox (in screen coordinates). The bounds are - // remembered until the preview is committed or destroyed. This is only used - // when showing results for a search provider that supports instant. - void SetOmniboxBounds(const gfx::Rect& bounds); + // Tells the preview page that the user typed |user_text| into the omnibox. + // If |verbatim| is false, the page predicts the query the user means to type + // and fetches results for the prediction. If |verbatim| is true, |user_text| + // is taken as the exact query (no prediction is made). + void Update(const string16& user_text, bool verbatim); - // Returns true if the mouse or a touch-pointer is down as the result of - // activating the preview content. - bool IsPointerDownFromActivate(); + // Tells the preview page of the bounds of the omnibox dropdown (in screen + // coordinates). This is used by the page to offset the results to avoid them + // being covered by the omnibox dropdown. + void SetOmniboxBounds(const gfx::Rect& bounds); - // Releases the preview TabContents passing ownership to the caller. - // This is intended to be called when the preview TabContents is - // committed. This does not notify the delegate. |tab_contents| is the - // underlying tab onto which the preview will be committed. It can be NULL - // when the underlying tab is irrelevant, for example when |type| is - // INSTANT_COMMIT_DESTROY. + // Releases the preview TabContents passing ownership to the caller. This + // should be called when the preview is committed. Notifies the page but not + // the delegate. |text| is the final omnibox text being committed. NOTE: The + // caller should destroy this loader object right after this method, since + // none of the other methods will work once the preview has been released. TabContents* ReleasePreviewContents(InstantCommitType type, - TabContents* tab_contents); + const string16& text) WARN_UNUSED_RESULT; - // Calls through to method of same name on delegate. - bool ShouldCommitInstantOnPointerRelease(); - void CommitInstantLoader(); + // The preview TabContents. The loader retains ownership. This will be + // non-NULL until ReleasePreviewContents() is called. + TabContents* preview_contents() const { return preview_contents_.get(); } - // Preload |template_url|'s instant URL, if the loader doesn't already have - // a |preview_contents()| for it. - void MaybeLoadInstantURL(TabContents* tab_contents, - const TemplateURL* template_url); + // Returns true if the preview page is known to support the Instant API. This + // starts out false, and becomes true whenever we get any message from the + // page. Once true, it never becomes false (the page isn't expected to drop + // Instant API support suddenly). + bool supports_instant() const { return supports_instant_; } - // Returns true if the preview NavigationController's WebContents has a - // pending NavigationEntry. - bool IsNavigationPending() const; + // Returns the URL that we're loading. + const std::string& instant_url() const { return instant_url_; } + + // Returns info about the last navigation by the Instant page. If the page + // hasn't navigated since the last Update(), this contains NULL. + scoped_refptr<history::HistoryAddPageArgs> last_navigation() const { + return last_navigation_; + } + + // Returns true if the mouse or a touch pointer is down due to activating the + // preview content. + bool IsPointerDownFromActivate() const; // content::NotificationObserver: virtual void Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) OVERRIDE; - // The preview TabContents; may be null. - TabContents* preview_contents() const { - return preview_contents_.get(); - } - - // Returns true if the preview TabContents is ready to be shown. A - // non-instant loader is ready once the renderer paints, otherwise it isn't - // ready until we get a response back from the page. - bool ready() const { return ready_; } - - // Returns true if the current load returned a 200. - bool http_status_ok() const { return http_status_ok_; } - - // Returns true if the url needs to be reloaded. This is set to true for - // downloads. - bool needs_reload() const { return needs_reload_; } - - const GURL& url() const { return url_; } - - bool verbatim() const { return verbatim_; } - - // Are we showing instant results? - bool is_showing_instant() const { return template_url_id_ != 0; } - - // If we're showing instant this returns non-zero. - TemplateURLID template_url_id() const { return template_url_id_; } - - // See description above field. - const string16& user_text() const { return user_text_; } - - // Are we waiting for the preview page to finish loading and to determine if - // it supports instant? - bool is_determining_if_page_supports_instant() const { - return frame_load_observer_.get() != NULL; - } - private: - friend class InstantLoaderManagerTest; - friend class InstantTest; - class FrameLoadObserver; - class PaintObserverImpl; class WebContentsDelegateImpl; - // Invoked when the page wants to update the suggested text. If |user_text_| - // starts with |suggested_text|, then the delegate is notified of the change, - // which results in updating the omnibox. - void SetCompleteSuggestedText(const string16& suggested_text, - InstantCompleteBehavior behavior); - - // Invoked when the page paints. - void PreviewPainted(); - - // Invoked when the http status code changes. This may notify the delegate. - void SetHTTPStatusOK(bool is_ok); - - // Invoked to show the preview. This is invoked in two possible cases: when - // the renderer paints, or when an auth dialog is shown. This notifies the - // delegate the preview is ready to be shown. - void ShowPreview(); + void SetupPreviewContents(); + void CleanupPreviewContents(); + void ReplacePreviewContents(TabContents* old_tc, TabContents* new_tc); - // Invoked once the page has finished loading and the script has been sent. - void PageFinishedLoading(); + InstantLoaderDelegate* const loader_delegate_; - // Returns the bounds of the omnibox in terms of the preview tab contents. - gfx::Rect GetOmniboxBoundsInTermsOfPreview(); - - // Invoked if it the page doesn't really support instant when we thought it - // did. If |needs_reload| is true, the text changed since the first load and - // the page needs to be reloaded. - void PageDoesntSupportInstant(bool needs_reload); - - // Invokes |SetBoundsToPage(false)|. This is called from the timer. - void ProcessBoundsChange(); - - // Notifes the page of the omnibox bounds. If |force_if_loading| is true the - // bounds are sent down even if we're waiting on the load, otherwise if we're - // waiting on the load and |force_if_loading| is false this does nothing. - void SendBoundsToPage(bool force_if_loading); - - // Called when the TabContentsDelegate wants to swap a new TabContents - // into our |preview_contents_|. - void ReplacePreviewContents(TabContents* old_tc, - TabContents* new_tc); - - // Called to set up the |preview_contents_| based on |tab_contents| when it is - // created or replaced. - void SetupPreviewContents(TabContents* tab_contents); - - // Creates and sets the preview TabContents. - void CreatePreviewContents(TabContents* tab_contents); - - // Creates and loads the |template_url|'s instant URL. - void LoadInstantURL(const TemplateURL* template_url, - content::PageTransition transition_type, - const string16& user_text, - bool verbatim, - bool override_user_agent); - - InstantLoaderDelegate* delegate_; - - // If we're showing instant results this is the ID of the TemplateURL driving - // the results. A value of 0 means there is no TemplateURL. - const TemplateURLID template_url_id_; - - // The url we're displaying. - GURL url_; + // See comments on the getter above. + scoped_ptr<TabContents> preview_contents_; // Delegate of the preview WebContents. Used to detect when the user does some // gesture on the WebContents and the preview needs to be activated. - scoped_ptr<WebContentsDelegateImpl> preview_tab_contents_delegate_; - - // The preview TabContents; may be null. - scoped_ptr<TabContents> preview_contents_; - - // Is the preview_contents ready to be shown? - bool ready_; - - // Was the last status code a 200? - bool http_status_ok_; - - // The text the user typed in the omnibox, stripped of the leading ?, if any. - string16 user_text_; - - // The latest suggestion from the page. - string16 complete_suggested_text_; - - // The latest suggestion (suggested text less the user text). - string16 last_suggestion_; + scoped_ptr<WebContentsDelegateImpl> preview_delegate_; - // See description above setter. - gfx::Rect omnibox_bounds_; + // See comments on the getter above. + bool supports_instant_; - // Last bounds passed to the page. - gfx::Rect last_omnibox_bounds_; - - scoped_ptr<FrameLoadObserver> frame_load_observer_; - - // Transition type of the match last passed to Update. - content::PageTransition last_transition_type_; - - // Timer used to update the bounds of the omnibox. - base::OneShotTimer<InstantLoader> update_bounds_timer_; + // See comments on the getter above. + const std::string instant_url_; // Used to get notifications about renderers coming and going. content::NotificationRegistrar registrar_; - // Last value of verbatim passed to |Update|. - bool verbatim_; - - // True if the page needs to be reloaded. - bool needs_reload_; - - // See description above constructor. - std::string group_; - - // The session storage namespace identifier of the original tab contents that - // the preview_contents_ was based upon. - scoped_refptr<content::SessionStorageNamespace> session_storage_namespace_; + // See comments on the getter above. + scoped_refptr<history::HistoryAddPageArgs> last_navigation_; DISALLOW_COPY_AND_ASSIGN(InstantLoader); }; diff --git a/chrome/browser/instant/instant_loader_delegate.h b/chrome/browser/instant/instant_loader_delegate.h index 9de5171..8fbd1f9 100644 --- a/chrome/browser/instant/instant_loader_delegate.h +++ b/chrome/browser/instant/instant_loader_delegate.h @@ -5,51 +5,41 @@ #ifndef CHROME_BROWSER_INSTANT_INSTANT_LOADER_DELEGATE_H_ #define CHROME_BROWSER_INSTANT_INSTANT_LOADER_DELEGATE_H_ +#include <vector> + #include "base/string16.h" #include "chrome/common/instant_types.h" -class GURL; - -namespace gfx { -class Rect; -} - class InstantLoader; -// InstantLoader's delegate. This interface is implemented by InstantController. +// InstantLoader calls these methods on its delegate (InstantController) +// to notify it of interesting events happening on the Instant preview. class InstantLoaderDelegate { public: - // Invoked when the status (either http_status_ok or ready) has changed. - virtual void InstantStatusChanged(InstantLoader* loader) = 0; - // Invoked when the loader has suggested text. - virtual void SetSuggestedTextFor( + virtual void SetSuggestions( InstantLoader* loader, - const string16& text, + const std::vector<string16>& suggestions, InstantCompleteBehavior behavior) = 0; - // Returns the bounds of instant. - virtual gfx::Rect GetInstantBounds() = 0; - - // Returns true if instant should be committed on mouse up or at the end of a - // touch-gesture. - virtual bool ShouldCommitInstantOnPointerRelease() = 0; - - // Invoked when the the loader should be committed. + // Commit the preview. virtual void CommitInstantLoader(InstantLoader* loader) = 0; - // Invoked if the loader was created with the intention that the site supports - // instant, but it turned out the site doesn't support instant. - virtual void InstantLoaderDoesntSupportInstant(InstantLoader* loader) = 0; + // Notification that the first page load (of the Instant URL) completed. + virtual void InstantLoaderPreviewLoaded(InstantLoader* loader) = 0; - // Adds the specified url to the set of urls instant won't prefetch for. - virtual void AddToBlacklist(InstantLoader* loader, const GURL& url) = 0; + // Notification when the loader has determined whether or not the page + // supports the Instant API. + virtual void InstantSupportDetermined(InstantLoader* loader, + bool supports_instant) = 0; - // Invoked if the loader swaps to a different WebContents. + // Notification that the loader swapped a different TabContents into the + // preview, usually because a prerendered page was navigated to. virtual void SwappedTabContents(InstantLoader* loader) = 0; - // Invoked when the webcontents created by the loader is focused. - virtual void InstantLoaderContentsFocused() = 0; + // Notification that the preview gained focus, usually due to the user + // clicking on it. + virtual void InstantLoaderContentsFocused(InstantLoader* loader) = 0; protected: virtual ~InstantLoaderDelegate() {} diff --git a/chrome/browser/instant/instant_unload_handler.cc b/chrome/browser/instant/instant_unload_handler.cc index d423208..b18c323 100644 --- a/chrome/browser/instant/instant_unload_handler.cc +++ b/chrome/browser/instant/instant_unload_handler.cc @@ -6,7 +6,6 @@ #include <algorithm> -#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_navigator.h" #include "chrome/browser/ui/tab_contents/tab_contents.h" #include "content/public/browser/render_view_host.h" @@ -99,11 +98,7 @@ void InstantUnloadHandler::Activate(WebContentsDelegateImpl* delegate) { params.tabstrip_index = delegate->index(); // Remove (and delete) the delegate. - ScopedVector<WebContentsDelegateImpl>::iterator i = - std::find(delegates_.begin(), delegates_.end(), delegate); - DCHECK(i != delegates_.end()); - delegates_.erase(i); - delegate = NULL; + Destroy(delegate); // Add the tab back in. chrome::Navigate(¶ms); diff --git a/chrome/browser/instant/instant_unload_handler.h b/chrome/browser/instant/instant_unload_handler.h index b0b7dba..eddf04b 100644 --- a/chrome/browser/instant/instant_unload_handler.h +++ b/chrome/browser/instant/instant_unload_handler.h @@ -13,7 +13,7 @@ class TabContents; // InstantUnloadHandler ensures that the beforeunload and unload handlers are // run when using Instant. When the user commits the Instant preview the -// existing TabContents is passed to |RunUnloadListenersOrDestroy|. If the tab +// existing TabContents is passed to RunUnloadListenersOrDestroy(). If the tab // has no beforeunload or unload listeners, the tab is deleted; otherwise the // beforeunload and unload listeners are executed. If the beforeunload listener // shows a dialog the tab is added back to the tabstrip at its original location diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc index a71062b..f5bb9c5 100644 --- a/chrome/browser/profiles/profile_impl.cc +++ b/chrome/browser/profiles/profile_impl.cc @@ -43,7 +43,6 @@ #include "chrome/browser/geolocation/chrome_geolocation_permission_context.h" #include "chrome/browser/history/shortcuts_backend.h" #include "chrome/browser/history/top_sites.h" -#include "chrome/browser/instant/instant_controller.h" #include "chrome/browser/metrics/metrics_service.h" #include "chrome/browser/net/chrome_url_request_context.h" #include "chrome/browser/net/net_pref_observer.h" @@ -372,8 +371,6 @@ void ProfileImpl::DoFinalInit(bool is_new_profile) { g_browser_process->background_mode_manager()->RegisterProfile(this); } - InstantController::RecordMetrics(this); - FilePath cookie_path = GetPath(); cookie_path = cookie_path.Append(chrome::kCookieFilename); FilePath server_bound_cert_path = GetPath(); diff --git a/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc b/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc index d0157be1..516f373 100644 --- a/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc +++ b/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc @@ -16,7 +16,6 @@ #include "chrome/browser/extensions/user_script_listener.h" #include "chrome/browser/external_protocol/external_protocol_handler.h" #include "chrome/browser/google/google_util.h" -#include "chrome/browser/instant/instant_loader.h" #include "chrome/browser/net/load_timing_observer.h" #include "chrome/browser/net/resource_prefetch_predictor_observer.h" #include "chrome/browser/prerender/prerender_manager.h" @@ -235,17 +234,6 @@ bool ChromeResourceDispatcherHostDelegate::AcceptAuthRequest( ResourceDispatcherHostLoginDelegate* ChromeResourceDispatcherHostDelegate::CreateLoginDelegate( net::AuthChallengeInfo* auth_info, net::URLRequest* request) { - std::string instant_header_value; - // For instant, return a NULL delegate. Auth navigations don't commit the load - // (the load remains pending) until the user cancels or succeeds in - // authorizing. Since we don't allow merging of WebContents with pending loads - // we disallow auth dialogs from showing during instant. Returning NULL does - // that. - // TODO: see if we can handle this case more robustly. - if (request->extra_request_headers().GetHeader( - InstantLoader::kInstantHeader, &instant_header_value) && - instant_header_value == InstantLoader::kInstantHeaderValue) - return NULL; return CreateLoginPrompt(auth_info, request); } diff --git a/chrome/browser/resources/options2/browser_options.js b/chrome/browser/resources/options2/browser_options.js index de62292..e0acdb7 100644 --- a/chrome/browser/resources/options2/browser_options.js +++ b/chrome/browser/resources/options2/browser_options.js @@ -170,15 +170,11 @@ cr.define('options', function() { $('default-search-engine').addEventListener('change', this.setDefaultSearchEngine_); $('instant-enabled-control').customChangeHandler = function(event) { - if (this.checked) { - if (self.instantConfirmDialogShown_) - chrome.send('enableInstant'); - else - OptionsPage.showPageByName('instantConfirm', false); - } else { - chrome.send('disableInstant'); + if (this.checked && !self.instantConfirmDialogShown_) { + OptionsPage.showPageByName('instantConfirm', false); + return true; // Stop default preference processing. } - return true; + return false; // Allow default preference processing. }; Preferences.getInstance().addEventListener('instant.confirm_dialog_shown', this.onInstantConfirmDialogShownChanged_.bind(this)); diff --git a/chrome/browser/resources/options2/instant_confirm_overlay.js b/chrome/browser/resources/options2/instant_confirm_overlay.js index c1890ec..6cadd5c 100644 --- a/chrome/browser/resources/options2/instant_confirm_overlay.js +++ b/chrome/browser/resources/options2/instant_confirm_overlay.js @@ -32,7 +32,8 @@ cr.define('options', function() { /** @inheritDoc */ handleConfirm: function() { SettingsDialog.prototype.handleConfirm.call(this); - chrome.send('enableInstant'); + Preferences.setBooleanPref('instant.confirm_dialog_shown', true); + Preferences.setBooleanPref('instant.enabled', true); }, /** @inheritDoc */ diff --git a/chrome/browser/ui/browser_instant_controller.cc b/chrome/browser/ui/browser_instant_controller.cc index 8312972..dc58960 100644 --- a/chrome/browser/ui/browser_instant_controller.cc +++ b/chrome/browser/ui/browser_instant_controller.cc @@ -19,10 +19,8 @@ #include "chrome/common/chrome_notification_types.h" #include "chrome/common/pref_names.h" #include "content/public/browser/notification_service.h" -#include "content/public/browser/notification_source.h" #include "content/public/browser/web_contents.h" - namespace chrome { //////////////////////////////////////////////////////////////////////////////// @@ -32,7 +30,7 @@ BrowserInstantController::BrowserInstantController(Browser* browser) : browser_(browser) { profile_pref_registrar_.Init(browser_->profile()->GetPrefs()); profile_pref_registrar_.Add(prefs::kInstantEnabled, this); - CreateInstantIfNecessary(); + ResetInstant(); browser_->tab_strip_model()->AddObserver(this); } @@ -41,40 +39,33 @@ BrowserInstantController::~BrowserInstantController() { } bool BrowserInstantController::OpenInstant(WindowOpenDisposition disposition) { - if (!instant() || !instant()->PrepareForCommit() || - disposition == NEW_BACKGROUND_TAB) { - // NEW_BACKGROUND_TAB results in leaving the omnibox open, so we don't - // attempt to use the instant preview. + // NEW_BACKGROUND_TAB results in leaving the omnibox open, so we don't attempt + // to use the Instant preview. + if (!instant() || !instant_->IsCurrent() || disposition == NEW_BACKGROUND_TAB) return false; - } if (disposition == CURRENT_TAB) { content::NotificationService::current()->Notify( chrome::NOTIFICATION_INSTANT_COMMITTED, - content::Source<TabContents>(instant()->CommitCurrentPreview( + content::Source<TabContents>(instant_->CommitCurrentPreview( INSTANT_COMMIT_PRESSED_ENTER)), content::NotificationService::NoDetails()); return true; } + if (disposition == NEW_FOREGROUND_TAB) { - TabContents* preview_contents = instant()->ReleasePreviewContents( - INSTANT_COMMIT_PRESSED_ENTER, NULL); - // HideInstant is invoked after release so that InstantController is not - // active when HideInstant asks it for its state. - HideInstant(); - preview_contents->web_contents()->GetController().PruneAllButActive(); - browser_->tab_strip_model()->AddTabContents( - preview_contents, - -1, - instant()->last_transition_type(), - TabStripModel::ADD_ACTIVE); - instant()->CompleteRelease(preview_contents); + TabContents* preview = instant_->ReleasePreviewContents( + INSTANT_COMMIT_PRESSED_ENTER); + preview->web_contents()->GetController().PruneAllButActive(); + browser_->tab_strip_model()->AddTabContents(preview, -1, + instant_->last_transition_type(), TabStripModel::ADD_ACTIVE); content::NotificationService::current()->Notify( chrome::NOTIFICATION_INSTANT_COMMITTED, - content::Source<TabContents>(preview_contents), + content::Source<TabContents>(preview), content::NotificationService::NoDetails()); return true; } + // The omnibox currently doesn't use other dispositions, so we don't attempt // to handle them. If you hit this NOTREACHED file a bug and I'll (sky) add // support for the new disposition. @@ -85,33 +76,45 @@ bool BrowserInstantController::OpenInstant(WindowOpenDisposition disposition) { //////////////////////////////////////////////////////////////////////////////// // BrowserInstantController, InstantControllerDelegate implementation: -void BrowserInstantController::ShowInstant(TabContents* preview_contents) { - browser_->window()->ShowInstant(preview_contents); +void BrowserInstantController::ShowInstant() { + TabContents* preview = instant_->GetPreviewContents(); + browser_->window()->ShowInstant(preview); + + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN, + content::Source<InstantController>(instant()), + content::NotificationService::NoDetails()); - // TODO(beng): investigate if we can avoid this and instead rely on the - // visibility of the WebContentsView + // TODO(beng): Investigate if we can avoid this and instead rely on the + // visibility of the WebContentsView. chrome::GetActiveWebContents(browser_)->WasHidden(); - preview_contents->web_contents()->WasShown(); + preview->web_contents()->WasShown(); } void BrowserInstantController::HideInstant() { browser_->window()->HideInstant(); + + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_INSTANT_CONTROLLER_HIDDEN, + content::Source<InstantController>(instant()), + content::NotificationService::NoDetails()); + if (chrome::GetActiveWebContents(browser_)) chrome::GetActiveWebContents(browser_)->WasShown(); - if (instant_->GetPreviewContents()) - instant_->GetPreviewContents()->web_contents()->WasHidden(); + if (TabContents* preview = instant_->GetPreviewContents()) + preview->web_contents()->WasHidden(); } -void BrowserInstantController::CommitInstant(TabContents* preview_contents) { - TabContents* tab_contents = chrome::GetActiveTabContents(browser_); - int index = browser_->tab_strip_model()->GetIndexOfTabContents(tab_contents); +void BrowserInstantController::CommitInstant(TabContents* preview) { + TabContents* active_tab = chrome::GetActiveTabContents(browser_); + int index = browser_->tab_strip_model()->GetIndexOfTabContents(active_tab); DCHECK_NE(TabStripModel::kNoTab, index); - // TabStripModel takes ownership of preview_contents. - browser_->tab_strip_model()->ReplaceTabContentsAt(index, preview_contents); - // InstantUnloadHandler takes ownership of tab_contents. - instant_unload_handler_->RunUnloadListenersOrDestroy(tab_contents, index); + // TabStripModel takes ownership of |preview|. + browser_->tab_strip_model()->ReplaceTabContentsAt(index, preview); + // InstantUnloadHandler takes ownership of |active_tab|. + instant_unload_handler_->RunUnloadListenersOrDestroy(active_tab, index); - GURL url = preview_contents->web_contents()->GetURL(); + GURL url = preview->web_contents()->GetURL(); DCHECK(browser_->profile()->GetExtensionService()); if (browser_->profile()->GetExtensionService()->IsInstalledApp(url)) { AppLauncherHandler::RecordAppLaunchType( @@ -136,7 +139,7 @@ void BrowserInstantController::InstantPreviewFocused() { instant_->GetPreviewContents()->web_contents()); } -TabContents* BrowserInstantController::GetInstantHostTabContents() const { +TabContents* BrowserInstantController::GetActiveTabContents() const { return chrome::GetActiveTabContents(browser_); } @@ -147,20 +150,10 @@ void BrowserInstantController::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { - DCHECK(type == chrome::NOTIFICATION_PREF_CHANGED); - const std::string& pref_name = - *content::Details<std::string>(details).ptr(); - DCHECK(pref_name == prefs::kInstantEnabled); - if (browser_shutdown::ShuttingDownWithoutClosingBrowsers() || - !InstantController::IsEnabled(browser_->profile())) { - if (instant()) { - instant()->DestroyPreviewContents(); - instant_.reset(); - instant_unload_handler_.reset(); - } - } else { - CreateInstantIfNecessary(); - } + DCHECK_EQ(chrome::NOTIFICATION_PREF_CHANGED, type); + DCHECK_EQ(std::string(prefs::kInstantEnabled), + *content::Details<std::string>(details).ptr()); + ResetInstant(); } //////////////////////////////////////////////////////////////////////////////// @@ -168,18 +161,21 @@ void BrowserInstantController::Observe( void BrowserInstantController::TabDeactivated(TabContents* contents) { if (instant()) - instant()->Hide(); + instant_->Hide(); } //////////////////////////////////////////////////////////////////////////////// // BrowserInstantController, private: -void BrowserInstantController::CreateInstantIfNecessary() { - if (browser_->is_type_tabbed() && +void BrowserInstantController::ResetInstant() { + if (!browser_shutdown::ShuttingDownWithoutClosingBrowsers() && InstantController::IsEnabled(browser_->profile()) && - !browser_->profile()->IsOffTheRecord()) { + browser_->is_type_tabbed() && !browser_->profile()->IsOffTheRecord()) { instant_.reset(new InstantController(this, InstantController::INSTANT)); instant_unload_handler_.reset(new InstantUnloadHandler(browser_)); + } else { + instant_.reset(); + instant_unload_handler_.reset(); } } diff --git a/chrome/browser/ui/browser_instant_controller.h b/chrome/browser/ui/browser_instant_controller.h index 6aa23ab..088a89f 100644 --- a/chrome/browser/ui/browser_instant_controller.h +++ b/chrome/browser/ui/browser_instant_controller.h @@ -5,18 +5,32 @@ #ifndef CHROME_BROWSER_UI_BROWSER_INSTANT_CONTROLLER_H_ #define CHROME_BROWSER_UI_BROWSER_INSTANT_CONTROLLER_H_ +#include "base/basictypes.h" +#include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" +#include "base/string16.h" #include "chrome/browser/instant/instant_controller_delegate.h" #include "chrome/browser/prefs/pref_change_registrar.h" #include "chrome/browser/ui/tabs/tab_strip_model_observer.h" +#include "chrome/common/instant_types.h" #include "content/public/browser/notification_observer.h" #include "webkit/glue/window_open_disposition.h" class Browser; class InstantController; +class InstantTest; class InstantUnloadHandler; class TabContents; +namespace content { +class NotificationDetails; +class NotificationSource; +} + +namespace gfx { +class Rect; +} + namespace chrome { class BrowserInstantController : public InstantControllerDelegate, @@ -26,7 +40,7 @@ class BrowserInstantController : public InstantControllerDelegate, explicit BrowserInstantController(Browser* browser); virtual ~BrowserInstantController(); - // Commits the current instant, returning true on success. This is intended + // Commits the current Instant, returning true on success. This is intended // for use from OpenCurrentURL. bool OpenInstant(WindowOpenDisposition disposition); @@ -36,14 +50,14 @@ class BrowserInstantController : public InstantControllerDelegate, private: // Overridden from InstantControllerDelegate: - virtual void ShowInstant(TabContents* preview_contents) OVERRIDE; + virtual void ShowInstant() OVERRIDE; virtual void HideInstant() OVERRIDE; - virtual void CommitInstant(TabContents* preview_contents) OVERRIDE; + virtual void CommitInstant(TabContents* preview) OVERRIDE; virtual void SetSuggestedText(const string16& text, InstantCompleteBehavior behavior) OVERRIDE; virtual gfx::Rect GetInstantBounds() OVERRIDE; virtual void InstantPreviewFocused() OVERRIDE; - virtual TabContents* GetInstantHostTabContents() const OVERRIDE; + virtual TabContents* GetActiveTabContents() const OVERRIDE; // Overridden from content::NotificationObserver: virtual void Observe(int type, @@ -53,8 +67,9 @@ class BrowserInstantController : public InstantControllerDelegate, // Overridden from TabStripModelObserver: virtual void TabDeactivated(TabContents* contents) OVERRIDE; - // If this browser should have instant one is created, otherwise does nothing. - void CreateInstantIfNecessary(); + // If this browser should have Instant, a new InstantController created; + // otherwise any existing InstantController is destroyed. + void ResetInstant(); Browser* browser_; diff --git a/chrome/browser/ui/cocoa/browser_window_controller.mm b/chrome/browser/ui/cocoa/browser_window_controller.mm index 5b8164e..fe1d890 100644 --- a/chrome/browser/ui/cocoa/browser_window_controller.mm +++ b/chrome/browser/ui/cocoa/browser_window_controller.mm @@ -1920,8 +1920,8 @@ willAnimateFromState:(bookmarks::VisualState)oldState - (void)commitInstant { InstantController* instant = browser_->instant_controller()->instant(); - if (instant) - instant->CommitIfCurrent(); + if (instant && instant->IsCurrent()) + instant->CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST); } diff --git a/chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.mm b/chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.mm index 047f60a..df9de65 100644 --- a/chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.mm +++ b/chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.mm @@ -21,7 +21,6 @@ #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/location_bar_controller.h" #include "chrome/browser/extensions/tab_helper.h" -#include "chrome/browser/instant/instant_controller.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search_engines/template_url.h" #include "chrome/browser/search_engines/template_url_service.h" diff --git a/chrome/browser/ui/gtk/location_bar_view_gtk.cc b/chrome/browser/ui/gtk/location_bar_view_gtk.cc index a270455..3c5d6ef 100644 --- a/chrome/browser/ui/gtk/location_bar_view_gtk.cc +++ b/chrome/browser/ui/gtk/location_bar_view_gtk.cc @@ -32,7 +32,6 @@ #include "chrome/browser/extensions/location_bar_controller.h" #include "chrome/browser/extensions/tab_helper.h" #include "chrome/browser/favicon/favicon_tab_helper.h" -#include "chrome/browser/instant/instant_controller.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search_engines/template_url.h" #include "chrome/browser/search_engines/template_url_service.h" diff --git a/chrome/browser/ui/gtk/omnibox/omnibox_view_gtk.cc b/chrome/browser/ui/gtk/omnibox/omnibox_view_gtk.cc index 5c71ddf..71d6ec7 100644 --- a/chrome/browser/ui/gtk/omnibox/omnibox_view_gtk.cc +++ b/chrome/browser/ui/gtk/omnibox/omnibox_view_gtk.cc @@ -384,9 +384,9 @@ void OmniboxViewGtk::Init() { ui::MultiAnimation::Parts parts; parts.push_back(ui::MultiAnimation::Part( - InstantController::kAutoCommitPauseTimeMS, ui::Tween::ZERO)); + InstantController::kInlineAutocompletePauseTimeMS, ui::Tween::ZERO)); parts.push_back(ui::MultiAnimation::Part( - InstantController::kAutoCommitFadeInTimeMS, ui::Tween::EASE_IN)); + InstantController::kInlineAutocompleteFadeInTimeMS, ui::Tween::EASE_IN)); instant_animation_.reset(new ui::MultiAnimation(parts)); instant_animation_->set_continuous(false); diff --git a/chrome/browser/ui/omnibox/omnibox_edit_model.cc b/chrome/browser/ui/omnibox/omnibox_edit_model.cc index 131ee22..bf5901d 100644 --- a/chrome/browser/ui/omnibox/omnibox_edit_model.cc +++ b/chrome/browser/ui/omnibox/omnibox_edit_model.cc @@ -97,8 +97,7 @@ OmniboxEditModel::OmniboxEditModel(OmniboxView* view, is_keyword_hint_(false), profile_(profile), in_revert_(false), - allow_exact_keyword_match_(false), - instant_complete_behavior_(INSTANT_COMPLETE_DELAYED) { + allow_exact_keyword_match_(false) { } OmniboxEditModel::~OmniboxEditModel() { @@ -186,8 +185,7 @@ void OmniboxEditModel::FinalizeInstantQuery(const string16& input_text, void OmniboxEditModel::SetSuggestedText(const string16& text, InstantCompleteBehavior behavior) { - instant_complete_behavior_ = behavior; - if (instant_complete_behavior_ == INSTANT_COMPLETE_NOW) { + if (behavior == INSTANT_COMPLETE_NOW) { if (!text.empty()) FinalizeInstantQuery(view_->GetText(), text, false); else @@ -213,7 +211,8 @@ bool OmniboxEditModel::CommitSuggestedText(bool skip_inline_autocomplete) { bool OmniboxEditModel::AcceptCurrentInstantPreview() { InstantController* instant = controller_->GetInstant(); - return instant && instant->CommitIfCurrent(); + return instant && instant->IsCurrent() && + instant->CommitCurrentPreview(INSTANT_COMMIT_PRESSED_ENTER); } void OmniboxEditModel::OnChanged() { @@ -243,10 +242,12 @@ void OmniboxEditModel::OnChanged() { UMA_HISTOGRAM_ENUMERATION("AutocompleteActionPredictor.Action", recommended_action, AutocompleteActionPredictor::LAST_PREDICT_ACTION); + string16 suggested_text; + InstantCompleteBehavior complete_behavior = INSTANT_COMPLETE_NOW; - if (DoInstant(current_match, &suggested_text)) { - SetSuggestedText(suggested_text, instant_complete_behavior_); + if (DoInstant(current_match, &suggested_text, &complete_behavior)) { + SetSuggestedText(suggested_text, complete_behavior); } else { switch (recommended_action) { case AutocompleteActionPredictor::ACTION_PRERENDER: @@ -442,7 +443,7 @@ void OmniboxEditModel::StopAutocomplete() { if (popup_->IsOpen() && !in_revert_) { InstantController* instant = controller_->GetInstant(); if (instant && !instant->commit_on_pointer_release()) - instant->DestroyPreviewContents(); + instant->Hide(); } autocomplete_controller_->Stop(true); @@ -630,7 +631,7 @@ void OmniboxEditModel::OpenMatch(const AutocompleteMatch& match, InstantController* instant = controller_->GetInstant(); if (instant && !popup_->IsOpen()) - instant->DestroyPreviewContents(); + instant->Hide(); in_revert_ = false; } @@ -700,8 +701,7 @@ void OmniboxEditModel::OnSetFocus(bool control_down) { void OmniboxEditModel::OnWillKillFocus(gfx::NativeView view_gaining_focus) { SetSuggestedText(string16(), INSTANT_COMPLETE_NOW); - InstantController* instant = controller_->GetInstant(); - if (instant) + if (InstantController* instant = controller_->GetInstant()) instant->OnAutocompleteLostFocus(view_gaining_focus); NotifySearchTabHelper(); @@ -1104,9 +1104,12 @@ void OmniboxEditModel::NotifySearchTabHelper() { } } -bool OmniboxEditModel::DoInstant(const AutocompleteMatch& match, - string16* suggested_text) { +bool OmniboxEditModel::DoInstant( + const AutocompleteMatch& match, + string16* suggested_text, + InstantCompleteBehavior* complete_behavior) { DCHECK(suggested_text); + DCHECK(complete_behavior); if (in_revert_) return false; @@ -1117,8 +1120,19 @@ bool OmniboxEditModel::DoInstant(const AutocompleteMatch& match, return false; if (user_input_in_progress_ && popup_->IsOpen()) { - return instant->Update(match, view_->GetText(), UseVerbatimInstant(), - suggested_text); + string16 text = view_->GetText(); + AutocompleteInput::RemoveForcedQueryStringIfNecessary( + autocomplete_controller_->input().type(), &text); + + // If there's any inline autocompletion, split it out from |text|. + if (!inline_autocomplete_text_.empty()) { + DCHECK_GE(text.size(), inline_autocomplete_text_.size()); + text.resize(text.size() - inline_autocomplete_text_.size()); + *suggested_text = inline_autocomplete_text_; + } + + return instant->Update(match, text, UseVerbatimInstant(), suggested_text, + complete_behavior); } // It's possible DoInstant() was called due to an OnChanged() event from the diff --git a/chrome/browser/ui/omnibox/omnibox_edit_model.h b/chrome/browser/ui/omnibox/omnibox_edit_model.h index f990b3e..4a02f33 100644 --- a/chrome/browser/ui/omnibox/omnibox_edit_model.h +++ b/chrome/browser/ui/omnibox/omnibox_edit_model.h @@ -82,7 +82,7 @@ class OmniboxEditModel : public AutocompleteControllerDelegate { // Sets the url, and if known, the title and favicon. void GetDataForURLExport(GURL* url, string16* title, SkBitmap* favicon); - // Returns true if a verbatim query should be used for instant. A verbatim + // Returns true if a verbatim query should be used for Instant. A verbatim // query is forced in certain situations, such as pressing delete at the end // of the edit. bool UseVerbatimInstant(); @@ -137,8 +137,7 @@ class OmniboxEditModel : public AutocompleteControllerDelegate { bool skip_inline_autocomplete); // Sets the suggestion text. - void SetSuggestedText(const string16& text, - InstantCompleteBehavior behavior); + void SetSuggestedText(const string16& text, InstantCompleteBehavior behavior); // Commits the suggested text. If |skip_inline_autocomplete| is true then the // suggested text will be committed as final text as if it's inputted by the @@ -147,11 +146,11 @@ class OmniboxEditModel : public AutocompleteControllerDelegate { // TODO: can the return type be void? bool CommitSuggestedText(bool skip_inline_autocomplete); - // Accepts the currently showing instant preview, if any, and returns true. - // Returns false if there is no instant preview showing. + // Accepts the currently showing Instant preview, if any, and returns true. + // Returns false if there is no Instant preview showing. bool AcceptCurrentInstantPreview(); - // Invoked any time the text may have changed in the edit. Updates instant and + // Invoked any time the text may have changed in the edit. Updates Instant and // notifies the controller. void OnChanged(); @@ -277,12 +276,6 @@ class OmniboxEditModel : public AutocompleteControllerDelegate { // Invoked when the popup is going to change its bounds to |bounds|. void PopupBoundsChangedTo(const gfx::Rect& bounds); -#if defined(UNIT_TEST) - InstantCompleteBehavior instant_complete_behavior() const { - return instant_complete_behavior_; - } -#endif - private: enum PasteState { NONE, // Most recent edit was not a paste. @@ -373,9 +366,13 @@ class OmniboxEditModel : public AutocompleteControllerDelegate { // Notifies the SearchTabHelper that autocomplete state has changed. void NotifySearchTabHelper(); - // Tries to start an instant preview for |match|. Returns true if instant - // processed the match. - bool DoInstant(const AutocompleteMatch& match, string16* suggested_text); + // Tries to start an Instant preview for |match|. Returns true if Instant + // processed the match. |suggested_text| should initially contain the current + // inline autocomplete text. Instant will replace it with new suggested text + // and set |complete_behavior| accordingly. + bool DoInstant(const AutocompleteMatch& match, + string16* suggested_text, + InstantCompleteBehavior* complete_behavior); // Starts a prerender for the given |match|. void DoPrerender(const AutocompleteMatch& match); @@ -497,7 +494,7 @@ class OmniboxEditModel : public AutocompleteControllerDelegate { Profile* profile_; // This is needed as prior to accepting the current text the model is - // reverted, which triggers resetting instant. We don't want to update instant + // reverted, which triggers resetting Instant. We don't want to update Instant // in this case, so we use the flag to determine if this is happening. bool in_revert_; @@ -508,9 +505,6 @@ class OmniboxEditModel : public AutocompleteControllerDelegate { // This has no effect if we're already in keyword mode. bool allow_exact_keyword_match_; - // Last value of InstantCompleteBehavior supplied to |SetSuggestedText|. - InstantCompleteBehavior instant_complete_behavior_; - DISALLOW_COPY_AND_ASSIGN(OmniboxEditModel); }; diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc index 283a834..0f2fc86 100644 --- a/chrome/browser/ui/views/frame/browser_view.cc +++ b/chrome/browser/ui/views/frame/browser_view.cc @@ -18,7 +18,6 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/debugger/devtools_window.h" #include "chrome/browser/extensions/tab_helper.h" -#include "chrome/browser/instant/instant_controller.h" #include "chrome/browser/managed_mode.h" #include "chrome/browser/native_window_notification_source.h" #include "chrome/browser/ntp_background_util.h" diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc index b634b45..973b9d5 100644 --- a/chrome/browser/ui/views/location_bar/location_bar_view.cc +++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc @@ -20,7 +20,6 @@ #include "chrome/browser/extensions/location_bar_controller.h" #include "chrome/browser/extensions/tab_helper.h" #include "chrome/browser/favicon/favicon_tab_helper.h" -#include "chrome/browser/instant/instant_controller.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search_engines/template_url.h" #include "chrome/browser/search_engines/template_url_service.h" @@ -1228,7 +1227,7 @@ bool LocationBarView::SkipDefaultKeyEventProcessing( return true; } - // Tab while showing instant commits instant immediately. + // Tab while showing Instant commits instant immediately. // Return true so that focus traversal isn't attempted. The edit ends // up doing nothing in this case. if (location_entry_->model()->AcceptCurrentInstantPreview()) diff --git a/chrome/browser/ui/views/location_bar/suggested_text_view.cc b/chrome/browser/ui/views/location_bar/suggested_text_view.cc index 37ccc44..e30b08e 100644 --- a/chrome/browser/ui/views/location_bar/suggested_text_view.cc +++ b/chrome/browser/ui/views/location_bar/suggested_text_view.cc @@ -73,9 +73,9 @@ void SuggestedTextView::AnimationCanceled(const ui::Animation* animation) { ui::Animation* SuggestedTextView::CreateAnimation() { ui::MultiAnimation::Parts parts; parts.push_back(ui::MultiAnimation::Part( - InstantController::kAutoCommitPauseTimeMS, ui::Tween::ZERO)); + InstantController::kInlineAutocompletePauseTimeMS, ui::Tween::ZERO)); parts.push_back(ui::MultiAnimation::Part( - InstantController::kAutoCommitFadeInTimeMS, ui::Tween::EASE_IN)); + InstantController::kInlineAutocompleteFadeInTimeMS, ui::Tween::EASE_IN)); ui::MultiAnimation* animation = new ui::MultiAnimation(parts); animation->set_delegate(this); animation->set_continuous(false); diff --git a/chrome/browser/ui/webui/instant_ui.cc b/chrome/browser/ui/webui/instant_ui.cc index f7c2635..e066dc5 100644 --- a/chrome/browser/ui/webui/instant_ui.cc +++ b/chrome/browser/ui/webui/instant_ui.cc @@ -5,14 +5,11 @@ #include "chrome/browser/ui/webui/instant_ui.h" #include "base/bind.h" -#include "base/values.h" -#include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/webui/chrome_web_ui_data_source.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "content/public/browser/web_ui.h" -#include "content/public/browser/web_ui_controller.h" #include "content/public/browser/web_ui_message_handler.h" #include "grit/browser_resources.h" @@ -50,7 +47,7 @@ class InstantUIMessageHandler void GetPreferenceValue(const base::ListValue* args); void SetPreferenceValue(const base::ListValue* args); - // Slows down instant animations by a time factor. + // Slows down Instant animations by a time factor. static int slow_animation_scale_factor_; DISALLOW_COPY_AND_ASSIGN(InstantUIMessageHandler); @@ -115,8 +112,7 @@ void InstantUIMessageHandler::SetPreferenceValue(const base::ListValue* args) { //////////////////////////////////////////////////////////////////////////////// // InstantUI -InstantUI::InstantUI(content::WebUI* web_ui) - : WebUIController(web_ui) { +InstantUI::InstantUI(content::WebUI* web_ui) : WebUIController(web_ui) { web_ui->AddMessageHandler(new InstantUIMessageHandler()); // Set up the chrome://instant/ source. diff --git a/chrome/browser/ui/webui/instant_ui.h b/chrome/browser/ui/webui/instant_ui.h index 0260eec..a5a2d8f 100644 --- a/chrome/browser/ui/webui/instant_ui.h +++ b/chrome/browser/ui/webui/instant_ui.h @@ -7,14 +7,14 @@ #include "content/public/browser/web_ui_controller.h" -// Provides configuration options for instant web search. +// Provides configuration options for Instant web search. class InstantUI : public content::WebUIController { public: // Constructs an instance using |web_ui| for its data sources and message // handlers. explicit InstantUI(content::WebUI* web_ui); - // Returns a scale factor to slow down instant animations. + // Returns a scale factor to slow down Instant animations. static int GetSlowAnimationScaleFactor(); private: diff --git a/chrome/browser/ui/webui/options2/browser_options_handler.cc b/chrome/browser/ui/webui/options2/browser_options_handler.cc index 84e11d5..7b84554 100644 --- a/chrome/browser/ui/webui/options2/browser_options_handler.cc +++ b/chrome/browser/ui/webui/options2/browser_options_handler.cc @@ -23,7 +23,6 @@ #include "chrome/browser/chrome_page_zoom.h" #include "chrome/browser/custom_home_pages_table_model.h" #include "chrome/browser/download/download_prefs.h" -#include "chrome/browser/instant/instant_controller.h" #include "chrome/browser/net/url_fixer_upper.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/prefs/session_startup_pref.h" @@ -433,14 +432,6 @@ void BrowserOptionsHandler::RegisterMessages() { base::Bind(&BrowserOptionsHandler::SetDefaultSearchEngine, base::Unretained(this))); web_ui()->RegisterMessageCallback( - "enableInstant", - base::Bind(&BrowserOptionsHandler::EnableInstant, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "disableInstant", - base::Bind(&BrowserOptionsHandler::DisableInstant, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( "createProfile", base::Bind(&BrowserOptionsHandler::CreateProfile, base::Unretained(this))); @@ -879,14 +870,6 @@ void BrowserOptionsHandler::Observe( } } -void BrowserOptionsHandler::EnableInstant(const ListValue* args) { - InstantController::Enable(Profile::FromWebUI(web_ui())); -} - -void BrowserOptionsHandler::DisableInstant(const ListValue* args) { - InstantController::Disable(Profile::FromWebUI(web_ui())); -} - void BrowserOptionsHandler::ToggleAutoLaunch(const ListValue* args) { #if defined(OS_WIN) if (!auto_launch_trial::IsInAutoLaunchGroup()) diff --git a/chrome/browser/ui/webui/options2/browser_options_handler.h b/chrome/browser/ui/webui/options2/browser_options_handler.h index 71ee40a..750f1d5 100644 --- a/chrome/browser/ui/webui/options2/browser_options_handler.h +++ b/chrome/browser/ui/webui/options2/browser_options_handler.h @@ -88,10 +88,6 @@ class BrowserOptionsHandler // Sets the search engine at the given index to be default. Called from WebUI. void SetDefaultSearchEngine(const base::ListValue* args); - // Enables/disables Instant. - void EnableInstant(const base::ListValue* args); - void DisableInstant(const base::ListValue* args); - // Enables/disables auto-launching of Chrome on computer startup. void ToggleAutoLaunch(const base::ListValue* args); diff --git a/chrome/chrome_renderer.gypi b/chrome/chrome_renderer.gypi index 3363e7e..5099d7c 100644 --- a/chrome/chrome_renderer.gypi +++ b/chrome/chrome_renderer.gypi @@ -254,8 +254,6 @@ 'renderer/safe_browsing/phishing_url_feature_extractor.h', 'renderer/safe_browsing/scorer.cc', 'renderer/safe_browsing/scorer.h', - 'renderer/search_extension.cc', - 'renderer/search_extension.h', 'renderer/searchbox.cc', 'renderer/searchbox.h', 'renderer/searchbox_extension.cc', diff --git a/chrome/common/chrome_notification_types.h b/chrome/common/chrome_notification_types.h index 40d7965..002f93f 100644 --- a/chrome/common/chrome_notification_types.h +++ b/chrome/common/chrome_notification_types.h @@ -1097,13 +1097,16 @@ enum NotificationType { // Sent each time the InstantController shows the InstantLoader. NOTIFICATION_INSTANT_CONTROLLER_SHOWN, - // Sent when an Instant preview is committed. The Source is the - // TabContents containing the committed preview. There are no details. + // Sent each time the InstantController hides the InstantLoader. + NOTIFICATION_INSTANT_CONTROLLER_HIDDEN, + + // Sent when an Instant preview is committed. The Source is the TabContents + // containing the committed preview. There are no details. NOTIFICATION_INSTANT_COMMITTED, - // Sent when the instant loader determines whether the page supports the - // instant API or not. The details is a boolean indicating if the page - // supports instant. The source is not used. + // Sent when the Instant loader determines whether the page supports the + // Instant API or not. The details is a boolean indicating if the page + // supports Instant. The source is not used. NOTIFICATION_INSTANT_SUPPORT_DETERMINED, // Sent when the CaptivePortalService checks if we're behind a captive portal. diff --git a/chrome/common/instant_types.h b/chrome/common/instant_types.h index 46638f9..968993c 100644 --- a/chrome/common/instant_types.h +++ b/chrome/common/instant_types.h @@ -1,20 +1,22 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. #ifndef CHROME_COMMON_INSTANT_TYPES_H_ #define CHROME_COMMON_INSTANT_TYPES_H_ -// Enum describing the ways instant suggest text can be completed. +// Ways that the Instant suggested text is autocompleted into the omnibox. enum InstantCompleteBehavior { - // Complete the suggestion now. + // Autocomplete the suggestion immediately. INSTANT_COMPLETE_NOW, - // Complete the suggestion after a delay. + // Autocomplete the suggestion after a delay. INSTANT_COMPLETE_DELAYED, - // Never complete the suggestion. - INSTANT_COMPLETE_NEVER + // Do not autocomplete the suggestion. The suggestion may still be displayed + // in the omnibox, but not made a part of the omnibox text by default (e.g., + // by displaying the suggestion as non-highlighted, non-selected gray text). + INSTANT_COMPLETE_NEVER, }; #endif // CHROME_COMMON_INSTANT_TYPES_H_ diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h index 41f6110..51094f6 100644 --- a/chrome/common/render_messages.h +++ b/chrome/common/render_messages.h @@ -269,19 +269,15 @@ IPC_MESSAGE_ROUTED3(ChromeViewMsg_HandleMessageFromExternalHost, IPC_MESSAGE_ROUTED4(ChromeViewMsg_SearchBoxChange, string16 /* value */, bool /* verbatim */, - int /* selection_start */, - int /* selection_end */) -IPC_MESSAGE_ROUTED2(ChromeViewMsg_SearchBoxSubmit, - string16 /* value */, - bool /* verbatim */) -IPC_MESSAGE_ROUTED0(ChromeViewMsg_SearchBoxCancel) + size_t /* selection_start */, + size_t /* selection_end */) +IPC_MESSAGE_ROUTED1(ChromeViewMsg_SearchBoxSubmit, + string16 /* value */) +IPC_MESSAGE_ROUTED1(ChromeViewMsg_SearchBoxCancel, + string16 /* value */) IPC_MESSAGE_ROUTED1(ChromeViewMsg_SearchBoxResize, gfx::Rect /* search_box_bounds */) -IPC_MESSAGE_ROUTED4(ChromeViewMsg_DetermineIfPageSupportsInstant, - string16 /* value*/, - bool /* verbatim */, - int /* selection_start */, - int /* selection_end */) +IPC_MESSAGE_ROUTED0(ChromeViewMsg_DetermineIfPageSupportsInstant) // Toggles visual muting of the render view area. This is on when a constrained // window is showing. @@ -604,14 +600,17 @@ IPC_MESSAGE_ROUTED0(ChromeViewHostMsg_FocusedEditableNodeTouched) // Suggest results ----------------------------------------------------------- +// Sent by the Instant preview to populate the omnibox with query suggestions. IPC_MESSAGE_ROUTED3(ChromeViewHostMsg_SetSuggestions, - int32 /* page_id */, - std::vector<std::string> /* suggestions */, - InstantCompleteBehavior) + int /* page_id */, + std::vector<string16> /* suggestions */, + InstantCompleteBehavior /* behavior */) +// Sent by the Instant preview indicating whether the page supports the Instant +// API or not (http://dev.chromium.org/searchbox). IPC_MESSAGE_ROUTED2(ChromeViewHostMsg_InstantSupportDetermined, - int32 /* page_id */, - bool /* result */) + int /* page_id */, + bool /* result */) // The currently displayed PDF has an unsupported feature. IPC_MESSAGE_ROUTED0(ChromeViewHostMsg_PDFHasUnsupportedFeature) diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc index 6643f24..fff4b27 100644 --- a/chrome/renderer/chrome_content_renderer_client.cc +++ b/chrome/renderer/chrome_content_renderer_client.cc @@ -59,7 +59,6 @@ #include "chrome/renderer/print_web_view_helper.h" #include "chrome/renderer/safe_browsing/malware_dom_details.h" #include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h" -#include "chrome/renderer/search_extension.h" #include "chrome/renderer/searchbox.h" #include "chrome/renderer/searchbox_extension.h" #include "chrome/renderer/spellchecker/spellcheck.h" @@ -178,10 +177,6 @@ void ChromeContentRendererClient::RenderThreadStarted() { thread->RegisterExtension(extensions_v8::ExternalExtension::Get()); thread->RegisterExtension(extensions_v8::LoadTimesExtension::Get()); thread->RegisterExtension(extensions_v8::SearchBoxExtension::Get()); - v8::Extension* search_extension = extensions_v8::SearchExtension::Get(); - // search_extension is null if not enabled. - if (search_extension) - thread->RegisterExtension(search_extension); CommandLine* command_line = CommandLine::ForCurrentProcess(); if (command_line->HasSwitch(switches::kEnableBenchmarking)) diff --git a/chrome/renderer/search_extension.cc b/chrome/renderer/search_extension.cc deleted file mode 100644 index 01e3a7d..0000000 --- a/chrome/renderer/search_extension.cc +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2011 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/search_extension.h" - -#include <string> -#include <vector> - -#include "base/command_line.h" -#include "chrome/renderer/searchbox.h" -#include "content/public/renderer/render_view.h" -#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" -#include "v8/include/v8.h" - -using WebKit::WebFrame; -using WebKit::WebView; - -namespace extensions_v8 { - -const char* const kSearchExtensionName = "v8/InstantSearch"; - -class SearchExtensionWrapper : public v8::Extension { - public: - SearchExtensionWrapper(); - - // 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(); - - // Implementation of window.chrome.setSuggestResult. - static v8::Handle<v8::Value> SetSuggestResult(const v8::Arguments& args); - - private: - DISALLOW_COPY_AND_ASSIGN(SearchExtensionWrapper); -}; - -SearchExtensionWrapper::SearchExtensionWrapper() - : v8::Extension(kSearchExtensionName, - "var chrome;" - "if (!chrome)" - " chrome = {};" - "chrome.setSuggestResult = function(text) {" - " native function NativeSetSuggestResult();" - " NativeSetSuggestResult(text);" - "};") { -} - -v8::Handle<v8::FunctionTemplate> SearchExtensionWrapper::GetNativeFunction( - v8::Handle<v8::String> name) { - if (name->Equals(v8::String::New("NativeSetSuggestResult"))) { - return v8::FunctionTemplate::New(SetSuggestResult); - } - - return v8::Handle<v8::FunctionTemplate>(); -} - -// static -content::RenderView* SearchExtensionWrapper::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> SearchExtensionWrapper::SetSuggestResult( - const v8::Arguments& args) { - if (!args.Length() || !args[0]->IsString()) return v8::Undefined(); - - content::RenderView* render_view = GetRenderView(); - if (!render_view) return v8::Undefined(); - - std::vector<std::string> suggestions; - suggestions.push_back(std::string(*v8::String::Utf8Value(args[0]))); - SearchBox::Get(render_view)->SetSuggestions(suggestions, - INSTANT_COMPLETE_NOW); - return v8::Undefined(); -} - -v8::Extension* SearchExtension::Get() { - return new SearchExtensionWrapper(); -} - -} // namespace extensions_v8 diff --git a/chrome/renderer/search_extension.h b/chrome/renderer/search_extension.h deleted file mode 100644 index 99c6f1f..0000000 --- a/chrome/renderer/search_extension.h +++ /dev/null @@ -1,23 +0,0 @@ -// 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_SEARCH_EXTENSION_H_ -#define CHROME_RENDERER_SEARCH_EXTENSION_H_ - -namespace v8 { -class Extension; -} - -namespace extensions_v8 { - -class SearchExtension { - public: - // Returns the v8::Extension object handling search bindings. Returns null if - // match-preview is not enabled. Caller takes ownership of returned object. - static v8::Extension* Get(); -}; - -} // namespace extensions_v8 - -#endif // CHROME_RENDERER_SEARCH_EXTENSION_H_ diff --git a/chrome/renderer/searchbox.cc b/chrome/renderer/searchbox.cc index fc9ee55..a662713 100644 --- a/chrome/renderer/searchbox.cc +++ b/chrome/renderer/searchbox.cc @@ -9,8 +9,6 @@ #include "content/public/renderer/render_view.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" -using WebKit::WebView; - SearchBox::SearchBox(content::RenderView* render_view) : content::RenderViewObserver(render_view), content::RenderViewObserverTracker<SearchBox>(render_view), @@ -22,7 +20,7 @@ SearchBox::SearchBox(content::RenderView* render_view) SearchBox::~SearchBox() { } -void SearchBox::SetSuggestions(const std::vector<std::string>& suggestions, +void SearchBox::SetSuggestions(const std::vector<string16>& suggestions, InstantCompleteBehavior behavior) { // Explicitly allow empty vector to be sent to the browser. render_view()->Send(new ChromeViewHostMsg_SetSuggestions( @@ -34,10 +32,10 @@ gfx::Rect SearchBox::GetRect() { // Need to adjust for scale. if (rect_.IsEmpty()) return rect_; - WebView* web_view = render_view()->GetWebView(); + WebKit::WebView* web_view = render_view()->GetWebView(); if (!web_view) return rect_; - double zoom = WebView::zoomLevelToZoomFactor(web_view->zoomLevel()); + double zoom = WebKit::WebView::zoomLevelToZoomFactor(web_view->zoomLevel()); if (zoom == 0) return rect_; return gfx::Rect(static_cast<int>(static_cast<float>(rect_.x()) / zoom), @@ -62,21 +60,22 @@ bool SearchBox::OnMessageReceived(const IPC::Message& message) { void SearchBox::OnChange(const string16& value, bool verbatim, - int selection_start, - int selection_end) { + size_t selection_start, + size_t selection_end) { value_ = value; verbatim_ = verbatim; selection_start_ = selection_start; selection_end_ = selection_end; - if (!render_view()->GetWebView() || !render_view()->GetWebView()->mainFrame()) - return; - extensions_v8::SearchBoxExtension::DispatchChange( - render_view()->GetWebView()->mainFrame()); + if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { + extensions_v8::SearchBoxExtension::DispatchChange( + render_view()->GetWebView()->mainFrame()); + } } -void SearchBox::OnSubmit(const string16& value, bool verbatim) { +void SearchBox::OnSubmit(const string16& value) { value_ = value; - verbatim_ = verbatim; + verbatim_ = true; + selection_start_ = selection_end_ = value_.size(); if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { extensions_v8::SearchBoxExtension::DispatchSubmit( render_view()->GetWebView()->mainFrame()); @@ -84,8 +83,10 @@ void SearchBox::OnSubmit(const string16& value, bool verbatim) { Reset(); } -void SearchBox::OnCancel() { - verbatim_ = false; +void SearchBox::OnCancel(const string16& value) { + value_ = value; + verbatim_ = true; + selection_start_ = selection_end_ = value_.size(); if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { extensions_v8::SearchBoxExtension::DispatchCancel( render_view()->GetWebView()->mainFrame()); @@ -95,29 +96,24 @@ void SearchBox::OnCancel() { void SearchBox::OnResize(const gfx::Rect& bounds) { rect_ = bounds; - if (!render_view()->GetWebView() || !render_view()->GetWebView()->mainFrame()) - return; - extensions_v8::SearchBoxExtension::DispatchResize( - render_view()->GetWebView()->mainFrame()); + if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { + extensions_v8::SearchBoxExtension::DispatchResize( + render_view()->GetWebView()->mainFrame()); + } } -void SearchBox::OnDetermineIfPageSupportsInstant(const string16& value, - bool verbatim, - int selection_start, - int selection_end) { - value_ = value; - verbatim_ = verbatim; - selection_start_ = selection_start; - selection_end_ = selection_end; - bool result = extensions_v8::SearchBoxExtension::PageSupportsInstant( - render_view()->GetWebView()->mainFrame()); - render_view()->Send(new ChromeViewHostMsg_InstantSupportDetermined( - render_view()->GetRoutingID(), render_view()->GetPageId(), result)); +void SearchBox::OnDetermineIfPageSupportsInstant() { + if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { + bool result = extensions_v8::SearchBoxExtension::PageSupportsInstant( + render_view()->GetWebView()->mainFrame()); + render_view()->Send(new ChromeViewHostMsg_InstantSupportDetermined( + render_view()->GetRoutingID(), render_view()->GetPageId(), result)); + } } void SearchBox::Reset() { + value_.clear(); verbatim_ = false; - value_ = string16(); selection_start_ = selection_end_ = 0; rect_ = gfx::Rect(); } diff --git a/chrome/renderer/searchbox.h b/chrome/renderer/searchbox.h index 9f3c104..8447870 100644 --- a/chrome/renderer/searchbox.h +++ b/chrome/renderer/searchbox.h @@ -1,19 +1,27 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. #ifndef CHROME_RENDERER_SEARCHBOX_H_ #define CHROME_RENDERER_SEARCHBOX_H_ -#include <string> #include <vector> +#include "base/basictypes.h" #include "base/string16.h" #include "chrome/common/instant_types.h" #include "content/public/renderer/render_view_observer.h" #include "content/public/renderer/render_view_observer_tracker.h" #include "ui/gfx/rect.h" +namespace content { +class RenderView; +} + +namespace IPC { +class Message; +} + class SearchBox : public content::RenderViewObserver, public content::RenderViewObserverTracker<SearchBox> { public: @@ -21,13 +29,13 @@ class SearchBox : public content::RenderViewObserver, virtual ~SearchBox(); // Sends ViewHostMsg_SetSuggestions to the browser. - void SetSuggestions(const std::vector<std::string>& suggestions, + void SetSuggestions(const std::vector<string16>& suggestions, InstantCompleteBehavior behavior); const string16& value() const { return value_; } bool verbatim() const { return verbatim_; } - uint32 selection_start() const { return selection_start_; } - uint32 selection_end() const { return selection_end_; } + size_t selection_start() const { return selection_start_; } + size_t selection_end() const { return selection_end_; } gfx::Rect GetRect(); private: @@ -36,23 +44,20 @@ class SearchBox : public content::RenderViewObserver, void OnChange(const string16& value, bool verbatim, - int selection_start, - int selection_end); - void OnSubmit(const string16& value, bool verbatim); - void OnCancel(); + size_t selection_start, + size_t selection_end); + void OnSubmit(const string16& value); + void OnCancel(const string16& value); void OnResize(const gfx::Rect& bounds); - void OnDetermineIfPageSupportsInstant(const string16& value, - bool verbatim, - int selection_start, - int selection_end); + void OnDetermineIfPageSupportsInstant(); // Sets the searchbox values to their initial value. void Reset(); string16 value_; bool verbatim_; - uint32 selection_start_; - uint32 selection_end_; + size_t selection_start_; + size_t selection_end_; gfx::Rect rect_; DISALLOW_COPY_AND_ASSIGN(SearchBox); diff --git a/chrome/renderer/searchbox_extension.cc b/chrome/renderer/searchbox_extension.cc index 29bddcf..fe027bb 100644 --- a/chrome/renderer/searchbox_extension.cc +++ b/chrome/renderer/searchbox_extension.cc @@ -4,27 +4,14 @@ #include "chrome/renderer/searchbox_extension.h" -#include <string> -#include <vector> - -#include "base/basictypes.h" -#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 "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/platform/WebString.h" #include "ui/base/resource/resource_bundle.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"; @@ -65,59 +52,17 @@ static const char kDispatchResizeEventScript[] = " 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);" - "})();"; + "if (window.chrome &&" + " window.chrome.searchBox &&" + " window.chrome.searchBox.onsubmit &&" + " typeof window.chrome.searchBox.onsubmit == 'function') {" + " true;" + "} else {" + " false;" + "}"; + // ---------------------------------------------------------------------------- class SearchBoxExtensionWrapper : public v8::Extension { @@ -169,7 +114,8 @@ class SearchBoxExtensionWrapper : public v8::Extension { SearchBoxExtensionWrapper::SearchBoxExtensionWrapper( const base::StringPiece& code) - : v8::Extension(kSearchBoxExtensionName, code.data(), 0, 0, code.size()) {} + : v8::Extension(kSearchBoxExtensionName, code.data(), 0, 0, code.size()) { +} v8::Handle<v8::FunctionTemplate> SearchBoxExtensionWrapper::GetNativeFunction( v8::Handle<v8::String> name) { @@ -197,12 +143,12 @@ v8::Handle<v8::FunctionTemplate> SearchBoxExtensionWrapper::GetNativeFunction( // static content::RenderView* SearchBoxExtensionWrapper::GetRenderView() { - WebFrame* webframe = WebFrame::frameForEnteredContext(); + WebKit::WebFrame* webframe = WebKit::WebFrame::frameForEnteredContext(); DCHECK(webframe) << "There should be an active frame since we just got " - "a native function called."; + "a native function called."; if (!webframe) return NULL; - WebView* webview = webframe->view(); + WebKit::WebView* webview = webframe->view(); if (!webview) return NULL; // can happen during closing return content::RenderView::FromWebView(webview); @@ -215,7 +161,7 @@ v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetValue( 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().data()), SearchBox::Get(render_view)->value().length()); } @@ -232,7 +178,7 @@ 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()); + return v8::Uint32::New(SearchBox::Get(render_view)->selection_start()); } // static @@ -240,7 +186,7 @@ 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()); + return v8::Uint32::New(SearchBox::Get(render_view)->selection_end()); } // static @@ -275,36 +221,30 @@ v8::Handle<v8::Value> SearchBoxExtensionWrapper::GetHeight( 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; + std::vector<string16> 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); + if (args.Length() && args[0]->IsObject()) { + v8::Local<v8::Object> suggestion_json = args[0]->ToObject(); + + 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("now"))) { + behavior = INSTANT_COMPLETE_NOW; + } else 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; + } else { + VLOG(1) << "Unsupported complete behavior '" + << *v8::String::Utf8Value(complete_value) << "'"; + } } - } 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")); @@ -312,34 +252,21 @@ v8::Handle<v8::Value> SearchBoxExtensionWrapper::SetSuggestions( 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)); + size_t length = suggestions_array->Length(); + for (size_t i = 0; i < length; i++) { + v8::Local<v8::Value> suggestion_value = suggestions_array->Get(i); if (!suggestion_value->IsObject()) continue; - v8::Local<v8::Object> suggestion_object = - suggestion_value.As<v8::Object>(); + v8::Local<v8::Object> suggestion_object = suggestion_value->ToObject(); 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; + string16 suggestion(reinterpret_cast<char16*>(*v8::String::Value( + suggestion_object_value->ToString()))); 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()) @@ -348,62 +275,48 @@ v8::Handle<v8::Value> SearchBoxExtensionWrapper::SetSuggestions( } // static -void Dispatch(WebFrame* frame, - WebString event_dispatch_script, - WebString no_event_handler_script) { +void Dispatch(WebKit::WebFrame* frame, WebKit::WebString 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)); - } + if (!frame) return; + frame->executeScript(WebKit::WebScriptSource(script)); } // static -void SearchBoxExtension::DispatchChange(WebFrame* frame) { - Dispatch(frame, kDispatchChangeEventScript, kUserInputScript); +void SearchBoxExtension::DispatchChange(WebKit::WebFrame* frame) { + Dispatch(frame, kDispatchChangeEventScript); } // static -void SearchBoxExtension::DispatchSubmit(WebFrame* frame) { - Dispatch(frame, kDispatchSubmitEventScript, kUserDoneScript); +void SearchBoxExtension::DispatchSubmit(WebKit::WebFrame* frame) { + Dispatch(frame, kDispatchSubmitEventScript); } // static -void SearchBoxExtension::DispatchCancel(WebFrame* frame) { - Dispatch(frame, kDispatchCancelEventScript, kUserDoneScript); +void SearchBoxExtension::DispatchCancel(WebKit::WebFrame* frame) { + Dispatch(frame, kDispatchCancelEventScript); } // static -void SearchBoxExtension::DispatchResize(WebFrame* frame) { - Dispatch(frame, kDispatchResizeEventScript, kSetOmniboxBoundsScript); +void SearchBoxExtension::DispatchResize(WebKit::WebFrame* frame) { + Dispatch(frame, kDispatchResizeEventScript); } // static -bool SearchBoxExtension::PageSupportsInstant(WebFrame* frame) { +bool SearchBoxExtension::PageSupportsInstant(WebKit::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))); - } + WebKit::WebScriptSource(kSupportsInstantScript)); + bool supports_instant = !v.IsEmpty() && v->BooleanValue(); + + // Send a resize message to tell the page that Chrome is actively using the + // searchbox API with it. The page uses the message to transition from + // "homepage" mode to "search" mode. + if (supports_instant) + DispatchResize(frame); - return supports_searchbox_api || supports_deprecated_api; + return supports_instant; } // static diff --git a/chrome/renderer/searchbox_extension.h b/chrome/renderer/searchbox_extension.h index 1c52ab4..04f1e00 100644 --- a/chrome/renderer/searchbox_extension.h +++ b/chrome/renderer/searchbox_extension.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. diff --git a/chrome/test/data/instant.html b/chrome/test/data/instant.html index 513a007..189d056 100644 --- a/chrome/test/data/instant.html +++ b/chrome/test/data/instant.html @@ -1,50 +1,45 @@ -<html> -<body> -<h1>Instant</h1> -<script> -window.chrome.sv = true; - -window.onsubmitcalls = 0; -window.onchangecalls = 0; -window.oncancelcalls = 0; -window.onresizecalls = 0; - -Object.prototype.clone = function() { - var copy = {}; - for (var prop in this) copy[prop] = this[prop]; - return copy; +<html><body><h1>Instant</h1><script> + +var onchangecalls = 0; +var onsubmitcalls = 0; +var oncancelcalls = 0; +var onresizecalls = 0; + +var value = ""; +var verbatim = false; +var height = 0; + +var suggestion = [ { value: "query suggestion" } ]; +var behavior = "now"; + +chrome.searchBox.onchange = function() { + onchangecalls++; + value = chrome.searchBox.value; + verbatim = chrome.searchBox.verbatim; + chrome.searchBox.setSuggestions({ + suggestions: suggestion, + complete_behavior: behavior + }); }; -window.beforeLoadSearchBox = window.chrome.searchBox.clone(); -window.lastSearchBox = window.chrome.searchBox.clone(); - -window.setSuggestionsArgument = { - suggestions: [ - { value: "defghi" } - ] +chrome.searchBox.onsubmit = function() { + onsubmitcalls++; + value = chrome.searchBox.value; + verbatim = chrome.searchBox.verbatim; }; -window.chrome.searchBox.onsubmit = function() { - window.chrome.searchBox.setSuggestions(setSuggestionsArgument); - window.lastSearchBox = window.chrome.searchBox.clone(); - window.onsubmitcalls++; +chrome.searchBox.oncancel = function() { + oncancelcalls++; + value = chrome.searchBox.value; + verbatim = chrome.searchBox.verbatim; }; -window.chrome.searchBox.onchange = function() { - window.chrome.searchBox.setSuggestions(setSuggestionsArgument); - window.lastSearchBox = window.chrome.searchBox.clone(); - window.onchangecalls++; +chrome.searchBox.onresize = function() { + onresizecalls++; + height = chrome.searchBox.height; }; -window.chrome.searchBox.oncancel = function() { - window.chrome.searchBox.setSuggestions(setSuggestionsArgument); - window.lastSearchBox = window.chrome.searchBox.clone(); - window.oncancelcalls++; -}; +if (chrome.searchBox.value != "") + chrome.searchBox.onchange(); -window.chrome.searchBox.onresize = function() { - window.onresizecalls++; -}; -</script> -</body> -</html> +</script></body></html> diff --git a/chrome/test/functional/PYAUTO_TESTS b/chrome/test/functional/PYAUTO_TESTS index a2fa0a5..46a0a73 100644 --- a/chrome/test/functional/PYAUTO_TESTS +++ b/chrome/test/functional/PYAUTO_TESTS @@ -150,12 +150,6 @@ '-infobars.OneClickInfobarTest.testNoSameIDSigninForTwoProfiles', # crbug.com/124280 '-downloads.DownloadsTest.testPauseAndResume', - # crbug.com/137042 - '-instant.InstantTest.testFindInCanDismissInstant', - '-instant.InstantTest.testInstantLoadsFor100CharsLongQuery', - '-instant.InstantTest.testInstantNavigation', - '-instant.InstantTest.testNTPCanDismissInstant', - '-instant.InstantTest.testNewWindowCanDismissInstant', # Mysteriously broken? # crbug.com/138857 @@ -285,8 +279,6 @@ # ================================================== # Disabled tests that need to be investigated/fixed. # ================================================== - # crbug.com/85310 - '-instant', # crbug.com/91033 '-omnibox.OmniboxTest.testOmniboxSearchHistory', # crbug.com/103379 @@ -380,8 +372,6 @@ '-infobars.InfobarTest.testPluginCrashForMultiTabs', # crbug.com/109035 '-infobars.InfobarTest.testPluginCrashInfobar', - # Flaky: crosbug.com/14439 - '-instant', # crosbug.com/14256 '-ntp.NTPTest.testLaunchAppFullScreen', # Content history broken in omnibox. crosbug.com/14416 @@ -578,7 +568,6 @@ # Add back the omnibox tests excluded from the CONTINUOUS suite above # because they do not produce omnibox results under virtual X on linux. # crbug.com/85310 - 'instant', 'omnibox.OmniboxTest.testAppComboNameWithSpecialCharSearch', 'omnibox.OmniboxTest.testAppNameWithNumberSearch', 'omnibox.OmniboxTest.testAppNameWithSpaceSearch', @@ -609,9 +598,6 @@ # crbug.com/109035 '-youtube.YoutubeTest.testPlayerBytes', '-youtube.YoutubeTest.testPlayerResolution', - # crbug.com/135211 - '-instant.InstantTest.testExtnPageCanDismissInstant', - '-instant.InstantSettingsTest.testEnableDisableInstant', ], 'chromeos': [ diff --git a/chrome/test/functional/instant.py b/chrome/test/functional/instant.py index 66817f5c..48829aa 100755 --- a/chrome/test/functional/instant.py +++ b/chrome/test/functional/instant.py @@ -9,7 +9,6 @@ import os import pyauto_functional # Must be imported before pyauto import pyauto - class InstantSettingsTest(pyauto.PyUITest): """Test Chrome Instant settings.""" @@ -18,19 +17,21 @@ class InstantSettingsTest(pyauto.PyUITest): Check if the setting can be enabled and disabled.""" self.assertFalse(self.GetPrefsInfo().Prefs(pyauto.kInstantEnabled), msg='Instant is enabled by default.') - # Enable instant. - self.AppendSwitchASCIIToCommandLine('instant-field-trial', 'instant'); + + # Enable Instant. self.SetPrefs(pyauto.kInstantEnabled, True) self.assertTrue(self.GetPrefsInfo().Prefs(pyauto.kInstantEnabled), msg='Instant is not enabled.') + + # Make sure Instant works. self.SetOmniboxText('google') self.assertTrue(self.WaitUntil( lambda: self.GetInstantInfo().get('current') and not self.GetInstantInfo().get('loading'))) title = self.GetInstantInfo()['title'] self.assertEqual('Google', title, msg='Instant did not load.') + # Disable Instant. - self.AppendSwitchASCIIToCommandLine('instant-field-trial', 'disabled'); self.SetPrefs(pyauto.kInstantEnabled, False) self.assertFalse(self.GetInstantInfo()['enabled'], msg='Instant is not disabled.') @@ -41,7 +42,6 @@ class InstantTest(pyauto.PyUITest): def setUp(self): pyauto.PyUITest.setUp(self) - self.AppendSwitchASCIIToCommandLine('instant-field-trial', 'instant'); self.SetPrefs(pyauto.kInstantEnabled, True) def _DoneLoading(self): @@ -64,9 +64,9 @@ class InstantTest(pyauto.PyUITest): return True return False - def testInstantNavigation(self): - """Test that instant navigates based on omnibox input.""" - # Initiate instant search (at default google.com). + def testInstantLoadsSearchResults(self): + """Test that Instant loads search results based on omnibox input.""" + # Initiate Instant search (at default google.com). self.SetOmniboxText('chrome instant') self.assertTrue(self.WaitUntil(self._DoneLoading)) location = self.GetInstantInfo()['location'] @@ -74,74 +74,74 @@ class InstantTest(pyauto.PyUITest): msg='No google.com in %s' % location) def testInstantCaseSensitivity(self): - """Verify that Chrome Instant results case insensitive.""" + """Verify that Chrome Instant results are case insensitive.""" # Text in lowercase letters. self.SetOmniboxText('google') self.assertTrue(self.WaitUntil(self._DoneLoading)) lowercase_instant_info = self.GetInstantInfo() + # Text in uppercase letters. self.SetOmniboxText('GOOGLE') self.assertTrue(self.WaitUntil(self._DoneLoading)) uppercase_instant_info = self.GetInstantInfo() + # Check lowercase and uppercase text results are same. self.assertEquals(lowercase_instant_info, uppercase_instant_info, - msg='Lowercase and Uppercase instant info doesn\'t match') + msg='Lowercase and uppercase Instant info do not match.') + # Text in mixed case letters. self.SetOmniboxText('GooGle') self.assertTrue(self.WaitUntil(self._DoneLoading)) mixedcase_instant_info = self.GetInstantInfo() + # Check mixedcase and uppercase text results are same. self.assertEquals(mixedcase_instant_info, uppercase_instant_info, - msg='Mixedcase and Uppercase instant info doesn\'t match') + msg='Mixedcase and uppercase Instant info do not match.') - def testInstantWithSearchEngineOtherThanGoogle(self): - """Verify that Instant is inactive for search engines other than Google.""" - # Check with Yahoo!. + def testInstantWithNonInstantSearchEngine(self): + """Verify that Instant is inactive for non-Instant search engines.""" + # Check with Yahoo!, which doesn't support Instant yet. self.MakeSearchEngineDefault('yahoo.com') self.assertFalse(self.GetInstantInfo()['active'], msg='Instant is active for Yahoo!') - # Check with Bing. + + # Check with Bing, which doesn't support Instant yet. self.MakeSearchEngineDefault('bing.com') self.assertFalse(self.GetInstantInfo()['active'], msg='Instant is active for Bing.') def testInstantDisabledInIncognito(self): - """Test that instant is disabled in Incognito mode.""" + """Test that Instant is disabled in incognito mode.""" self.RunCommand(pyauto.IDC_NEW_INCOGNITO_WINDOW) self.SetOmniboxText('google', windex=1) self.assertFalse(self.GetInstantInfo()['active'], - 'Instant enabled in Incognito mode.') - - def testInstantOverlayNotStoredInHistory(self): - """Test that instant overlay page is not stored in history.""" - self.SetOmniboxText('google') - self.assertTrue(self.WaitUntil(self._DoneLoading)) - history = self.GetHistoryInfo().History() - self.assertEqual(0, len(history)) + 'Instant enabled in incognito mode.') def testInstantDisabledForURLs(self): - """Test that instant is disabled for non-search URLs.""" + """Test that Instant is disabled for non-search URLs.""" self.SetOmniboxText('http://www.google.com/') self.WaitUntilOmniboxQueryDone() self.assertFalse(self.GetInstantInfo()['current'], 'Instant enabled for non-search URLs.') + self.SetOmniboxText('google.es') self.WaitUntilOmniboxQueryDone() self.assertFalse(self.GetInstantInfo()['current'], 'Instant enabled for non-search URLs.') + self.SetOmniboxText(self.GetFileURLForDataPath('title2.html')) self.WaitUntilOmniboxQueryDone() self.assertFalse(self.GetInstantInfo()['current'], 'Instant enabled for non-search URLs.') def testInstantDisabledForJavaScript(self): - """Test that instant is disabled for javascript URLs.""" + """Test that Instant is disabled for JavaScript URLs.""" self.SetOmniboxText('javascript:') self.assertFalse(self.GetInstantInfo()['current'], - 'Instant enabled for javascript URL.') + 'Instant enabled for JavaScript URL.') def testInstantLoadsFor100CharsLongQuery(self): - """Test that instant loads for search query of 100 characters.""" + """Test that Instant loads for search query of 100 characters.""" query = 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz' \ 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv' self.assertEqual(100, len(query)) @@ -149,49 +149,43 @@ class InstantTest(pyauto.PyUITest): self.assertTrue(self.WaitUntil(self._DoneLoadingGoogleQuery, args=[query])) def _BringUpInstant(self): - """Helper function to bring up instant.""" + """Helper function to bring up Instant.""" self.SetOmniboxText('google') self.assertTrue(self.WaitUntil(self._DoneLoading)) self.assertTrue('www.google.com' in self.GetInstantInfo()['location'], msg='No www.google.com in %s' % self.GetInstantInfo()['location']) + def testInstantOverlayNotStoredInHistory(self): + """Test that Instant overlay page is not stored in history.""" + self._BringUpInstant() + history = self.GetHistoryInfo().History() + self.assertEqual(0, len(history), msg='Instant URL stored in history.') + def testFindInCanDismissInstant(self): - """Test that instant preview is dismissed by find-in-page.""" + """Test that Instant preview is dismissed by find-in-page.""" self._BringUpInstant() self.OpenFindInPage() - self.assertEqual(self.GetActiveTabTitle(), 'New Tab') + self.assertFalse(self.GetInstantInfo()['current'], + 'Find-in-page does not dismiss Instant.') def testNTPCanDismissInstant(self): - """Test that instant preview is dismissed by adding new tab page.""" + """Test that Instant preview is dismissed by adding new tab page.""" self.NavigateToURL('about:blank'); self._BringUpInstant() self.AppendTab(pyauto.GURL('chrome://newtab')) - self.CloseTab(tab_index=1) - self.assertEqual(self.GetActiveTabTitle(), 'about:blank') + self.assertFalse(self.GetInstantInfo()['current'], + 'NTP does not dismiss Instant.') def testExtnPageCanDismissInstant(self): - """Test that instant preview is dismissed by extension page.""" + """Test that Instant preview is dismissed by extension page.""" self._BringUpInstant() self.AppendTab(pyauto.GURL('chrome://extensions')) - self.CloseTab(tab_index=1) - self.assertEqual(self.GetActiveTabTitle(), 'New Tab') - - def testNewWindowCanDismissInstant(self): - """Test that instant preview is dismissed by New Window.""" - self._BringUpInstant() - self.OpenNewBrowserWindow(True) - self.CloseBrowserWindow(1) - self.assertEqual(self.GetActiveTabTitle(), 'New Tab') - - def testPreFetchInstantURLNotInHistory(self): - """Test that pre-fetched URLs are not saved in History.""" - self._BringUpInstant() - history = self.GetHistoryInfo().History() - self.assertFalse(history, msg='Pre-feteched URL saved in History') + self.assertFalse(self.GetInstantInfo()['current'], + 'Extension page does not dismiss Instant.') def _AssertInstantDoesNotDownloadFile(self, path): - """Asserts instant does not download the specified file. + """Asserts Instant does not download the specified file. Args: path: Path to file. @@ -205,14 +199,13 @@ class InstantTest(pyauto.PyUITest): msg='Should not download: %s' % filepath) def testInstantDoesNotDownloadZipFile(self): - """Test that instant does not download zip file.""" + """Test that Instant does not download zip file.""" self._AssertInstantDoesNotDownloadFile(os.path.join('zip', 'test.zip')) def testInstantDoesNotDownloadPDFFile(self): - """Test that instant does not download PDF file.""" + """Test that Instant does not download PDF file.""" self._AssertInstantDoesNotDownloadFile(os.path.join('printing', 'cloud_print_unittest.pdf')) - if __name__ == '__main__': pyauto_functional.Main() |