diff options
65 files changed, 660 insertions, 151 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index adcf2f9..6c0f4de 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -3037,6 +3037,15 @@ each locale. --> <message name="IDS_EXTENSIONS_ENABLE" desc="The link for enabling extensions."> Enable </message> + <message name="IDS_EXTENSIONS_ENABLE_INCOGNITO" desc="The link for enabling extensions in incognito."> + Enable in Incognito + </message> + <message name="IDS_EXTENSIONS_DISABLE_INCOGNITO" desc="The link for disabling extensions in incognito."> + Disable in Incognito + </message> + <message name="IDS_EXTENSIONS_ENABLE_INCOGNITO_WARNING" desc="A warning message displayed when the user attempts to enable a non-incognito-safe extension in incognito."> + This extension doesn't officially support running in incognito mode. Enable anyway? + </message> <message name="IDS_EXTENSIONS_RELOAD" desc="The link for reloading extensions."> Reload </message> diff --git a/chrome/browser/automation/automation_provider.cc b/chrome/browser/automation/automation_provider.cc index 5bf39b0..d993afb 100644 --- a/chrome/browser/automation/automation_provider.cc +++ b/chrome/browser/automation/automation_provider.cc @@ -1787,8 +1787,8 @@ bool AutomationProvider::InterceptBrowserEventMessageFromExternalHost( } if (profile()->GetExtensionMessageService()) { - profile()->GetExtensionMessageService()-> - DispatchEventToRenderers(event_name.c_str(), json_args); + profile()->GetExtensionMessageService()->DispatchEventToRenderers( + event_name, json_args, profile()->IsOffTheRecord()); } return true; diff --git a/chrome/browser/dom_ui/dom_ui_factory.cc b/chrome/browser/dom_ui/dom_ui_factory.cc index 6f0c275..e35063e 100644 --- a/chrome/browser/dom_ui/dom_ui_factory.cc +++ b/chrome/browser/dom_ui/dom_ui_factory.cc @@ -35,11 +35,13 @@ DOMUI* NewDOMUI(TabContents* contents, const GURL& url) { // Special case for extensions. template<> DOMUI* NewDOMUI<ExtensionDOMUI>(TabContents* contents, const GURL& url) { - // Don't use a DOMUI for non-existent extensions. + // Don't use a DOMUI for non-existent extensions or for incognito tabs. The + // latter restriction is because we require extensions to run within a single + // process. ExtensionsService* service = contents->profile()->GetExtensionsService(); bool valid_extension = (service && service->GetExtensionById(url.host(), false)); - if (valid_extension) + if (valid_extension && !contents->profile()->IsOffTheRecord()) return new ExtensionDOMUI(contents); return NULL; } diff --git a/chrome/browser/extensions/execute_code_in_tab_function.cc b/chrome/browser/extensions/execute_code_in_tab_function.cc index da16d80..27ccf3f 100644 --- a/chrome/browser/extensions/execute_code_in_tab_function.cc +++ b/chrome/browser/extensions/execute_code_in_tab_function.cc @@ -48,7 +48,7 @@ bool ExecuteCodeInTabFunction::RunImpl() { Value* tab_value = NULL; EXTENSION_FUNCTION_VALIDATE(args->Get(0, &tab_value)); if (tab_value->IsType(Value::TYPE_NULL)) { - browser = dispatcher()->GetBrowser(); + browser = GetBrowser(); if (!browser) { error_ = keys::kNoCurrentWindowError; return false; @@ -57,8 +57,9 @@ bool ExecuteCodeInTabFunction::RunImpl() { return false; } else { EXTENSION_FUNCTION_VALIDATE(tab_value->GetAsInteger(&execute_tab_id_)); - if (!ExtensionTabUtil::GetTabById(execute_tab_id_, profile(), &browser, - NULL, &contents, NULL)) { + if (!ExtensionTabUtil::GetTabById(execute_tab_id_, profile(), + include_incognito(), + &browser, NULL, &contents, NULL)) { return false; } } @@ -127,7 +128,8 @@ void ExecuteCodeInTabFunction::DidLoadFile(bool success, bool ExecuteCodeInTabFunction::Execute(const std::string& code_string) { TabContents* contents = NULL; Browser* browser = NULL; - if (!ExtensionTabUtil::GetTabById(execute_tab_id_, profile(), &browser, NULL, + if (!ExtensionTabUtil::GetTabById(execute_tab_id_, profile(), + include_incognito(), &browser, NULL, &contents, NULL) && contents && browser) { SendResponse(false); return false; diff --git a/chrome/browser/extensions/extension_accessibility_api.cc b/chrome/browser/extensions/extension_accessibility_api.cc index 2ea3775..38745a7 100644 --- a/chrome/browser/extensions/extension_accessibility_api.cc +++ b/chrome/browser/extensions/extension_accessibility_api.cc @@ -157,8 +157,8 @@ void ExtensionAccessibilityEventRouter::DispatchEvent( const char* event_name, const std::string& json_args) { if (enabled_ && profile && profile->GetExtensionMessageService()) { - profile->GetExtensionMessageService()-> - DispatchEventToRenderers(event_name, json_args); + profile->GetExtensionMessageService()->DispatchEventToRenderers( + event_name, json_args, profile->IsOffTheRecord()); } } diff --git a/chrome/browser/extensions/extension_bookmark_manager_api.cc b/chrome/browser/extensions/extension_bookmark_manager_api.cc index 81848f3..a67b716 100644 --- a/chrome/browser/extensions/extension_bookmark_manager_api.cc +++ b/chrome/browser/extensions/extension_bookmark_manager_api.cc @@ -170,8 +170,8 @@ void ExtensionBookmarkManagerEventRouter::DispatchEvent(const char* event_name, std::string json_args; base::JSONWriter::Write(args, false, &json_args); - profile_->GetExtensionMessageService()-> - DispatchEventToRenderers(event_name, json_args); + profile_->GetExtensionMessageService()->DispatchEventToRenderers( + event_name, json_args, profile_->IsOffTheRecord()); } void ExtensionBookmarkManagerEventRouter::DispatchDragEvent( diff --git a/chrome/browser/extensions/extension_bookmarks_module.cc b/chrome/browser/extensions/extension_bookmarks_module.cc index 993cb05..d561e2a 100644 --- a/chrome/browser/extensions/extension_bookmarks_module.cc +++ b/chrome/browser/extensions/extension_bookmarks_module.cc @@ -170,8 +170,8 @@ void ExtensionBookmarkEventRouter::DispatchEvent(Profile *profile, const char* event_name, const std::string json_args) { if (profile->GetExtensionMessageService()) { - profile->GetExtensionMessageService()-> - DispatchEventToRenderers(event_name, json_args); + profile->GetExtensionMessageService()->DispatchEventToRenderers( + event_name, json_args, profile->IsOffTheRecord()); } } diff --git a/chrome/browser/extensions/extension_browser_event_router.cc b/chrome/browser/extensions/extension_browser_event_router.cc index 388863a..9b379bc 100644 --- a/chrome/browser/extensions/extension_browser_event_router.cc +++ b/chrome/browser/extensions/extension_browser_event_router.cc @@ -88,8 +88,8 @@ static void DispatchEvent(Profile* profile, const char* event_name, const std::string json_args) { if (profile->GetExtensionMessageService()) { - profile->GetExtensionMessageService()-> - DispatchEventToRenderers(event_name, json_args); + profile->GetExtensionMessageService()->DispatchEventToRenderers( + event_name, json_args, profile->IsOffTheRecord()); } } @@ -437,8 +437,8 @@ void ExtensionBrowserEventRouter::PageActionExecuted( DispatchOldPageActionEvent(profile, extension_id, page_action_id, tab_id, url, button); TabContents* tab_contents = NULL; - if (!ExtensionTabUtil::GetTabById(tab_id, profile, NULL, NULL, &tab_contents, - NULL)) { + if (!ExtensionTabUtil::GetTabById(tab_id, profile, profile->IsOffTheRecord(), + NULL, NULL, &tab_contents, NULL)) { return; } std::string event_name = std::string("pageAction/") + extension_id; diff --git a/chrome/browser/extensions/extension_browsertest.cc b/chrome/browser/extensions/extension_browsertest.cc index 9b4ddd8..d96d6dc 100644 --- a/chrome/browser/extensions/extension_browsertest.cc +++ b/chrome/browser/extensions/extension_browsertest.cc @@ -51,7 +51,8 @@ void ExtensionBrowserTest::SetUpCommandLine(CommandLine* command_line) { command_line->AppendSwitch(switches::kEnableExtensionToolstrips); } -bool ExtensionBrowserTest::LoadExtension(const FilePath& path) { +bool ExtensionBrowserTest::LoadExtensionImpl(const FilePath& path, + bool incognito_enabled) { ExtensionsService* service = browser()->profile()->GetExtensionsService(); size_t num_before = service->extensions()->size(); { @@ -67,9 +68,22 @@ bool ExtensionBrowserTest::LoadExtension(const FilePath& path) { if (num_after != (num_before + 1)) return false; + if (incognito_enabled) { + Extension* extension = service->extensions()->at(num_after - 1); + service->SetIsIncognitoEnabled(extension->id(), true); + } + return WaitForExtensionHostsToLoad(); } +bool ExtensionBrowserTest::LoadExtension(const FilePath& path) { + return LoadExtensionImpl(path, false); +} + +bool ExtensionBrowserTest::LoadExtensionIncognito(const FilePath& path) { + return LoadExtensionImpl(path, true); +} + // This class is used to simulate an installation abort by the user. class MockAbortExtensionInstallUI : public ExtensionInstallUI { public: diff --git a/chrome/browser/extensions/extension_browsertest.h b/chrome/browser/extensions/extension_browsertest.h index 263f582..8a34903 100644 --- a/chrome/browser/extensions/extension_browsertest.h +++ b/chrome/browser/extensions/extension_browsertest.h @@ -25,6 +25,9 @@ class ExtensionBrowserTest virtual void SetUpCommandLine(CommandLine* command_line); bool LoadExtension(const FilePath& path); + // Same as above, but enables the extension in incognito mode first. + bool LoadExtensionIncognito(const FilePath& path); + // |expected_change| indicates how many extensions should be installed (or // disabled, if negative). // 1 means you expect a new install, 0 means you expect an upgrade, -1 means @@ -93,6 +96,7 @@ class ExtensionBrowserTest bool InstallOrUpdateExtension(const std::string& id, const FilePath& path, bool should_cancel, int expected_change); + bool LoadExtensionImpl(const FilePath& path, bool incognito_enabled); bool WaitForExtensionHostsToLoad(); diff --git a/chrome/browser/extensions/extension_devtools_bridge.cc b/chrome/browser/extensions/extension_devtools_bridge.cc index c9a5f50..a6399d8 100644 --- a/chrome/browser/extensions/extension_devtools_bridge.cc +++ b/chrome/browser/extensions/extension_devtools_bridge.cc @@ -36,7 +36,8 @@ bool ExtensionDevToolsBridge::RegisterAsDevToolsClientHost() { TabStripModel* tab_strip; TabContents* contents; int tab_index; - if (ExtensionTabUtil::GetTabById(tab_id_, profile_, &browser, &tab_strip, + if (ExtensionTabUtil::GetTabById(tab_id_, profile_, true, + &browser, &tab_strip, &contents, &tab_index)) { DevToolsManager* devtools_manager = DevToolsManager::GetInstance(); devtools_manager->RegisterDevToolsClientHostFor( @@ -63,8 +64,8 @@ void ExtensionDevToolsBridge::InspectedTabClosing() { // TODO(knorton): Remove this event in favor of the standard tabs.onRemoved // event in extensions. std::string json("[{}]"); - profile_->GetExtensionMessageService()-> - DispatchEventToRenderers(on_tab_close_event_name_, json); + profile_->GetExtensionMessageService()->DispatchEventToRenderers( + on_tab_close_event_name_, json, profile_->IsOffTheRecord()); // This may result in this object being destroyed. extension_devtools_manager_->BridgeClosingForTab(tab_id_); @@ -87,7 +88,7 @@ void ExtensionDevToolsBridge::OnRpcMessage(const DevToolsMessageData& data) { && data.method_name == kApuPageEventMessageName) { std::string json = StringPrintf("[%s]", data.arguments[0].c_str()); profile_->GetExtensionMessageService()->DispatchEventToRenderers( - on_page_event_name_, json); + on_page_event_name_, json, profile_->IsOffTheRecord()); } } diff --git a/chrome/browser/extensions/extension_dom_ui.cc b/chrome/browser/extensions/extension_dom_ui.cc index de2d13b..b39b15b 100644 --- a/chrome/browser/extensions/extension_dom_ui.cc +++ b/chrome/browser/extensions/extension_dom_ui.cc @@ -76,11 +76,19 @@ void ExtensionDOMUI::ProcessDOMUIMessage(const std::string& message, has_callback); } -Browser* ExtensionDOMUI::GetBrowser() const { +Browser* ExtensionDOMUI::GetBrowser(bool include_incognito) const { + Browser* browser = NULL; TabContentsDelegate* tab_contents_delegate = tab_contents()->delegate(); - if (tab_contents_delegate) - return tab_contents_delegate->GetBrowser(); - return NULL; + if (tab_contents_delegate) { + browser = tab_contents_delegate->GetBrowser(); + if (browser && browser->profile()->IsOffTheRecord() && !include_incognito) { + // Fall back to the toplevel regular browser if we don't want to include + // incognito browsers. + browser = BrowserList::GetLastActiveWithProfile( + browser->profile()->GetOriginalProfile()); + } + } + return browser; } Profile* ExtensionDOMUI::GetProfile() { diff --git a/chrome/browser/extensions/extension_dom_ui.h b/chrome/browser/extensions/extension_dom_ui.h index 59a89a8..9d85ccb 100644 --- a/chrome/browser/extensions/extension_dom_ui.h +++ b/chrome/browser/extensions/extension_dom_ui.h @@ -41,11 +41,12 @@ class ExtensionDOMUI bool has_callback); // ExtensionFunctionDispatcher::Delegate - virtual Browser* GetBrowser() const; + virtual Browser* GetBrowser(bool include_incognito) const; virtual ExtensionDOMUI* GetExtensionDOMUI() { return this; } virtual gfx::NativeWindow GetFrameNativeWindow(); // ExtensionPopupHost::Delegate + virtual Browser* GetBrowser() const { return GetBrowser(true); } virtual RenderViewHost* GetRenderViewHost(); virtual Profile* GetProfile(); virtual gfx::NativeView GetNativeViewOfHost(); diff --git a/chrome/browser/extensions/extension_function.h b/chrome/browser/extensions/extension_function.h index 6db6443..06f69ab 100644 --- a/chrome/browser/extensions/extension_function.h +++ b/chrome/browser/extensions/extension_function.h @@ -68,6 +68,9 @@ class ExtensionFunction : public base::RefCounted<ExtensionFunction> { void set_has_callback(bool has_callback) { has_callback_ = has_callback; } bool has_callback() { return has_callback_; } + void set_include_incognito(bool include) { include_incognito_ = include; } + bool include_incognito() { return include_incognito_; } + // Execute the API. Clients should call set_raw_args() and // set_request_id() before calling this method. Derived classes should be // ready to return raw_result() and error() before returning from this @@ -88,6 +91,10 @@ class ExtensionFunction : public base::RefCounted<ExtensionFunction> { return NULL; } + Browser* GetBrowser() { + return dispatcher()->GetBrowser(include_incognito_); + } + // The peer to the dispatcher that will service this extension function call. scoped_refptr<ExtensionFunctionDispatcher::Peer> peer_; @@ -101,6 +108,9 @@ class ExtensionFunction : public base::RefCounted<ExtensionFunction> { // of this call. bool has_callback_; + // True if this callback should include information from incognito contexts. + bool include_incognito_; + DISALLOW_COPY_AND_ASSIGN(ExtensionFunction); }; diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc index 69e4d54..35b73a5 100644 --- a/chrome/browser/extensions/extension_function_dispatcher.cc +++ b/chrome/browser/extensions/extension_function_dispatcher.cc @@ -174,6 +174,7 @@ void FactoryRegistry::ResetFunctions() { RegisterFunction<ExtensionTestFailFunction>(); RegisterFunction<ExtensionTestLogFunction>(); RegisterFunction<ExtensionTestQuotaResetFunction>(); + RegisterFunction<ExtensionTestCreateIncognitoTabFunction>(); // Accessibility. RegisterFunction<GetFocusedControlFunction>(); @@ -212,7 +213,7 @@ ExtensionFunction* FactoryRegistry::NewFunction(const std::string& name) { gfx::NativeWindow ExtensionFunctionDispatcher::Delegate:: GetFrameNativeWindow() { - Browser* browser = GetBrowser(); + Browser* browser = GetBrowser(true); // If a browser is bound to this dispatcher, then return the widget hosting // the window. Extensions hosted in ExternalTabContainer objects may not // have a running browser instance. @@ -266,6 +267,9 @@ ExtensionFunctionDispatcher::ExtensionFunctionDispatcher( epm->RegisterExtensionProcess(extension_id(), render_view_host->process()->id()); + bool incognito_enabled = + profile()->GetExtensionsService()->IsIncognitoEnabled(extension->id()); + // Update the extension permissions. Doing this each time we create an EFD // ensures that new processes are informed of permissions for newly installed // extensions. @@ -273,6 +277,8 @@ ExtensionFunctionDispatcher::ExtensionFunctionDispatcher( extension->id(), extension->api_permissions())); render_view_host->Send(new ViewMsg_Extension_SetHostPermissions( extension->url(), extension->host_permissions())); + render_view_host->Send(new ViewMsg_Extension_ExtensionSetIncognitoEnabled( + extension->id(), incognito_enabled)); } ExtensionFunctionDispatcher::~ExtensionFunctionDispatcher() { @@ -280,8 +286,8 @@ ExtensionFunctionDispatcher::~ExtensionFunctionDispatcher() { peer_->dispatcher_ = NULL; } -Browser* ExtensionFunctionDispatcher::GetBrowser() { - return delegate_->GetBrowser(); +Browser* ExtensionFunctionDispatcher::GetBrowser(bool include_incognito) { + return delegate_->GetBrowser(include_incognito); } ExtensionPopupHost* ExtensionFunctionDispatcher::GetPopupHost() { @@ -324,6 +330,8 @@ void ExtensionFunctionDispatcher::HandleRequest(const std::string& name, function->SetArgs(args); function->set_request_id(request_id); function->set_has_callback(has_callback); + function->set_include_incognito( + profile()->GetExtensionsService()->IsIncognitoEnabled(extension_id())); ExtensionsService* service = profile()->GetExtensionsService(); DCHECK(service); diff --git a/chrome/browser/extensions/extension_function_dispatcher.h b/chrome/browser/extensions/extension_function_dispatcher.h index 38376e7..1bdfca2 100644 --- a/chrome/browser/extensions/extension_function_dispatcher.h +++ b/chrome/browser/extensions/extension_function_dispatcher.h @@ -34,7 +34,10 @@ class ExtensionFunctionDispatcher { public: class Delegate { public: - virtual Browser* GetBrowser() const = 0; + // Returns the browser that this delegate is associated with. If the browser + // is incognito, but |include_incognito_windows| is false, we fall back to + // the toplevel browser in the original profile. + virtual Browser* GetBrowser(bool include_incognito_windows) const = 0; // Returns the gfx::NativeWindow that contains the view hosting the // environment in which the function dispatcher resides. @@ -89,7 +92,9 @@ class ExtensionFunctionDispatcher { // Gets the browser extension functions should operate relative to. For // example, for positioning windows, or alert boxes, or creating tabs. - Browser* GetBrowser(); + // If |include_incognito| is false, and the appropriate browser is incognito, + // we will fall back to a regular browser window or NULL if unavailable. + Browser* GetBrowser(bool include_incognito); // Get the extension popup hosting environment for the ExtensionHost // or ExtensionDOMUI associted with this dispatcher. diff --git a/chrome/browser/extensions/extension_history_api.cc b/chrome/browser/extensions/extension_history_api.cc index c1808d5..75ed48a 100644 --- a/chrome/browser/extensions/extension_history_api.cc +++ b/chrome/browser/extensions/extension_history_api.cc @@ -140,8 +140,8 @@ void ExtensionHistoryEventRouter::DispatchEvent(Profile* profile, const char* event_name, const std::string& json_args) { if (profile && profile->GetExtensionMessageService()) { - profile->GetExtensionMessageService()-> - DispatchEventToRenderers(event_name, json_args); + profile->GetExtensionMessageService()->DispatchEventToRenderers( + event_name, json_args, profile->IsOffTheRecord()); } } diff --git a/chrome/browser/extensions/extension_host.cc b/chrome/browser/extensions/extension_host.cc index e139899..5c01d69 100644 --- a/chrome/browser/extensions/extension_host.cc +++ b/chrome/browser/extensions/extension_host.cc @@ -483,8 +483,8 @@ void ExtensionHost::ProcessDOMUIMessage(const std::string& message, int request_id, bool has_callback) { if (extension_function_dispatcher_.get()) { - extension_function_dispatcher_->HandleRequest(message, content, request_id, - has_callback); + extension_function_dispatcher_->HandleRequest( + message, content, request_id, has_callback); } } @@ -612,19 +612,28 @@ void ExtensionHost::HandleMouseLeave() { #endif } -Browser* ExtensionHost::GetBrowser() const { +Browser* ExtensionHost::GetBrowser(bool include_incognito) const { + Browser* browser = NULL; + if (view_.get()) - return view_->browser(); + browser = view_->browser(); - Profile* profile = render_view_host()->process()->profile(); - Browser* browser = BrowserList::GetLastActiveWithProfile(profile); + if (!browser || + (browser->profile()->IsOffTheRecord() && !include_incognito)) { + Profile* profile = render_view_host()->process()->profile(); + // Make sure we don't return an incognito browser without proper access. + if (!include_incognito) + profile = profile->GetOriginalProfile(); - // It's possible for a browser to exist, but to have never been active. - // This can happen if you launch the browser on a machine without an active - // desktop (a headless buildbot) or if you quickly give another app focus - // at launch time. This is easy to do with browser_tests. - if (!browser) - browser = BrowserList::FindBrowserWithProfile(profile); + browser = BrowserList::GetLastActiveWithProfile(profile); + + // It's possible for a browser to exist, but to have never been active. + // This can happen if you launch the browser on a machine without an active + // desktop (a headless buildbot) or if you quickly give another app focus + // at launch time. This is easy to do with browser_tests. + if (!browser) + browser = BrowserList::FindBrowserWithProfile(profile); + } // TODO(erikkay): can this still return NULL? Is Rafael's comment still // valid here? diff --git a/chrome/browser/extensions/extension_host.h b/chrome/browser/extensions/extension_host.h index fd155d7..83f2cb2 100644 --- a/chrome/browser/extensions/extension_host.h +++ b/chrome/browser/extensions/extension_host.h @@ -196,10 +196,11 @@ class ExtensionHost : public ExtensionPopupHost::PopupDelegate, // If this ExtensionHost has a view, this returns the Browser that view is a // part of. If this is a global background page, we use the active Browser // instead. - virtual Browser* GetBrowser() const; + virtual Browser* GetBrowser(bool include_incognito) const; virtual ExtensionHost* GetExtensionHost() { return this; } // ExtensionPopupHost::Delegate + virtual Browser* GetBrowser() const { return GetBrowser(true); } virtual RenderViewHost* GetRenderViewHost() { return render_view_host(); } virtual gfx::NativeView GetNativeViewOfHost() { return view()->native_view(); diff --git a/chrome/browser/extensions/incognito_noscript_apitest.cc b/chrome/browser/extensions/extension_incognito_apitest.cc index d4e71a5..29de8fb 100755 --- a/chrome/browser/extensions/incognito_noscript_apitest.cc +++ b/chrome/browser/extensions/extension_incognito_apitest.cc @@ -5,7 +5,8 @@ #include "chrome/browser/browser.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/browser_window.h" -#include "chrome/browser/extensions/extension_browsertest.h" +#include "chrome/browser/extensions/browser_action_test_util.h" +#include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extensions_service.h" #include "chrome/browser/extensions/user_script_master.h" #include "chrome/browser/profile.h" @@ -24,7 +25,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, IncognitoNoScript) { CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableExperimentalExtensionApis); ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("api_test") - .AppendASCII("incognito_no_script"))); + .AppendASCII("incognito").AppendASCII("content_scripts"))); // Open incognito window and navigate to test page. ui_test_utils::OpenURLOffTheRecord(browser()->profile(), @@ -56,21 +57,13 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, IncognitoYesScript) { // that loads to "modified". CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableExperimentalExtensionApis); - ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("api_test") - .AppendASCII("incognito_no_script"))); + ASSERT_TRUE(LoadExtensionIncognito(test_data_dir_.AppendASCII("api_test") + .AppendASCII("incognito").AppendASCII("content_scripts"))); // Dummy extension #2. ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("api_test") .AppendASCII("content_scripts").AppendASCII("isolated_world1"))); - // Now enable the incognito_no_script extension in incognito mode, and ensure - // that page titles are modified. - ExtensionsService* service = browser()->profile()->GetExtensionsService(); - service->extension_prefs()->SetIsIncognitoEnabled( - service->extensions()->at(1)->id(), true); - browser()->profile()->GetUserScriptMaster()->ReloadExtensionForTesting( - service->extensions()->at(1)); - // Open incognito window and navigate to test page. ui_test_utils::OpenURLOffTheRecord(browser()->profile(), GURL("http://www.example.com:1337/files/extensions/test_file.html")); @@ -86,3 +79,66 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, IncognitoYesScript) { &result); EXPECT_TRUE(result); } + +// Tests that the APIs in an incognito-enabled extension work properly. +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, Incognito) { + host_resolver()->AddRule("*", "127.0.0.1"); + StartHTTPServer(); + + ResultCatcher catcher; + + // Open incognito window and navigate to test page. + ui_test_utils::OpenURLOffTheRecord(browser()->profile(), + GURL("http://www.example.com:1337/files/extensions/test_file.html")); + + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableExperimentalExtensionApis); + ASSERT_TRUE(LoadExtensionIncognito(test_data_dir_ + .AppendASCII("incognito").AppendASCII("apis"))); + + EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); +} + +// Tests that the APIs in an incognito-disabled extension don't see incognito +// events or callbacks. +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, IncognitoDisabled) { + host_resolver()->AddRule("*", "127.0.0.1"); + StartHTTPServer(); + + ResultCatcher catcher; + + // Open incognito window and navigate to test page. + ui_test_utils::OpenURLOffTheRecord(browser()->profile(), + GURL("http://www.example.com:1337/files/extensions/test_file.html")); + + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableExperimentalExtensionApis); + ASSERT_TRUE(LoadExtension(test_data_dir_ + .AppendASCII("incognito").AppendASCII("apis_disabled"))); + + EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); +} + +// Test that opening a popup from an incognito browser window works properly. +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, IncognitoPopup) { + host_resolver()->AddRule("*", "127.0.0.1"); + StartHTTPServer(); + + ResultCatcher catcher; + + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableExperimentalExtensionApis); + ASSERT_TRUE(LoadExtensionIncognito(test_data_dir_ + .AppendASCII("incognito").AppendASCII("popup"))); + + // Open incognito window and navigate to test page. + ui_test_utils::OpenURLOffTheRecord(browser()->profile(), + GURL("http://www.example.com:1337/files/extensions/test_file.html")); + Browser* incognito_browser = BrowserList::FindBrowserWithType( + browser()->profile()->GetOffTheRecordProfile(), Browser::TYPE_NORMAL); + + // Simulate the incognito's browser action being clicked. + BrowserActionTestUtil(incognito_browser).Press(0); + + EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); +} diff --git a/chrome/browser/extensions/extension_message_service.cc b/chrome/browser/extensions/extension_message_service.cc index 9339196..dd464ea 100644 --- a/chrome/browser/extensions/extension_message_service.cc +++ b/chrome/browser/extensions/extension_message_service.cc @@ -92,10 +92,12 @@ static void DispatchOnMessage(const ExtensionMessageService::MessagePort& port, static void DispatchEvent(const ExtensionMessageService::MessagePort& port, const std::string& event_name, - const std::string& event_args) { + const std::string& event_args, + bool has_incognito_data) { ListValue args; args.Set(0, Value::CreateStringValue(event_name)); args.Set(1, Value::CreateStringValue(event_args)); + args.Set(2, Value::CreateBooleanValue(has_incognito_data)); port.sender->Send(new ViewMsg_ExtensionMessageInvoke( port.routing_id, ExtensionMessageService::kDispatchEvent, args)); } @@ -283,7 +285,7 @@ void ExtensionMessageService::OpenChannelToTabOnUIThread( TabContents* contents = NULL; MessagePort receiver; receiver.debug_info = 2; - if (ExtensionTabUtil::GetTabById(tab_id, source->profile(), + if (ExtensionTabUtil::GetTabById(tab_id, source->profile(), true, NULL, NULL, &contents, NULL)) { receiver.sender = contents->render_view_host(); receiver.routing_id = contents->render_view_host()->routing_id(); @@ -451,7 +453,8 @@ void ExtensionMessageService::PostMessageFromRenderer( } void ExtensionMessageService::DispatchEventToRenderers( - const std::string& event_name, const std::string& event_args) { + const std::string& event_name, const std::string& event_args, + bool has_incognito_data) { DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); std::set<int>& pids = listeners_[event_name]; @@ -467,7 +470,7 @@ void ExtensionMessageService::DispatchEventToRenderers( continue; } - DispatchEvent(renderer, event_name, event_args); + DispatchEvent(renderer, event_name, event_args, has_incognito_data); } } diff --git a/chrome/browser/extensions/extension_message_service.h b/chrome/browser/extensions/extension_message_service.h index b04780c..6b8ade6 100644 --- a/chrome/browser/extensions/extension_message_service.h +++ b/chrome/browser/extensions/extension_message_service.h @@ -82,7 +82,8 @@ class ExtensionMessageService // Send an event to every registered extension renderer. void DispatchEventToRenderers( - const std::string& event_name, const std::string& event_args); + const std::string& event_name, const std::string& event_args, + bool has_incognito_data); // Given an extension ID, opens a channel between the given // automation "port" or DevTools service and that extension. the diff --git a/chrome/browser/extensions/extension_page_actions_module.cc b/chrome/browser/extensions/extension_page_actions_module.cc index 1379b2f..5fcbf45 100644 --- a/chrome/browser/extensions/extension_page_actions_module.cc +++ b/chrome/browser/extensions/extension_page_actions_module.cc @@ -70,7 +70,8 @@ bool PageActionFunction::SetPageActionEnabled(bool enable) { // Find the TabContents that contains this tab id. TabContents* contents = NULL; - ExtensionTabUtil::GetTabById(tab_id, profile(), NULL, NULL, &contents, NULL); + ExtensionTabUtil::GetTabById(tab_id, profile(), include_incognito(), + NULL, NULL, &contents, NULL); if (!contents) { error_ = ExtensionErrorUtils::FormatErrorMessage(kNoTabError, IntToString(tab_id)); @@ -102,7 +103,8 @@ bool PageActionFunction::InitCommon(int tab_id) { // Find the TabContents that contains this tab id. contents_ = NULL; - ExtensionTabUtil::GetTabById(tab_id, profile(), NULL, NULL, &contents_, NULL); + ExtensionTabUtil::GetTabById(tab_id, profile(), include_incognito(), + NULL, NULL, &contents_, NULL); if (!contents_) { error_ = ExtensionErrorUtils::FormatErrorMessage(kNoTabError, IntToString(tab_id)); diff --git a/chrome/browser/extensions/extension_popup_api.cc b/chrome/browser/extensions/extension_popup_api.cc index 48e414c..d6edbc0 100644 --- a/chrome/browser/extensions/extension_popup_api.cc +++ b/chrome/browser/extensions/extension_popup_api.cc @@ -155,7 +155,7 @@ bool PopupShowFunction::RunImpl() { BubbleBorder::ArrowLocation arrow_location = (NULL != dispatcher()->GetExtensionHost()) ? BubbleBorder::BOTTOM_LEFT : BubbleBorder::TOP_LEFT; - popup_ = ExtensionPopup::Show(url, dispatcher()->GetBrowser(), + popup_ = ExtensionPopup::Show(url, GetBrowser(), dispatcher()->profile(), dispatcher()->GetFrameNativeWindow(), rect, @@ -238,5 +238,6 @@ void PopupEventRouter::OnPopupClosed(Profile* profile, profile->GetExtensionMessageService()->DispatchEventToRenderers( full_event_name, - base::JSONWriter::kEmptyArray); + base::JSONWriter::kEmptyArray, + profile->IsOffTheRecord()); } diff --git a/chrome/browser/extensions/extension_process_manager.cc b/chrome/browser/extensions/extension_process_manager.cc index 3121d3c..7ecee27 100644 --- a/chrome/browser/extensions/extension_process_manager.cc +++ b/chrome/browser/extensions/extension_process_manager.cc @@ -232,6 +232,19 @@ void ExtensionProcessManager::Observe(NotificationType type, break; } + case NotificationType::EXTENSION_INCOGNITO_CHANGED: { + Extension* extension = + Details<std::pair<Extension*, bool> >(details).ptr()->first; + bool incognito_enabled = + Details<std::pair<Extension*, bool> >(details).ptr()->second; + RenderProcessHost* rph = GetExtensionProcess(extension->id()); + if (rph) { + rph->Send(new ViewMsg_Extension_ExtensionSetIncognitoEnabled( + extension->id(), incognito_enabled)); + } + break; + } + case NotificationType::EXTENSION_HOST_DESTROYED: { ExtensionHost* host = Details<ExtensionHost>(details).ptr(); all_hosts_.erase(host); diff --git a/chrome/browser/extensions/extension_processes_api.cc b/chrome/browser/extensions/extension_processes_api.cc index 8495d9d..fda8c7f 100644 --- a/chrome/browser/extensions/extension_processes_api.cc +++ b/chrome/browser/extensions/extension_processes_api.cc @@ -24,8 +24,8 @@ bool GetProcessForTabFunction::RunImpl() { TabContents* contents = NULL; int tab_index = -1; - if (!ExtensionTabUtil::GetTabById(tab_id, profile(), NULL, NULL, - &contents, &tab_index)) + if (!ExtensionTabUtil::GetTabById(tab_id, profile(), include_incognito(), + NULL, NULL, &contents, &tab_index)) return false; int process_id = contents->GetRenderProcessHost()->id(); diff --git a/chrome/browser/extensions/extension_protocols.cc b/chrome/browser/extensions/extension_protocols.cc index 21dcd12..b6e66f6 100644 --- a/chrome/browser/extensions/extension_protocols.cc +++ b/chrome/browser/extensions/extension_protocols.cc @@ -6,12 +6,16 @@ #include "base/string_util.h" #include "chrome/browser/net/chrome_url_request_context.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host_request_info.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_file_util.h" #include "chrome/common/extensions/extension_resource.h" #include "chrome/common/url_constants.h" #include "googleurl/src/url_util.h" +#include "net/base/net_errors.h" #include "net/url_request/url_request_file_job.h" +#include "net/url_request/url_request_error_job.h" // Factory registered with URLRequest to create URLRequestJobs for extension:// // URLs. @@ -20,6 +24,16 @@ static URLRequestJob* CreateExtensionURLRequestJob(URLRequest* request, ChromeURLRequestContext* context = static_cast<ChromeURLRequestContext*>(request->context()); + // Don't allow toplevel navigations to extension resources in incognito mode. + // This is because an extension must run in a single process, and an incognito + // tab prevents that. + // TODO(mpcomplete): better error code. + const ResourceDispatcherHostRequestInfo* info = + ResourceDispatcherHost::InfoForRequest(request); + if (context->is_off_the_record() && + info && info->resource_type() == ResourceType::MAIN_FRAME) + return new URLRequestErrorJob(request, net::ERR_ADDRESS_UNREACHABLE); + // chrome-extension://extension-id/resource/path.js FilePath directory_path = context->GetPathForExtension(request->url().host()); if (directory_path.value().empty()) { diff --git a/chrome/browser/extensions/extension_tabs_module.cc b/chrome/browser/extensions/extension_tabs_module.cc index e00964d..c602cf6 100644 --- a/chrome/browser/extensions/extension_tabs_module.cc +++ b/chrome/browser/extensions/extension_tabs_module.cc @@ -36,11 +36,14 @@ namespace keys = extension_tabs_module_constants; // message if the window cannot be found by id. static Browser* GetBrowserInProfileWithId(Profile* profile, const int window_id, + bool include_incognito, std::string* error_message); -// |error_message| can optionally be passed in a will be set with an appropriate -// message if the tab cannot be found by id. -static bool GetTabById(int tab_id, Profile* profile, Browser** browser, +// |error_message| can optionally be passed in and will be set with an +// appropriate message if the tab cannot be found by id. +static bool GetTabById(int tab_id, Profile* profile, + bool include_incognito, + Browser** browser, TabStripModel** tab_strip, TabContents** contents, int* tab_index, std::string* error_message); @@ -117,6 +120,8 @@ DictionaryValue* ExtensionTabUtil::CreateTabValue( result->SetBoolean(keys::kSelectedKey, tab_strip && tab_index == tab_strip->selected_index()); result->SetString(keys::kTitleKey, UTF16ToWide(contents->GetTitle())); + result->SetBoolean(keys::kIncognitoKey, + contents->profile()->IsOffTheRecord()); if (status != TAB_LOADING) { NavigationEntry* entry = contents->controller().GetActiveEntry(); @@ -140,6 +145,8 @@ DictionaryValue* ExtensionTabUtil::CreateWindowValue(const Browser* browser, if (browser->window()) focused = browser->window()->IsActive(); + result->SetBoolean(keys::kIncognitoKey, + browser->profile()->IsOffTheRecord()); result->SetBoolean(keys::kFocusedKey, focused); gfx::Rect bounds = browser->window()->GetRestoredBounds(); @@ -173,6 +180,7 @@ bool ExtensionTabUtil::GetDefaultTab(Browser* browser, TabContents** contents, } bool ExtensionTabUtil::GetTabById(int tab_id, Profile* profile, + bool include_incognito, Browser** browser, TabStripModel** tab_strip, TabContents** contents, @@ -180,10 +188,13 @@ bool ExtensionTabUtil::GetTabById(int tab_id, Profile* profile, Browser* target_browser; TabStripModel* target_tab_strip; TabContents* target_contents; + Profile* incognito_profile = + include_incognito ? profile->GetOffTheRecordProfile() : NULL; for (BrowserList::const_iterator iter = BrowserList::begin(); iter != BrowserList::end(); ++iter) { target_browser = *iter; - if (target_browser->profile() == profile) { + if (target_browser->profile() == profile || + target_browser->profile() == incognito_profile) { target_tab_strip = target_browser->tabstrip_model(); for (int i = 0; i < target_tab_strip->count(); ++i) { target_contents = target_tab_strip->GetTabContentsAt(i); @@ -210,7 +221,8 @@ bool GetWindowFunction::RunImpl() { int window_id; EXTENSION_FUNCTION_VALIDATE(args_->GetAsInteger(&window_id)); - Browser* browser = GetBrowserInProfileWithId(profile(), window_id, &error_); + Browser* browser = GetBrowserInProfileWithId(profile(), window_id, + include_incognito(), &error_); if (!browser) return false; @@ -219,7 +231,7 @@ bool GetWindowFunction::RunImpl() { } bool GetCurrentWindowFunction::RunImpl() { - Browser* browser = dispatcher()->GetBrowser(); + Browser* browser = GetBrowser(); if (!browser) { error_ = keys::kNoCurrentWindowError; return false; @@ -250,10 +262,13 @@ bool GetAllWindowsFunction::RunImpl() { } result_.reset(new ListValue()); + Profile* incognito_profile = + include_incognito() ? profile()->GetOffTheRecordProfile() : NULL; for (BrowserList::const_iterator browser = BrowserList::begin(); browser != BrowserList::end(); ++browser) { // Only examine browsers in the current profile. - if ((*browser)->profile() == profile()) { + if ((*browser)->profile() == profile() || + (*browser)->profile() == incognito_profile) { static_cast<ListValue*>(result_.get())-> Append(ExtensionTabUtil::CreateWindowValue(*browser, populate_tabs)); } @@ -297,7 +312,7 @@ bool CreateWindowFunction::RunImpl() { // NOTE(rafaelw): It's ok if dispatcher_->GetBrowser() returns NULL here. // GetBrowserWindowBounds will default to saved "default" values for the app. WindowSizer::GetBrowserWindowBounds(std::wstring(), empty_bounds, - dispatcher()->GetBrowser(), &bounds, + GetBrowser(), &bounds, &maximized); // Any part of the bounds can optionally be set by the caller. @@ -351,7 +366,8 @@ bool UpdateWindowFunction::RunImpl() { DictionaryValue* update_props; EXTENSION_FUNCTION_VALIDATE(args->GetDictionary(1, &update_props)); - Browser* browser = GetBrowserInProfileWithId(profile(), window_id, &error_); + Browser* browser = GetBrowserInProfileWithId(profile(), window_id, + include_incognito(), &error_); if (!browser) return false; @@ -397,7 +413,8 @@ bool RemoveWindowFunction::RunImpl() { int window_id; EXTENSION_FUNCTION_VALIDATE(args_->GetAsInteger(&window_id)); - Browser* browser = GetBrowserInProfileWithId(profile(), window_id, &error_); + Browser* browser = GetBrowserInProfileWithId(profile(), window_id, + include_incognito(), &error_); if (!browser) return false; @@ -415,9 +432,10 @@ bool GetSelectedTabFunction::RunImpl() { if (!args_->IsType(Value::TYPE_NULL)) { EXTENSION_FUNCTION_VALIDATE(args_->GetAsInteger(&window_id)); - browser = GetBrowserInProfileWithId(profile(), window_id, &error_); + browser = GetBrowserInProfileWithId(profile(), window_id, + include_incognito(), &error_); } else { - browser = dispatcher()->GetBrowser(); + browser = GetBrowser(); if (!browser) error_ = keys::kNoCurrentWindowError; } @@ -441,9 +459,10 @@ bool GetAllTabsInWindowFunction::RunImpl() { int window_id = -1; if (!args_->IsType(Value::TYPE_NULL)) { EXTENSION_FUNCTION_VALIDATE(args_->GetAsInteger(&window_id)); - browser = GetBrowserInProfileWithId(profile(), window_id, &error_); + browser = GetBrowserInProfileWithId(profile(), window_id, + include_incognito(), &error_); } else { - browser = dispatcher()->GetBrowser(); + browser = GetBrowser(); if (!browser) error_ = keys::kNoCurrentWindowError; } @@ -465,9 +484,10 @@ bool CreateTabFunction::RunImpl() { if (args->HasKey(keys::kWindowIdKey)) { EXTENSION_FUNCTION_VALIDATE(args->GetInteger( keys::kWindowIdKey, &window_id)); - browser = GetBrowserInProfileWithId(profile(), window_id, &error_); + browser = GetBrowserInProfileWithId(profile(), window_id, + include_incognito(), &error_); } else { - browser = dispatcher()->GetBrowser(); + browser = GetBrowser(); if (!browser) error_ = keys::kNoCurrentWindowError; } @@ -536,8 +556,8 @@ bool GetTabFunction::RunImpl() { TabStripModel* tab_strip = NULL; TabContents* contents = NULL; int tab_index = -1; - if (!GetTabById(tab_id, profile(), NULL, &tab_strip, &contents, &tab_index, - &error_)) + if (!GetTabById(tab_id, profile(), include_incognito(), + NULL, &tab_strip, &contents, &tab_index, &error_)) return false; result_.reset(ExtensionTabUtil::CreateTabValue(contents, tab_strip, @@ -556,8 +576,8 @@ bool UpdateTabFunction::RunImpl() { TabStripModel* tab_strip = NULL; TabContents* contents = NULL; int tab_index = -1; - if (!GetTabById(tab_id, profile(), NULL, &tab_strip, &contents, &tab_index, - &error_)) + if (!GetTabById(tab_id, profile(), include_incognito(), + NULL, &tab_strip, &contents, &tab_index, &error_)) return false; if (tab_strip->IsTabPinned(tab_index)) { @@ -645,8 +665,9 @@ bool MoveTabFunction::RunImpl() { TabStripModel* source_tab_strip = NULL; TabContents* contents = NULL; int tab_index = -1; - if (!GetTabById(tab_id, profile(), &source_browser, &source_tab_strip, - &contents, &tab_index, &error_)) + if (!GetTabById(tab_id, profile(), include_incognito(), + &source_browser, &source_tab_strip, &contents, + &tab_index, &error_)) return false; if (update_props->HasKey(keys::kWindowIdKey)) { @@ -655,7 +676,7 @@ bool MoveTabFunction::RunImpl() { EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( keys::kWindowIdKey, &window_id)); target_browser = GetBrowserInProfileWithId(profile(), window_id, - &error_); + include_incognito(), &error_); if (!target_browser) return false; @@ -708,7 +729,8 @@ bool RemoveTabFunction::RunImpl() { Browser* browser = NULL; TabContents* contents = NULL; - if (!GetTabById(tab_id, profile(), &browser, NULL, &contents, NULL, &error_)) + if (!GetTabById(tab_id, profile(), include_incognito(), + &browser, NULL, &contents, NULL, &error_)) return false; int tab_index = browser->GetIndexOfController(&contents->controller()); @@ -734,9 +756,10 @@ bool CaptureVisibleTabFunction::RunImpl() { if (!args_->IsType(Value::TYPE_NULL)) { EXTENSION_FUNCTION_VALIDATE(args_->GetAsInteger(&window_id)); - browser = GetBrowserInProfileWithId(profile(), window_id, &error_); + browser = GetBrowserInProfileWithId(profile(), window_id, + include_incognito(), &error_); } else { - browser = dispatcher()->GetBrowser(); + browser = GetBrowser(); } if (!browser) { @@ -856,14 +879,14 @@ bool DetectTabLanguageFunction::RunImpl() { // in the current window. if (!args_->IsType(Value::TYPE_NULL)) { EXTENSION_FUNCTION_VALIDATE(args_->GetAsInteger(&tab_id)); - if (!GetTabById(tab_id, profile(), &browser, NULL, &contents, NULL, - &error_)) { + if (!GetTabById(tab_id, profile(), include_incognito(), + &browser, NULL, &contents, NULL, &error_)) { return false; } if (!browser || !contents) return false; } else { - browser = dispatcher()->GetBrowser(); + browser = GetBrowser(); if (!browser) return false; contents = browser->tabstrip_model()->GetSelectedTabContents(); @@ -924,10 +947,14 @@ void DetectTabLanguageFunction::GotLanguage(const std::string& language) { static Browser* GetBrowserInProfileWithId(Profile* profile, const int window_id, + bool include_incognito, std::string* error_message) { + Profile* incognito_profile = + include_incognito ? profile->GetOffTheRecordProfile() : NULL; for (BrowserList::const_iterator browser = BrowserList::begin(); - browser != BrowserList::end(); ++browser) { - if ((*browser)->profile() == profile && + browser != BrowserList::end(); ++browser) { + if (((*browser)->profile() == profile || + (*browser)->profile() == incognito_profile) && ExtensionTabUtil::GetWindowId(*browser) == window_id) return *browser; } @@ -939,13 +966,15 @@ static Browser* GetBrowserInProfileWithId(Profile* profile, return NULL; } -static bool GetTabById(int tab_id, Profile* profile, Browser** browser, +static bool GetTabById(int tab_id, Profile* profile, + bool include_incognito, + Browser** browser, TabStripModel** tab_strip, TabContents** contents, int* tab_index, std::string* error_message) { - if (ExtensionTabUtil::GetTabById(tab_id, profile, browser, tab_strip, - contents, tab_index)) + if (ExtensionTabUtil::GetTabById(tab_id, profile, include_incognito, + browser, tab_strip, contents, tab_index)) return true; if (error_message) diff --git a/chrome/browser/extensions/extension_tabs_module.h b/chrome/browser/extensions/extension_tabs_module.h index 5282a17..c7d36f8 100644 --- a/chrome/browser/extensions/extension_tabs_module.h +++ b/chrome/browser/extensions/extension_tabs_module.h @@ -45,7 +45,8 @@ class ExtensionTabUtil { int* tab_id); // Any out parameter (|browser|, |tab_strip|, |contents|, & |tab_index|) may // be NULL and will not be set within the function. - static bool GetTabById(int tab_id, Profile* profile, Browser** browser, + static bool GetTabById(int tab_id, Profile* profile, bool incognito_enabled, + Browser** browser, TabStripModel** tab_strip, TabContents** contents, int* tab_index); diff --git a/chrome/browser/extensions/extension_tabs_module_constants.cc b/chrome/browser/extensions/extension_tabs_module_constants.cc index 96176ea..6a75742 100644 --- a/chrome/browser/extensions/extension_tabs_module_constants.cc +++ b/chrome/browser/extensions/extension_tabs_module_constants.cc @@ -32,6 +32,7 @@ const wchar_t kTopKey[] = L"top"; const wchar_t kUrlKey[] = L"url"; const wchar_t kWidthKey[] = L"width"; const wchar_t kWindowIdKey[] = L"windowId"; +const wchar_t kIncognitoKey[] = L"incognito"; const char kStatusValueComplete[] = "complete"; const char kStatusValueLoading[] = "loading"; diff --git a/chrome/browser/extensions/extension_tabs_module_constants.h b/chrome/browser/extensions/extension_tabs_module_constants.h index f51bc5a..9f72501 100644 --- a/chrome/browser/extensions/extension_tabs_module_constants.h +++ b/chrome/browser/extensions/extension_tabs_module_constants.h @@ -36,6 +36,7 @@ extern const wchar_t kTopKey[]; extern const wchar_t kUrlKey[]; extern const wchar_t kWidthKey[]; extern const wchar_t kWindowIdKey[]; +extern const wchar_t kIncognitoKey[]; // Value consts. extern const char kStatusValueComplete[]; diff --git a/chrome/browser/extensions/extension_test_api.cc b/chrome/browser/extensions/extension_test_api.cc index ac0a2eb..48323d4 100644 --- a/chrome/browser/extensions/extension_test_api.cc +++ b/chrome/browser/extensions/extension_test_api.cc @@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "chrome/browser/extensions/extension_test_api.h" + +#include "chrome/browser/browser.h" #include "chrome/browser/profile.h" #include "chrome/browser/extensions/extensions_service.h" #include "chrome/browser/extensions/extensions_quota_service.h" -#include "chrome/browser/extensions/extension_test_api.h" #include "chrome/common/notification_service.h" bool ExtensionTestPassFunction::RunImpl() { @@ -41,3 +43,10 @@ bool ExtensionTestQuotaResetFunction::RunImpl() { quota->violators_.clear(); return true; } + +bool ExtensionTestCreateIncognitoTabFunction::RunImpl() { + std::string url; + EXTENSION_FUNCTION_VALIDATE(args_->GetAsString(&url)); + Browser::OpenURLOffTheRecord(profile(), GURL(url)); + return true; +} diff --git a/chrome/browser/extensions/extension_test_api.h b/chrome/browser/extensions/extension_test_api.h index 3b89e53..ee62627 100644 --- a/chrome/browser/extensions/extension_test_api.h +++ b/chrome/browser/extensions/extension_test_api.h @@ -31,4 +31,10 @@ class ExtensionTestQuotaResetFunction : public SyncExtensionFunction { DECLARE_EXTENSION_FUNCTION_NAME("test.resetQuota") }; +class ExtensionTestCreateIncognitoTabFunction : public SyncExtensionFunction { + ~ExtensionTestCreateIncognitoTabFunction() {} + virtual bool RunImpl(); + DECLARE_EXTENSION_FUNCTION_NAME("test.createIncognitoTab") +}; + #endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_TEST_API_H_ diff --git a/chrome/browser/extensions/extension_toolstrip_api.cc b/chrome/browser/extensions/extension_toolstrip_api.cc index 18d1839..56ad0a2 100644 --- a/chrome/browser/extensions/extension_toolstrip_api.cc +++ b/chrome/browser/extensions/extension_toolstrip_api.cc @@ -40,7 +40,7 @@ bool ToolstripFunction::RunImpl() { error_ = kNotAToolstripError; return false; } - Browser* browser = dispatcher()->GetBrowser(); + Browser* browser = GetBrowser(); if (!browser) { error_ = kNotAToolstripError; return false; @@ -133,8 +133,8 @@ void ToolstripEventRouter::DispatchEvent(Profile *profile, std::string json_args; base::JSONWriter::Write(&json, false, &json_args); std::string full_event_name = StringPrintf(event_name, routing_id); - profile->GetExtensionMessageService()-> - DispatchEventToRenderers(full_event_name, json_args); + profile->GetExtensionMessageService()->DispatchEventToRenderers( + full_event_name, json_args, profile->IsOffTheRecord()); } } diff --git a/chrome/browser/extensions/extension_ui_unittest.cc b/chrome/browser/extensions/extension_ui_unittest.cc index 4bffb44..f1bd648 100644 --- a/chrome/browser/extensions/extension_ui_unittest.cc +++ b/chrome/browser/extensions/extension_ui_unittest.cc @@ -50,8 +50,8 @@ namespace { // Produce test output. scoped_ptr<DictionaryValue> actual_output_data( - ExtensionsDOMHandler::CreateExtensionDetailValue(&extension, pages, - true)); + ExtensionsDOMHandler::CreateExtensionDetailValue(NULL, &extension, + pages, true)); // Compare the outputs. return expected_output_data->Equals(actual_output_data.get()); diff --git a/chrome/browser/extensions/extension_uitest.cc b/chrome/browser/extensions/extension_uitest.cc index 8192968..5537dc4 100644 --- a/chrome/browser/extensions/extension_uitest.cc +++ b/chrome/browser/extensions/extension_uitest.cc @@ -218,8 +218,10 @@ public: tab_dict.SetInteger(extension_tabs_module_constants::kIndexKey, 1); tab_dict.SetInteger(extension_tabs_module_constants::kWindowIdKey, 1); tab_dict.SetBoolean(extension_tabs_module_constants::kSelectedKey, true); + tab_dict.SetBoolean(extension_tabs_module_constants::kIncognitoKey, + false); tab_dict.SetString(extension_tabs_module_constants::kUrlKey, - "http://www.google.com"); + "http://www.google.com"); std::string tab_json; base::JSONWriter::Write(&tab_dict, false, &tab_json); @@ -327,7 +329,7 @@ class ExtensionTestBrowserEvents : public ExtensionUITest { const char* ExtensionTestBrowserEvents::events_[] = { // Window events. "[\"windows.onCreated\", \"[{'id':42,'focused':true,'top':0,'left':0," - "'width':100,'height':100}]\"]", + "'width':100,'height':100,'incognito':false}]\"]", "[\"windows.onRemoved\", \"[42]\"]", @@ -335,11 +337,11 @@ const char* ExtensionTestBrowserEvents::events_[] = { // Tab events. "[\"tabs.onCreated\", \"[{'id':42,'index':1,'windowId':1," - "'selected':true,'url':'http://www.google.com'}]\"]", + "'selected':true,'url':'http://www.google.com','incognito':false}]\"]", "[\"tabs.onUpdated\", \"[42, {'status': 'complete'," "'url':'http://www.google.com'}, {'id':42,'index':1,'windowId':1," - "'selected':true,'url':'http://www.google.com'}]\"]", + "'selected':true,'url':'http://www.google.com','incognito':false}]\"]", "[\"tabs.onMoved\", \"[42, {'windowId':1,'fromIndex':1,'toIndex':2}]\"]", diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc index d171767..9cf2fb9 100644 --- a/chrome/browser/extensions/extensions_service.cc +++ b/chrome/browser/extensions/extensions_service.cc @@ -530,13 +530,19 @@ base::Time ExtensionsService::LastPingDay(const std::string& extension_id) { } bool ExtensionsService::IsIncognitoEnabled(const std::string& extension_id) { - Extension* extension = GetExtensionById(extension_id, true); - if (!extension) - return false; + return extension_prefs_->IsIncognitoEnabled(extension_id); +} + +void ExtensionsService::SetIsIncognitoEnabled(const std::string& extension_id, + bool enabled) { + Extension* extension = GetExtensionByIdInternal(extension_id, true, true); + extension_prefs_->SetIsIncognitoEnabled(extension_id, enabled); - return extension_prefs_->IsIncognitoEnabled(extension_id) && - extension->HasApiPermission(Extension::kExperimentalPermission) && - extension->HasApiPermission(Extension::kIncognitoPermission); + std::pair<Extension*, bool> details(extension, enabled); + NotificationService::current()->Notify( + NotificationType::EXTENSION_INCOGNITO_CHANGED, + Source<Profile>(profile_), + Details<std::pair<Extension*, bool> >(&details)); } void ExtensionsService::CheckForExternalUpdates() { diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h index 078ac01..a95d0aa 100644 --- a/chrome/browser/extensions/extensions_service.h +++ b/chrome/browser/extensions/extensions_service.h @@ -106,10 +106,9 @@ class ExtensionsService const base::Time& time); virtual base::Time LastPingDay(const std::string& extension_id); - // Returns true if this extension can run in an incognito window. The - // decision is based on both user consent and the extension having the right - // permission. + // Whether this extension can run in an incognito window. bool IsIncognitoEnabled(const std::string& extension_id); + void SetIsIncognitoEnabled(const std::string& extension_id, bool enabled); const FilePath& install_directory() const { return install_directory_; } diff --git a/chrome/browser/extensions/extensions_ui.cc b/chrome/browser/extensions/extensions_ui.cc index 24dc43e..4201c17 100644 --- a/chrome/browser/extensions/extensions_ui.cc +++ b/chrome/browser/extensions/extensions_ui.cc @@ -31,6 +31,7 @@ #include "chrome/browser/renderer_host/render_view_host.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/tab_contents/tab_contents_view.h" +#include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_error_reporter.h" @@ -100,6 +101,12 @@ void ExtensionsUIHTMLSource::StartDataRequest(const std::string& path, l10n_util::GetString(IDS_EXTENSIONS_DISABLE)); localized_strings.SetString(L"enable", l10n_util::GetString(IDS_EXTENSIONS_ENABLE)); + localized_strings.SetString(L"disableIncognito", + l10n_util::GetString(IDS_EXTENSIONS_DISABLE_INCOGNITO)); + localized_strings.SetString(L"enableIncognito", + l10n_util::GetString(IDS_EXTENSIONS_ENABLE_INCOGNITO)); + localized_strings.SetString(L"enableIncognitoWarning", + l10n_util::GetString(IDS_EXTENSIONS_ENABLE_INCOGNITO_WARNING)); localized_strings.SetString(L"reload", l10n_util::GetString(IDS_EXTENSIONS_RELOAD)); localized_strings.SetString(L"uninstall", @@ -251,6 +258,8 @@ void ExtensionsDOMHandler::RegisterMessages() { NewCallback(this, &ExtensionsDOMHandler::HandleReloadMessage)); dom_ui_->RegisterMessageCallback("enable", NewCallback(this, &ExtensionsDOMHandler::HandleEnableMessage)); + dom_ui_->RegisterMessageCallback("enableIncognito", + NewCallback(this, &ExtensionsDOMHandler::HandleEnableIncognitoMessage)); dom_ui_->RegisterMessageCallback("uninstall", NewCallback(this, &ExtensionsDOMHandler::HandleUninstallMessage)); dom_ui_->RegisterMessageCallback("options", @@ -284,6 +293,7 @@ void ExtensionsDOMHandler::HandleRequestExtensionsData(const Value* value) { // themes. if (!(*extension)->IsTheme()) { extensions_list->Append(CreateExtensionDetailValue( + extensions_service_.get(), *extension, GetActivePagesForExtension((*extension)->id()), true)); extension_icons->push_back(PickExtensionIcon(*extension)); } @@ -293,6 +303,7 @@ void ExtensionsDOMHandler::HandleRequestExtensionsData(const Value* value) { extension != extensions->end(); ++extension) { if (!(*extension)->IsTheme()) { extensions_list->Append(CreateExtensionDetailValue( + extensions_service_.get(), *extension, GetActivePagesForExtension((*extension)->id()), false)); extension_icons->push_back(PickExtensionIcon(*extension)); } @@ -303,6 +314,10 @@ void ExtensionsDOMHandler::HandleRequestExtensionsData(const Value* value) { ->GetBoolean(prefs::kExtensionsUIDeveloperMode); results->SetBoolean(L"developerMode", developer_mode); + results->SetBoolean(L"experimentalIncognito", + CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableExperimentalExtensionApis)); + if (icon_loader_.get()) icon_loader_->Cancel(); @@ -402,6 +417,17 @@ void ExtensionsDOMHandler::HandleEnableMessage(const Value* value) { } } +void ExtensionsDOMHandler::HandleEnableIncognitoMessage(const Value* value) { + CHECK(value->IsType(Value::TYPE_LIST)); + const ListValue* list = static_cast<const ListValue*>(value); + CHECK(list->GetSize() == 2); + std::string extension_id, enable_str; + CHECK(list->GetString(0, &extension_id)); + CHECK(list->GetString(1, &enable_str)); + extensions_service_->SetIsIncognitoEnabled(extension_id, + (enable_str == "true")); +} + void ExtensionsDOMHandler::HandleUninstallMessage(const Value* value) { CHECK(value->IsType(Value::TYPE_LIST)); const ListValue* list = static_cast<const ListValue*>(value); @@ -657,8 +683,8 @@ DictionaryValue* ExtensionsDOMHandler::CreateContentScriptDetailValue( // Static DictionaryValue* ExtensionsDOMHandler::CreateExtensionDetailValue( - const Extension *extension, const std::vector<ExtensionPage>& pages, - bool enabled) { + ExtensionsService* service, const Extension *extension, + const std::vector<ExtensionPage>& pages, bool enabled) { DictionaryValue* extension_data = new DictionaryValue(); extension_data->SetString(L"id", extension->id()); @@ -666,6 +692,10 @@ DictionaryValue* ExtensionsDOMHandler::CreateExtensionDetailValue( extension_data->SetString(L"description", extension->description()); extension_data->SetString(L"version", extension->version()->GetString()); extension_data->SetBoolean(L"enabled", enabled); + extension_data->SetBoolean(L"enabledIncognito", + service ? service->IsIncognitoEnabled(extension->id()) : false); + extension_data->SetBoolean(L"incognitoSafe", + extension->HasApiPermission(Extension::kIncognitoPermission)); extension_data->SetBoolean(L"allow_reload", extension->location() == Extension::LOAD); diff --git a/chrome/browser/extensions/extensions_ui.h b/chrome/browser/extensions/extensions_ui.h index cabc5be..decdc64 100644 --- a/chrome/browser/extensions/extensions_ui.h +++ b/chrome/browser/extensions/extensions_ui.h @@ -106,9 +106,11 @@ class ExtensionsDOMHandler virtual void RegisterMessages(); // Extension Detail JSON Struct for page. (static for ease of testing). + // Note: service can be NULL in unit tests. static DictionaryValue* CreateExtensionDetailValue( - const Extension *extension, - const std::vector<ExtensionPage>&, + ExtensionsService* service, + const Extension* extension, + const std::vector<ExtensionPage>& pages, bool enabled); // ContentScript JSON Struct for page. (static for ease of testing). @@ -143,6 +145,9 @@ class ExtensionsDOMHandler // Callback for "enable" message. void HandleEnableMessage(const Value* value); + // Callback for "enableIncognito" message. + void HandleEnableIncognitoMessage(const Value* value); + // Callback for "uninstall" message. void HandleUninstallMessage(const Value* value); diff --git a/chrome/browser/extensions/user_script_master.cc b/chrome/browser/extensions/user_script_master.cc index a7d69f6..9506efd 100644 --- a/chrome/browser/extensions/user_script_master.cc +++ b/chrome/browser/extensions/user_script_master.cc @@ -294,6 +294,8 @@ UserScriptMaster::UserScriptMaster(const FilePath& script_dir, Profile* profile) Source<Profile>(profile_)); registrar_.Add(this, NotificationType::EXTENSION_LOADED, Source<Profile>(profile_)); + registrar_.Add(this, NotificationType::EXTENSION_INCOGNITO_CHANGED, + Source<Profile>(profile_)); registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, Source<Profile>(profile_)); } @@ -348,6 +350,22 @@ void UserScriptMaster::Observe(NotificationType type, StartScan(); break; } + case NotificationType::EXTENSION_INCOGNITO_CHANGED: { + // Toggle the incognito_enabled bit for any content scripts inside the + // extension. + Extension* extension = + Details<std::pair<Extension*, bool> >(details).ptr()->first; + bool incognito_enabled = + Details<std::pair<Extension*, bool> >(details).ptr()->second; + for (UserScriptList::iterator iter = lone_scripts_.begin(); + iter != lone_scripts_.end(); ++iter) { + if (iter->extension_id() == extension->id()) + (*iter).set_incognito_enabled(incognito_enabled); + } + if (extensions_service_ready_) + StartScan(); + break; + } case NotificationType::EXTENSION_UNLOADED: { // Remove any content scripts. @@ -378,14 +396,3 @@ void UserScriptMaster::StartScan() { script_reloader_->StartScan(user_script_dir_, lone_scripts_); } - -void UserScriptMaster::ReloadExtensionForTesting(Extension* extension) { - bool incognito_enabled = profile_->GetExtensionsService()-> - IsIncognitoEnabled(extension->id()); - for (UserScriptList::iterator iter = lone_scripts_.begin(); - iter != lone_scripts_.end(); ++iter) { - if (iter->extension_id() == extension->id()) - (*iter).set_incognito_enabled(incognito_enabled); - } - StartScan(); -} diff --git a/chrome/browser/extensions/user_script_master.h b/chrome/browser/extensions/user_script_master.h index 19562f0..cc0b437 100644 --- a/chrome/browser/extensions/user_script_master.h +++ b/chrome/browser/extensions/user_script_master.h @@ -47,11 +47,6 @@ class UserScriptMaster : public base::RefCountedThreadSafe<UserScriptMaster>, // Returns the path to the directory user scripts are stored in. FilePath user_script_dir() const { return user_script_dir_; } - // Note: this is only for testing. This will reload the scripts associated - // with the given extension. This is only temporary until we get better - // machinery in place for toggling incognito-enabled extensions. - void ReloadExtensionForTesting(Extension* extension); - protected: friend class base::RefCountedThreadSafe<UserScriptMaster>; diff --git a/chrome/browser/resources/extensions_ui.html b/chrome/browser/resources/extensions_ui.html index 39a6956..465fdcc 100644 --- a/chrome/browser/resources/extensions_ui.html +++ b/chrome/browser/resources/extensions_ui.html @@ -349,6 +349,7 @@ var extensionDataFormat = { "description": "Extension long format description", "version": "1.0.231", "enabled": "true", + "enabledIncognito": "false", "allow_reload": true, "order": 1, "options_url": "options.html", @@ -388,6 +389,7 @@ var extensionDataFormat = { "description": "Extension long format description", "version": "1.0.231", "enabled": "true", + "enabledIncognito": "false", "allow_reload": false, "order": 2, "icon": "", @@ -427,6 +429,9 @@ function toggleDevModeExpanded() { chrome.send('toggleDeveloperMode', []); } +// Experimental flag that enables toggling an extension as incognito enabled. +var experimentalIncognito = false; + /** * Takes the |extensionsData| input argument which represents data about the * currently installed/running extensions and populates the html jstemplate with @@ -475,6 +480,7 @@ var rendered_once_ = false; function returnExtensionsData(extensionsData){ domui_responded_ = true; devModeExpanded = extensionsData.developerMode; + experimentalIncognito = extensionsData.experimentalIncognito; var bodyContainer = document.getElementById('body-container'); var body = document.getElementsByTagName('body')[0]; @@ -549,6 +555,19 @@ function handleEnableExtension(node, enable) { } /** + * Handles a 'enableIncognito' or 'disableIncognito' button getting clicked. + */ +function handleEnableExtensionIncognito(node, enable) { + // Tell the C++ ExtensionDOMHandler to reload the extension. + if (enable && !node.incognitoSafe) { + if (!confirm(templateData["enableIncognitoWarning"])) + return; + } + chrome.send('enableIncognito', [node.extensionId, String(enable)]); + requestExtensionsData(); +} + +/** * Handles an 'uninstall' button getting clicked. */ function handleUninstallExtension(node) { @@ -815,6 +834,21 @@ function autoUpdate() { >RELOAD</a> <span jsdisplay="enabled && allow_reload">-</span> <a + jsvalues=".extensionId:id;.enabled:enabled" + jsdisplay="enabled && enabledIncognito && experimentalIncognito" + onclick="handleEnableExtensionIncognito(this, false)" + href="javascript:void();" + i18n-content="disableIncognito" + >DISABLE INCOGNITO</a> + <a + jsvalues=".extensionId:id;.enabled=enabled" + jsdisplay="enabled && !enabledIncognito && experimentalIncognito" + onclick="handleEnableExtensionIncognito(this, true)" + href="javascript:void();" + i18n-content="enableIncognito" + >ENABLE INCOGNITO</a> + <span jsdisplay="enabled && experimentalIncognito">-</span> + <a jsvalues=".extensionId:id" jsdisplay="enabled" onclick="handleEnableExtension(this, false)" diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 6a28cf8..9a506e0 100755..100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1156,6 +1156,7 @@ 'browser/extensions/extension_crash_recovery_browsertest.cc', 'browser/extensions/extension_history_apitest.cc', 'browser/extensions/extension_i18n_apitest.cc', + 'browser/extensions/extension_incognito_apitest.cc', 'browser/extensions/extension_javascript_url_apitest.cc', 'browser/extensions/extension_management_tests.cc', 'browser/extensions/extension_messages_apitest.cc', @@ -1167,7 +1168,6 @@ 'browser/extensions/extension_toolstrip_apitest.cc', 'browser/extensions/extension_websocket_apitest.cc', 'browser/extensions/fragment_navigation_apitest.cc', - 'browser/extensions/incognito_noscript_apitest.cc', 'browser/extensions/isolated_world_apitest.cc', 'browser/extensions/page_action_apitest.cc', 'browser/extensions/permissions_apitest.cc', diff --git a/chrome/common/extensions/api/extension_api.json b/chrome/common/extensions/api/extension_api.json index a347888..58894e4 100755 --- a/chrome/common/extensions/api/extension_api.json +++ b/chrome/common/extensions/api/extension_api.json @@ -424,7 +424,8 @@ "left": {"type": "integer", "description": "The offset of the window from the left edge of the screen in pixels."}, "width": {"type": "integer", "description": "The width of the window in pixels."}, "height": {"type": "integer", "description": "The height of the window in pixels."}, - "tabs": {"type": "array", "items": { "$ref": "Tab" }, "optional": true, "description": "Array of <a href='tabs.html#type-Tab'>Tab</a> objects representing the current tabs in the window."} + "tabs": {"type": "array", "items": { "$ref": "Tab" }, "optional": true, "description": "Array of <a href='tabs.html#type-Tab'>Tab</a> objects representing the current tabs in the window."}, + "incognito": {"type": "boolean", "description": "Whether the window is incognito."} } } ], @@ -615,7 +616,8 @@ "url": {"type": "string", "description": "The URL the tab is displaying."}, "title": {"type": "string", "optional": true, "description": "The title of the tab. This may not be available if the tab is loading."}, "favIconUrl": {"type": "string", "optional": true, "description": "The URL of the tab's favicon. This may not be available if the tab is loading."}, - "status": {"type": "string", "optional": true, "description": "Either <em>loading</em> or <em>complete</em>."} + "status": {"type": "string", "optional": true, "description": "Either <em>loading</em> or <em>complete</em>."}, + "incognito": {"type": "boolean", "description": "Whether the tab is in an incognito window."} } } ], @@ -2287,6 +2289,14 @@ "parameters": [ {"type": "string", "name": "message"} ] + }, + { + "name": "createIncognitoTab", + "type": "function", + "description": "Creates an incognito tab during internal testing. Succeeds even if the extension is not enabled in incognito mode.", + "parameters": [ + {"type": "string", "name": "url"} + ] } ], "events": [] diff --git a/chrome/common/notification_type.h b/chrome/common/notification_type.h index ef3c0f9..2abe637 100644 --- a/chrome/common/notification_type.h +++ b/chrome/common/notification_type.h @@ -677,6 +677,10 @@ class NotificationType { // details are an Extension*, and the source is a Profile*. EXTENSION_UPDATE_DISABLED, + // Sent when an extension has been enabled/disabled in incognito. The + // details are std::pair<Extension*, bool>, and the source is a Profile. + EXTENSION_INCOGNITO_CHANGED, + // Sent when an extension is about to be installed so we can (in the case of // themes) alert the user with a loading dialog. The source is the download // manager and the details are the download url. diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h index 608716c..be83c9a8 100644 --- a/chrome/common/render_messages_internal.h +++ b/chrome/common/render_messages_internal.h @@ -722,6 +722,12 @@ IPC_BEGIN_MESSAGES(View) GURL /* source extension's origin */, std::vector<URLPattern> /* URLPatterns the extension can access */) + // Tell the renderer process that the given extension is enabled or disabled + // for incognito mode. + IPC_MESSAGE_CONTROL2(ViewMsg_Extension_ExtensionSetIncognitoEnabled, + std::string /* extension_id */, + bool /* enabled */) + // Tell the renderer process all known page action ids for a particular // extension. IPC_MESSAGE_CONTROL2(ViewMsg_Extension_UpdatePageActions, diff --git a/chrome/renderer/extensions/extension_api_client_unittest.cc b/chrome/renderer/extensions/extension_api_client_unittest.cc index 39d275f..7158a8c 100644 --- a/chrome/renderer/extensions/extension_api_client_unittest.cc +++ b/chrome/renderer/extensions/extension_api_client_unittest.cc @@ -80,7 +80,7 @@ TEST_F(ExtensionAPIClientTest, CallbackDispatching) { "function callback(result) {" " assert(typeof result == 'object', 'result not object');" " assert(JSON.stringify(result) == '{\"id\":1,\"index\":1,\"windowId\":1," - "\"selected\":true," + "\"selected\":true,\"incognito\":false," "\"url\":\"http://www.google.com/\"}'," " 'incorrect result');" " console.log('pass')" @@ -103,7 +103,7 @@ TEST_F(ExtensionAPIClientTest, CallbackDispatching) { // Now send the callback a response ExtensionProcessBindings::HandleResponse( callback_id, true, "{\"id\":1,\"index\":1,\"windowId\":1,\"selected\":true," - "\"url\":\"http://www.google.com/\"}", ""); + "\"incognito\":false,\"url\":\"http://www.google.com/\"}", ""); // And verify that it worked ASSERT_EQ("pass", GetConsoleMessage()); diff --git a/chrome/renderer/extensions/extension_process_bindings.cc b/chrome/renderer/extensions/extension_process_bindings.cc index f065dd9..694c276 100644 --- a/chrome/renderer/extensions/extension_process_bindings.cc +++ b/chrome/renderer/extensions/extension_process_bindings.cc @@ -55,6 +55,9 @@ typedef std::map<std::string, bool> PermissionsMap; // A map of extension ID to permissions map. typedef std::map<std::string, PermissionsMap> ExtensionPermissionsMap; +// A map of extension ID to whether this extension was enabled in incognito. +typedef std::map<std::string, bool> IncognitoEnabledMap; + const char kExtensionName[] = "chrome/ExtensionProcessBindings"; const char* kExtensionDeps[] = { BaseJsV8Extension::kName, @@ -68,6 +71,7 @@ struct SingletonData { std::set<std::string> function_names_; PageActionIdMap page_action_ids_; ExtensionPermissionsMap permissions_; + std::map<std::string, bool> incognito_enabled_map_; }; static std::set<std::string>* GetFunctionNameSet() { @@ -82,6 +86,10 @@ static PermissionsMap* GetPermissionsMap(const std::string& extension_id) { return &Singleton<SingletonData>()->permissions_[extension_id]; } +static std::map<std::string, bool>* GetIncognitoEnabledMap() { + return &Singleton<SingletonData>()->incognito_enabled_map_; +} + static void GetActiveExtensionIDs(std::set<std::string>* extension_ids) { ExtensionPermissionsMap& permissions = Singleton<SingletonData>()->permissions_; @@ -237,6 +245,8 @@ class ExtensionImpl : public ExtensionBase { return v8::FunctionTemplate::New(GetPopupParentWindow); } else if (name->Equals(v8::String::New("SetExtensionActionIcon"))) { return v8::FunctionTemplate::New(SetExtensionActionIcon); + } else if (name->Equals(v8::String::New("CanAccessIncognito"))) { + return v8::FunctionTemplate::New(CanAccessIncognito); } return ExtensionBase::GetNativeFunction(name); @@ -495,6 +505,16 @@ class ExtensionImpl : public ExtensionBase { return StartRequestCommon(args, dict); } + // Returns true if the extension can access incognito data. + static v8::Handle<v8::Value> CanAccessIncognito(const v8::Arguments& args) { + std::string extension_id = ExtensionIdForCurrentContext(); + if (extension_id.empty()) + return v8::False(); + + bool enabled = (*GetIncognitoEnabledMap())[extension_id]; + return v8::Boolean::New(enabled); + } + static v8::Handle<v8::Value> GetRenderViewId(const v8::Arguments& args) { RenderView* renderview = bindings_utils::GetRenderViewForCurrentContext(); if (!renderview) @@ -520,6 +540,11 @@ void ExtensionProcessBindings::SetFunctionNames( ExtensionImpl::SetFunctionNames(names); } +void ExtensionProcessBindings::SetIncognitoEnabled( + const std::string& extension_id, bool enabled) { + (*GetIncognitoEnabledMap())[extension_id] = enabled; +} + // static void ExtensionProcessBindings::HandleResponse(int request_id, bool success, const std::string& response, diff --git a/chrome/renderer/extensions/extension_process_bindings.h b/chrome/renderer/extensions/extension_process_bindings.h index 2fd7613..93c7d24 100644 --- a/chrome/renderer/extensions/extension_process_bindings.h +++ b/chrome/renderer/extensions/extension_process_bindings.h @@ -46,6 +46,10 @@ class ExtensionProcessBindings { static void SetHostPermissions(const GURL& extension_url, const std::vector<URLPattern>& permissions); + // Sets whether incognito is enabled for a particular extension. + static void SetIncognitoEnabled(const std::string& extension_id, + bool enabled); + // Check if the extension in the currently running context has permission to // access the given extension function. Must be called with a valid V8 // context in scope. diff --git a/chrome/renderer/render_thread.cc b/chrome/renderer/render_thread.cc index cc622b1..30c56fd 100644 --- a/chrome/renderer/render_thread.cc +++ b/chrome/renderer/render_thread.cc @@ -457,6 +457,11 @@ void RenderThread::OnExtensionSetHostPermissions( ExtensionProcessBindings::SetHostPermissions(extension_url, permissions); } +void RenderThread::OnExtensionSetIncognitoEnabled( + const std::string& extension_id, bool enabled) { + ExtensionProcessBindings::SetIncognitoEnabled(extension_id, enabled); +} + void RenderThread::OnDOMStorageEvent( const ViewMsg_DOMStorageEvent_Params& params) { if (!dom_storage_event_dispatcher_.get()) @@ -511,6 +516,8 @@ void RenderThread::OnControlMessageReceived(const IPC::Message& msg) { OnExtensionSetAPIPermissions) IPC_MESSAGE_HANDLER(ViewMsg_Extension_SetHostPermissions, OnExtensionSetHostPermissions) + IPC_MESSAGE_HANDLER(ViewMsg_Extension_ExtensionSetIncognitoEnabled, + OnExtensionSetIncognitoEnabled) IPC_MESSAGE_HANDLER(ViewMsg_DOMStorageEvent, OnDOMStorageEvent) #if defined(IPC_MESSAGE_LOG_ENABLED) diff --git a/chrome/renderer/render_thread.h b/chrome/renderer/render_thread.h index fb99d74..a7ad1fb 100644 --- a/chrome/renderer/render_thread.h +++ b/chrome/renderer/render_thread.h @@ -180,6 +180,9 @@ class RenderThread : public RenderThreadBase, void OnExtensionSetHostPermissions( const GURL& extension_url, const std::vector<URLPattern>& permissions); + void OnExtensionSetIncognitoEnabled( + const std::string& extension_id, + bool enabled); void OnSetNextPageID(int32 next_page_id); void OnSetCSSColors(const std::vector<CSSColors::CSSColorMapping>& colors); void OnCreateNewView(const ViewMsg_New_Params& params); diff --git a/chrome/renderer/resources/event_bindings.js b/chrome/renderer/resources/event_bindings.js index aaea01c..7ea6bbf 100644 --- a/chrome/renderer/resources/event_bindings.js +++ b/chrome/renderer/resources/event_bindings.js @@ -45,12 +45,20 @@ var chrome = chrome || {}; // An array of all attached event objects, used for detaching on unload. var allAttachedEvents = []; + // By default (for content scripts), this function returns false. Extension + // contexts override this to do the right thing. + chromeHidden.canAccessIncognito = function() { + return false; + } + chromeHidden.Event = {}; // Dispatches a named event with the given JSON array, which is deserialized // before dispatch. The JSON array is the list of arguments that will be // sent with the event callback. - chromeHidden.Event.dispatchJSON = function(name, args) { + chromeHidden.Event.dispatchJSON = function(name, args, hasIncognitoData) { + if (hasIncognitoData && !chromeHidden.canAccessIncognito()) + return; if (attachedNamedEvents[name]) { if (args) { args = JSON.parse(args); diff --git a/chrome/renderer/resources/extension_process_bindings.js b/chrome/renderer/resources/extension_process_bindings.js index f543366..e91fe27 100644 --- a/chrome/renderer/resources/extension_process_bindings.js +++ b/chrome/renderer/resources/extension_process_bindings.js @@ -18,12 +18,17 @@ var chrome = chrome || {}; native function GetPopupParentWindow(); native function GetPopupView(); native function SetExtensionActionIcon(); + native function CanAccessIncognito(); if (!chrome) chrome = {}; var chromeHidden = GetChromeHidden(); + chromeHidden.canAccessIncognito = function() { + return CanAccessIncognito(); + } + // Validate arguments. chromeHidden.validationTypes = []; chromeHidden.validate = function(args, schemas) { diff --git a/chrome/test/data/extensions/api_test/incognito/apis/background.html b/chrome/test/data/extensions/api_test/incognito/apis/background.html new file mode 100644 index 0000000..ff13197 --- /dev/null +++ b/chrome/test/data/extensions/api_test/incognito/apis/background.html @@ -0,0 +1,76 @@ +<script> +var normalWindow, normalTab; +var incognitoWindow, incognitoTab; + +var pass = chrome.test.callbackPass; +var assertEq = chrome.test.assertEq; +var assertTrue = chrome.test.assertTrue; + +chrome.test.runTests([ + function setupWindows() { + // The test harness should have set us up with 2 windows: 1 incognito + // and 1 regular. Verify that we can see both when we ask for it. + chrome.windows.getAll({populate: true}, pass(function(windows) { + assertEq(2, windows.length); + + if (windows[0].incognito) { + incognitoWindow = windows[0]; + normalWindow = windows[1]; + } else { + normalWindow = windows[0]; + incognitoWindow = windows[1]; + } + normalTab = normalWindow.tabs[0]; + incognitoTab = incognitoWindow.tabs[0]; + assertTrue(!normalWindow.incognito); + assertTrue(incognitoWindow.incognito); + })); + }, + + // Tests that we can update an incognito tab and get the event for it. + function tabUpdate() { + var newUrl = "about:blank"; + + // Prepare the event listeners first. + var done = chrome.test.listenForever(chrome.tabs.onUpdated, + function(id, info, tab) { + if (id == incognitoTab.id) { + assertTrue(tab.incognito); + assertEq(newUrl, tab.url); + if (info.status == "complete") + done(); + } + }); + + // Update our tabs. + chrome.tabs.update(incognitoTab.id, {"url": newUrl}, pass()); + }, + + // Tests a sequence of tab API calls. + function tabNested() { + // Setup our listeners. We check that the events fire in order. + var eventCounter = 0; + chrome.test.listenOnce(chrome.tabs.onCreated, function(tab) { + assertEq(1, ++eventCounter); + assertEq(incognitoTab.windowId, tab.windowId); + assertTrue(tab.incognito); + }); + chrome.test.listenOnce(chrome.tabs.onMoved, function(tabId) { + assertEq(2, ++eventCounter); + }); + chrome.test.listenOnce(chrome.tabs.onRemoved, function(tabId) { + assertEq(3, ++eventCounter); + }); + + // Create, select, move, and close a tab in our incognito window. + chrome.tabs.create({windowId: incognitoTab.windowId}, + pass(function(tab) { + chrome.tabs.move(tab.id, {index: 0}, + pass(function(tab) { + assertEq(incognitoTab.incognito, tab.incognito); + chrome.tabs.remove(tab.id, pass()); + })); + })); + } +]); +</script> diff --git a/chrome/test/data/extensions/api_test/incognito/apis/manifest.json b/chrome/test/data/extensions/api_test/incognito/apis/manifest.json new file mode 100644 index 0000000..d8f6450 --- /dev/null +++ b/chrome/test/data/extensions/api_test/incognito/apis/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "incognito apitest", + "version": "0.1", + "description": "test that an incognito extension behaves properly", + "background_page": "background.html", + "permissions": ["tabs", "experimental", "incognito"] +} diff --git a/chrome/test/data/extensions/api_test/incognito/apis_disabled/background.html b/chrome/test/data/extensions/api_test/incognito/apis_disabled/background.html new file mode 100644 index 0000000..8e0868d --- /dev/null +++ b/chrome/test/data/extensions/api_test/incognito/apis_disabled/background.html @@ -0,0 +1,28 @@ +<script> +var normalWindow, normalTab; + +var pass = chrome.test.callbackPass; +var assertEq = chrome.test.assertEq; +var assertTrue = chrome.test.assertTrue; + +chrome.test.runTests([ + function getAllWindows() { + // The test harness should have set us up with 2 windows: 1 incognito + // and 1 regular. We should only see the regular one. + chrome.windows.getAll({populate: true}, pass(function(windows) { + assertEq(1, windows.length); + normalWindow = windows[0]; + assertTrue(!normalWindow.incognito); + })); + }, + + function tabEvents() { + chrome.test.listenOnce(chrome.tabs.onCreated, function(tab) { + assertTrue(!tab.incognito); + }); + + chrome.test.createIncognitoTab("about:blank"); + chrome.tabs.create({url: "about:blank"}, pass()); + }, +]); +</script> diff --git a/chrome/test/data/extensions/api_test/incognito/apis_disabled/manifest.json b/chrome/test/data/extensions/api_test/incognito/apis_disabled/manifest.json new file mode 100644 index 0000000..b26f0cb --- /dev/null +++ b/chrome/test/data/extensions/api_test/incognito/apis_disabled/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "incognito apitest", + "version": "0.1", + "description": "test that an non-incognito extension doesn't see incognito events", + "background_page": "background.html", + "permissions": ["tabs"] +} diff --git a/chrome/test/data/extensions/api_test/incognito_no_script/change_page_title.js b/chrome/test/data/extensions/api_test/incognito/content_scripts/change_page_title.js index ed4cfd2..ed4cfd2 100755..100644 --- a/chrome/test/data/extensions/api_test/incognito_no_script/change_page_title.js +++ b/chrome/test/data/extensions/api_test/incognito/content_scripts/change_page_title.js diff --git a/chrome/test/data/extensions/api_test/incognito_no_script/manifest.json b/chrome/test/data/extensions/api_test/incognito/content_scripts/manifest.json index e8f013c..e8f013c 100755..100644 --- a/chrome/test/data/extensions/api_test/incognito_no_script/manifest.json +++ b/chrome/test/data/extensions/api_test/incognito/content_scripts/manifest.json diff --git a/chrome/test/data/extensions/api_test/incognito/popup/manifest.json b/chrome/test/data/extensions/api_test/incognito/popup/manifest.json new file mode 100644 index 0000000..c1683fe --- /dev/null +++ b/chrome/test/data/extensions/api_test/incognito/popup/manifest.json @@ -0,0 +1,9 @@ +{ + "name": "Incognito popup tester", + "version": "0.1", + "description": "apitest for popups in incognito mode", + "permissions": [ "tabs", "experimental", "incognito" ], + "browser_action": { + "popup": "popup.html" + } +} diff --git a/chrome/test/data/extensions/api_test/incognito/popup/popup.html b/chrome/test/data/extensions/api_test/incognito/popup/popup.html new file mode 100644 index 0000000..122da56 --- /dev/null +++ b/chrome/test/data/extensions/api_test/incognito/popup/popup.html @@ -0,0 +1,15 @@ +<script> +var pass = chrome.test.callbackPass; +var assertEq = chrome.test.assertEq; +var assertTrue = chrome.test.assertTrue; + +chrome.test.runTests([ + function getCurrentWindow() { + // With incognito enabled, we should get our current window (which should + // be incognito). + chrome.windows.getCurrent(pass(function(win) { + assertTrue(win.incognito); + })); + } +]); +</script> diff --git a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json index c76c336..228afc6 100644 --- a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json +++ b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json @@ -7,6 +7,8 @@ "permissions": ["http://*.google.com/*", "https://*.google.com/*"], "allow_reload": false, "order": 2, + "enabledIncognito": false, + "incognitoSafe": false, "content_scripts": [ { "matches": ["file://*", "http://*.google.com/*", "https://*.google.com/*"], diff --git a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension2.json b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension2.json index d6a70f5..e84e51a 100644 --- a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension2.json +++ b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension2.json @@ -7,6 +7,8 @@ "permissions": [], "allow_reload": false, "order": 2, + "enabledIncognito": false, + "incognitoSafe": false, "content_scripts": [], "views": [ { diff --git a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension3.json b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension3.json index 564053a..4a4ce6a 100644 --- a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension3.json +++ b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension3.json @@ -7,6 +7,8 @@ "permissions": [], "allow_reload": false, "order": 2, + "enabledIncognito": false, + "incognitoSafe": false, "content_scripts": [], "views": [] } |