diff options
24 files changed, 925 insertions, 99 deletions
diff --git a/chrome/browser/extensions/api/DEPS b/chrome/browser/extensions/api/DEPS index 03792c6..059fc1c 100644 --- a/chrome/browser/extensions/api/DEPS +++ b/chrome/browser/extensions/api/DEPS @@ -9,6 +9,7 @@ include_rules = [ specific_include_rules = { ".*test.*": [ + "+chrome/browser/ui/views/frame", "+components/captive_portal", ], } diff --git a/chrome/browser/extensions/api/sessions/sessions_api.cc b/chrome/browser/extensions/api/sessions/sessions_api.cc index 35131e3..c46f0e4 100644 --- a/chrome/browser/extensions/api/sessions/sessions_api.cc +++ b/chrome/browser/extensions/api/sessions/sessions_api.cc @@ -424,7 +424,9 @@ void SessionsRestoreFunction::SetResultRestoredTab( bool SessionsRestoreFunction::SetResultRestoredWindow(int window_id) { WindowController* controller = NULL; - if (!windows_util::GetWindowFromWindowID(this, window_id, &controller)) { + if (!windows_util::GetWindowFromWindowID( + this, window_id, WindowController::GetDefaultWindowFilter(), + &controller)) { // error_ is set by GetWindowFromWindowId function call. return false; } diff --git a/chrome/browser/extensions/api/tabs/tabs_api.cc b/chrome/browser/extensions/api/tabs/tabs_api.cc index a2d5aab..f46760c 100644 --- a/chrome/browser/extensions/api/tabs/tabs_api.cc +++ b/chrome/browser/extensions/api/tabs/tabs_api.cc @@ -116,6 +116,29 @@ using api::extension_types::InjectDetails; namespace { +template <typename T> +class ApiParameterExtractor { + public: + explicit ApiParameterExtractor(T* params) : params_(params) {} + ~ApiParameterExtractor() {} + + bool populate_tabs() { + if (params_->get_info.get() && params_->get_info->populate.get()) + return *params_->get_info->populate; + return false; + } + + WindowController::TypeFilter type_filters() { + if (params_->get_info.get() && params_->get_info->window_types.get()) + return WindowController::GetFilterFromWindowTypes( + *params_->get_info->window_types.get()); + return WindowController::GetDefaultWindowFilter(); + } + + private: + T* params_; +}; + bool GetBrowserFromWindowID(ChromeUIThreadExtensionFunction* function, int window_id, Browser** browser) { @@ -265,18 +288,14 @@ bool WindowsGetFunction::RunSync() { scoped_ptr<windows::Get::Params> params(windows::Get::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); - bool populate_tabs = false; - if (params->get_info.get() && params->get_info->populate.get()) - populate_tabs = *params->get_info->populate; - + ApiParameterExtractor<windows::Get::Params> extractor(params.get()); WindowController* controller; - if (!windows_util::GetWindowFromWindowID(this, - params->window_id, - &controller)) { + if (!windows_util::GetWindowFromWindowID( + this, params->window_id, extractor.type_filters(), &controller)) { return false; } - if (populate_tabs) + if (extractor.populate_tabs()) SetResult(controller->CreateWindowValueWithTabs(extension())); else SetResult(controller->CreateWindowValue()); @@ -288,17 +307,14 @@ bool WindowsGetCurrentFunction::RunSync() { windows::GetCurrent::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); - bool populate_tabs = false; - if (params->get_info.get() && params->get_info->populate.get()) - populate_tabs = *params->get_info->populate; - + ApiParameterExtractor<windows::GetCurrent::Params> extractor(params.get()); WindowController* controller; - if (!windows_util::GetWindowFromWindowID(this, - extension_misc::kCurrentWindowId, - &controller)) { + if (!windows_util::GetWindowFromWindowID( + this, extension_misc::kCurrentWindowId, extractor.type_filters(), + &controller)) { return false; } - if (populate_tabs) + if (extractor.populate_tabs()) SetResult(controller->CreateWindowValueWithTabs(extension())); else SetResult(controller->CreateWindowValue()); @@ -310,22 +326,24 @@ bool WindowsGetLastFocusedFunction::RunSync() { windows::GetLastFocused::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); - bool populate_tabs = false; - if (params->get_info.get() && params->get_info->populate.get()) - populate_tabs = *params->get_info->populate; - - // Note: currently this returns the last active browser. If we decide to - // include other window types (e.g. panels), we will need to add logic to - // WindowControllerList that mirrors the active behavior of BrowserList. - Browser* browser = chrome::FindAnyBrowser( - GetProfile(), include_incognito(), chrome::GetActiveDesktop()); - if (!browser || !browser->window()) { + ApiParameterExtractor<windows::GetLastFocused::Params> extractor( + params.get()); + // The WindowControllerList should contain a list of application, + // browser and devtools windows. + WindowController* controller = nullptr; + for (auto iter : WindowControllerList::GetInstance()->windows()) { + if (windows_util::CanOperateOnWindow(this, iter, + extractor.type_filters())) { + controller = iter; + if (controller->window()->IsActive()) + break; // Use focused window. + } + } + if (!controller) { error_ = keys::kNoLastFocusedWindowError; return false; } - WindowController* controller = - browser->extension_window_controller(); - if (populate_tabs) + if (extractor.populate_tabs()) SetResult(controller->CreateWindowValueWithTabs(extension())); else SetResult(controller->CreateWindowValue()); @@ -337,19 +355,17 @@ bool WindowsGetAllFunction::RunSync() { windows::GetAll::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); - bool populate_tabs = false; - if (params->get_info.get() && params->get_info->populate.get()) - populate_tabs = *params->get_info->populate; - + ApiParameterExtractor<windows::GetAll::Params> extractor(params.get()); base::ListValue* window_list = new base::ListValue(); const WindowControllerList::ControllerList& windows = WindowControllerList::GetInstance()->windows(); for (WindowControllerList::ControllerList::const_iterator iter = windows.begin(); iter != windows.end(); ++iter) { - if (!windows_util::CanOperateOnWindow(this, *iter)) + if (!windows_util::CanOperateOnWindow(this, *iter, + extractor.type_filters())) continue; - if (populate_tabs) + if (extractor.populate_tabs()) window_list->Append((*iter)->CreateWindowValueWithTabs(extension())); else window_list->Append((*iter)->CreateWindowValue()); @@ -693,9 +709,11 @@ bool WindowsUpdateFunction::RunSync() { EXTENSION_FUNCTION_VALIDATE(params); WindowController* controller; - if (!windows_util::GetWindowFromWindowID(this, params->window_id, - &controller)) + if (!windows_util::GetWindowFromWindowID( + this, params->window_id, WindowController::GetAllWindowFilter(), + &controller)) { return false; + } ui::WindowShowState show_state = ConvertToWindowShowState(params->update_info.state); @@ -795,8 +813,9 @@ bool WindowsRemoveFunction::RunSync() { EXTENSION_FUNCTION_VALIDATE(params); WindowController* controller; - if (!windows_util::GetWindowFromWindowID(this, params->window_id, - &controller)) + if (!windows_util::GetWindowFromWindowID( + this, params->window_id, WindowController::GetDefaultWindowFilter(), + &controller)) return false; WindowController::Reason reason; diff --git a/chrome/browser/extensions/api/tabs/tabs_constants.cc b/chrome/browser/extensions/api/tabs/tabs_constants.cc index 68f5b04..c1994b0 100644 --- a/chrome/browser/extensions/api/tabs/tabs_constants.cc +++ b/chrome/browser/extensions/api/tabs/tabs_constants.cc @@ -9,6 +9,7 @@ namespace tabs_constants { const char kActiveKey[] = "active"; const char kAllFramesKey[] = "allFrames"; +const char kAllWindowTypesKey[] = "allWindowTypes"; const char kAlwaysOnTopKey[] = "alwaysOnTop"; const char kBypassCache[] = "bypassCache"; const char kCodeKey[] = "code"; diff --git a/chrome/browser/extensions/api/tabs/tabs_constants.h b/chrome/browser/extensions/api/tabs/tabs_constants.h index 02d6c36..44e53da 100644 --- a/chrome/browser/extensions/api/tabs/tabs_constants.h +++ b/chrome/browser/extensions/api/tabs/tabs_constants.h @@ -13,6 +13,7 @@ namespace tabs_constants { // Keys used in serializing tab data & events. extern const char kActiveKey[]; extern const char kAllFramesKey[]; +extern const char kAllWindowTypesKey[]; extern const char kAlwaysOnTopKey[]; extern const char kBypassCache[]; extern const char kCodeKey[]; diff --git a/chrome/browser/extensions/api/tabs/tabs_test.cc b/chrome/browser/extensions/api/tabs/tabs_test.cc index 6bb2709..3d86ad1 100644 --- a/chrome/browser/extensions/api/tabs/tabs_test.cc +++ b/chrome/browser/extensions/api/tabs/tabs_test.cc @@ -13,6 +13,7 @@ #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/values.h" +#include "chrome/browser/apps/app_browsertest_util.h" #include "chrome/browser/devtools/devtools_window_testing.h" #include "chrome/browser/extensions/api/tabs/tabs_api.h" #include "chrome/browser/extensions/api/tabs/tabs_constants.h" @@ -26,6 +27,7 @@ #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/browser/ui/zoom/chrome_zoom_level_prefs.h" #include "chrome/test/base/ui_test_utils.h" #include "content/public/browser/browser_context.h" @@ -33,12 +35,17 @@ #include "content/public/common/page_zoom.h" #include "content/public/common/url_constants.h" #include "extensions/browser/api_test_utils.h" +#include "extensions/browser/app_window/app_window.h" +#include "extensions/browser/app_window/app_window_registry.h" +#include "extensions/browser/app_window/native_app_window.h" #include "extensions/common/manifest_constants.h" #include "extensions/common/test_util.h" #include "extensions/test/extension_test_message_listener.h" #include "extensions/test/result_catcher.h" #include "net/test/spawned_test_server/spawned_test_server.h" #include "ui/gfx/geometry/rect.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_observer.h" namespace extensions { @@ -46,7 +53,7 @@ namespace keys = tabs_constants; namespace utils = extension_function_test_utils; namespace { -using ExtensionTabsTest = ExtensionApiTest; +using ExtensionTabsTest = PlatformAppBrowserTest; class ExtensionWindowCreateTest : public InProcessBrowserTest { public: @@ -84,26 +91,6 @@ int GetWindowId(base::DictionaryValue* window) { } // namespace -IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, WindowTypes) { - Browser* normal_browser = new Browser(Browser::CreateParams( - browser()->profile(), browser()->host_desktop_type())); - EXPECT_EQ(keys::kWindowTypeValueNormal, - normal_browser->extension_window_controller()->GetWindowTypeText()); - - Browser* popup_browser = new Browser( - Browser::CreateParams(Browser::TYPE_POPUP, browser()->profile(), - browser()->host_desktop_type())); - EXPECT_EQ(keys::kWindowTypeValuePopup, - popup_browser->extension_window_controller()->GetWindowTypeText()); - - DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( - browser()->tab_strip_model()->GetWebContentsAt(0), false /* is_docked */); - EXPECT_EQ(keys::kWindowTypeValueDevTools, DevToolsWindowTesting::Get(devtools) - ->browser() - ->extension_window_controller() - ->GetWindowTypeText()); -} - IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetWindow) { int window_id = ExtensionTabUtil::GetWindowId(browser()); @@ -203,6 +190,22 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetWindow) { browser(), utils::INCLUDE_INCOGNITO))); EXPECT_TRUE(api_test_utils::GetBoolean(result.get(), "incognito")); + + // DevTools window. + DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( + browser()->tab_strip_model()->GetWebContentsAt(0), false /* is_docked */); + + function = new WindowsGetFunction(); + function->set_extension(extension.get()); + result.reset(utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( + function.get(), + base::StringPrintf("[%u, {\"windowTypes\": [\"devtools\"]}]", + ExtensionTabUtil::GetWindowId( + DevToolsWindowTesting::Get(devtools)->browser())), + browser(), utils::INCLUDE_INCOGNITO))); + EXPECT_EQ("devtools", api_test_utils::GetString(result.get(), "type")); + + DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); } IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetCurrentWindow) { @@ -257,7 +260,12 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetAllWindows) { window_ids.insert(ExtensionTabUtil::GetWindowId(new_browser)); } - // Undocked DevTools window should not be accessible. + // Application windows should not be accessible, unless allWindowTypes is set + // to true. + AppWindow* app_window = CreateTestAppWindow("{}"); + + // Undocked DevTools window should not be accessible, unless allWindowTypes is + // set to true. DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( browser()->tab_strip_model()->GetWebContentsAt(0), false /* is_docked */); @@ -270,8 +278,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetAllWindows) { browser()))); base::ListValue* windows = result.get(); - EXPECT_EQ(NUM_WINDOWS, windows->GetSize()); - for (size_t i = 0; i < NUM_WINDOWS; ++i) { + EXPECT_EQ(window_ids.size(), windows->GetSize()); + for (size_t i = 0; i < windows->GetSize(); ++i) { base::DictionaryValue* result_window = nullptr; EXPECT_TRUE(windows->GetDictionary(i, &result_window)); result_ids.insert(GetWindowId(result_window)); @@ -292,7 +300,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetAllWindows) { browser()))); windows = result.get(); - EXPECT_EQ(NUM_WINDOWS, windows->GetSize()); + EXPECT_EQ(window_ids.size(), windows->GetSize()); for (size_t i = 0; i < windows->GetSize(); ++i) { base::DictionaryValue* result_window = nullptr; EXPECT_TRUE(windows->GetDictionary(i, &result_window)); @@ -302,10 +310,88 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetAllWindows) { base::ListValue* tabs = nullptr; EXPECT_TRUE(result_window->GetList(keys::kTabsKey, &tabs)); } - // The returned ids should contain all the current browser instance ids. + // The returned ids should contain all the current app, browser and + // devtools instance ids. EXPECT_EQ(window_ids, result_ids); DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); + + CloseAppWindow(app_window); +} + +IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetAllWindowsAllTypes) { + const size_t NUM_WINDOWS = 5; + std::set<int> window_ids; + std::set<int> result_ids; + window_ids.insert(ExtensionTabUtil::GetWindowId(browser())); + + for (size_t i = 0; i < NUM_WINDOWS - 1; ++i) { + Browser* new_browser = CreateBrowser(browser()->profile()); + window_ids.insert(ExtensionTabUtil::GetWindowId(new_browser)); + } + + // Application windows should be accessible. + AppWindow* app_window = CreateTestAppWindow("{}"); + window_ids.insert(app_window->session_id().id()); + + // Undocked DevTools window should be accessible too. + DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( + browser()->tab_strip_model()->GetWebContentsAt(0), false /* is_docked */); + window_ids.insert(ExtensionTabUtil::GetWindowId( + DevToolsWindowTesting::Get(devtools)->browser())); + + scoped_refptr<WindowsGetAllFunction> function = new WindowsGetAllFunction(); + scoped_refptr<Extension> extension(test_util::CreateEmptyExtension()); + function->set_extension(extension.get()); + scoped_ptr<base::ListValue> result( + utils::ToList(utils::RunFunctionAndReturnSingleResult( + function.get(), + "[{\"windowTypes\": [\"app\", \"devtools\", \"normal\", \"panel\", " + "\"popup\"]}]", + browser()))); + + base::ListValue* windows = result.get(); + EXPECT_EQ(window_ids.size(), windows->GetSize()); + for (size_t i = 0; i < windows->GetSize(); ++i) { + base::DictionaryValue* result_window = nullptr; + EXPECT_TRUE(windows->GetDictionary(i, &result_window)); + result_ids.insert(GetWindowId(result_window)); + + // "populate" was not passed in so tabs are not populated. + base::ListValue* tabs = nullptr; + EXPECT_FALSE(result_window->GetList(keys::kTabsKey, &tabs)); + } + // The returned ids should contain all the current app, browser and + // devtools instance ids. + EXPECT_EQ(window_ids, result_ids); + + result_ids.clear(); + function = new WindowsGetAllFunction(); + function->set_extension(extension.get()); + result.reset(utils::ToList(utils::RunFunctionAndReturnSingleResult( + function.get(), + "[{\"populate\": true, \"windowTypes\": [\"app\", \"devtools\", " + "\"normal\", \"panel\", \"popup\"]}]", + browser()))); + + windows = result.get(); + EXPECT_EQ(window_ids.size(), windows->GetSize()); + for (size_t i = 0; i < windows->GetSize(); ++i) { + base::DictionaryValue* result_window = nullptr; + EXPECT_TRUE(windows->GetDictionary(i, &result_window)); + result_ids.insert(GetWindowId(result_window)); + + // "populate" was enabled so tabs should be populated. + base::ListValue* tabs = nullptr; + EXPECT_TRUE(result_window->GetList(keys::kTabsKey, &tabs)); + } + // The returned ids should contain all the current app, browser and + // devtools instance ids. + EXPECT_EQ(window_ids, result_ids); + + DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); + + CloseAppWindow(app_window); } IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, UpdateNoPermissions) { @@ -614,6 +700,354 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, InvalidUpdateWindowState) { keys::kInvalidWindowStateError)); } +IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, UpdateAppWindowSizeConstraint) { + AppWindow* app_window = CreateTestAppWindow( + "{\"outerBounds\": " + "{\"width\": 300, \"height\": 300," + " \"minWidth\": 200, \"minHeight\": 200," + " \"maxWidth\": 400, \"maxHeight\": 400}}"); + + scoped_refptr<WindowsGetFunction> get_function = new WindowsGetFunction(); + scoped_refptr<Extension> extension(test_util::CreateEmptyExtension().get()); + get_function->set_extension(extension.get()); + scoped_ptr<base::DictionaryValue> result( + utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( + get_function.get(), + base::StringPrintf("[%u, {\"windowTypes\": [\"app\"]}]", + app_window->session_id().id()), + browser()))); + + EXPECT_EQ(300, api_test_utils::GetInteger(result.get(), "width")); + EXPECT_EQ(300, api_test_utils::GetInteger(result.get(), "height")); + + // Verify the min width/height of the application window are + // respected. + scoped_refptr<WindowsUpdateFunction> update_min_function = + new WindowsUpdateFunction(); + result.reset(utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( + update_min_function.get(), + base::StringPrintf("[%u, {\"width\": 100, \"height\": 100}]", + app_window->session_id().id()), + browser()))); + + EXPECT_EQ(200, api_test_utils::GetInteger(result.get(), "width")); + EXPECT_EQ(200, api_test_utils::GetInteger(result.get(), "height")); + + // Verify the max width/height of the application window are + // respected. + scoped_refptr<WindowsUpdateFunction> update_max_function = + new WindowsUpdateFunction(); + result.reset(utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( + update_max_function.get(), + base::StringPrintf("[%u, {\"width\": 500, \"height\": 500}]", + app_window->session_id().id()), + browser()))); + + EXPECT_EQ(400, api_test_utils::GetInteger(result.get(), "width")); + EXPECT_EQ(400, api_test_utils::GetInteger(result.get(), "height")); + + CloseAppWindow(app_window); +} + +IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, UpdateDevToolsWindow) { + DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( + browser()->tab_strip_model()->GetWebContentsAt(0), false /* is_docked */); + + scoped_refptr<WindowsGetFunction> get_function = new WindowsGetFunction(); + scoped_refptr<Extension> extension(test_util::CreateEmptyExtension().get()); + get_function->set_extension(extension.get()); + scoped_ptr<base::DictionaryValue> result( + utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( + get_function.get(), + base::StringPrintf( + "[%u, {\"windowTypes\": [\"devtools\"]}]", + ExtensionTabUtil::GetWindowId( + DevToolsWindowTesting::Get(devtools)->browser())), + browser()))); + + // Verify the updating width/height works. + int32_t new_width = api_test_utils::GetInteger(result.get(), "width") - 50; + int32_t new_height = api_test_utils::GetInteger(result.get(), "height") - 50; + + scoped_refptr<WindowsUpdateFunction> update_function = + new WindowsUpdateFunction(); + result.reset(utils::ToDictionary(utils::RunFunctionAndReturnSingleResult( + update_function.get(), + base::StringPrintf("[%u, {\"width\": %d, \"height\": %d}]", + ExtensionTabUtil::GetWindowId( + DevToolsWindowTesting::Get(devtools)->browser()), + new_width, new_height), + browser()))); + + EXPECT_EQ(new_width, api_test_utils::GetInteger(result.get(), "width")); + EXPECT_EQ(new_height, api_test_utils::GetInteger(result.get(), "height")); + + DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); +} + +// TODO(llandwerlin): Activating a browser window and waiting for the +// action to happen requires views::Widget which is not available on +// MacOSX. Deactivate for now. +#if !defined(OS_MACOSX) +class ExtensionWindowLastFocusedTest : public ExtensionTabsTest { + public: + void SetUpOnMainThread() override; + + void ActivateAppWindow(AppWindow* app_window); + + void ActivateBrowserWindow(Browser* browser); + + Browser* CreateBrowserWithEmptyTab(bool as_popup); + + int GetTabId(const base::DictionaryValue* value) const; + + base::Value* RunFunction(UIThreadExtensionFunction* function, + const std::string& params); + + private: + // A helper class to wait for an AppWindow to become activated. On + // window system like X11, for a NativeWidget to be activated, we + // need to wait for the round trip communication with the X server. + class AppWindowActivatedWaiter : public AppWindowRegistry::Observer { + public: + AppWindowActivatedWaiter(AppWindow* app_window, + content::BrowserContext* browser_context) + : app_window_(app_window), + browser_context_(browser_context), + waiting_(false) { + AppWindowRegistry::Get(browser_context_)->AddObserver(this); + } + ~AppWindowActivatedWaiter() override { + AppWindowRegistry::Get(browser_context_)->RemoveObserver(this); + } + + void ActivateAndWait() { + app_window_->GetBaseWindow()->Activate(); + if (!app_window_->GetBaseWindow()->IsActive()) { + waiting_ = true; + content::RunMessageLoop(); + } + } + + // AppWindowRegistry::Observer: + void OnAppWindowActivated(AppWindow* app_window) override { + if (app_window_ == app_window && waiting_) { + base::MessageLoopForUI::current()->Quit(); + waiting_ = false; + } + } + + private: + AppWindow* app_window_; + content::BrowserContext* browser_context_; + bool waiting_; + }; + + // A helper class to wait for an views::Widget to become activated. + class WidgetActivatedWaiter : public views::WidgetObserver { + public: + explicit WidgetActivatedWaiter(views::Widget* widget) + : widget_(widget), waiting_(false) { + widget_->AddObserver(this); + } + ~WidgetActivatedWaiter() override { widget_->RemoveObserver(this); } + + void ActivateAndWait() { + widget_->Activate(); + if (!widget_->IsActive()) { + waiting_ = true; + content::RunMessageLoop(); + } + } + + // views::WidgetObserver: + void OnWidgetActivationChanged(views::Widget* widget, + bool active) override { + if (widget_ == widget && waiting_) { + base::MessageLoopForUI::current()->Quit(); + waiting_ = false; + } + } + + private: + views::Widget* widget_; + bool waiting_; + }; + + scoped_refptr<Extension> extension_; +}; + +void ExtensionWindowLastFocusedTest::SetUpOnMainThread() { + ExtensionTabsTest::SetUpOnMainThread(); + extension_ = test_util::CreateEmptyExtension(); +} + +void ExtensionWindowLastFocusedTest::ActivateAppWindow(AppWindow* app_window) { + AppWindowActivatedWaiter waiter(app_window, browser()->profile()); + waiter.ActivateAndWait(); +} + +void ExtensionWindowLastFocusedTest::ActivateBrowserWindow(Browser* browser) { + BrowserView* view = BrowserView::GetBrowserViewForBrowser(browser); + EXPECT_NE(nullptr, view); + views::Widget* widget = view->frame(); + EXPECT_NE(nullptr, widget); + WidgetActivatedWaiter waiter(widget); + waiter.ActivateAndWait(); +} + +Browser* ExtensionWindowLastFocusedTest::CreateBrowserWithEmptyTab( + bool as_popup) { + Browser* new_browser; + if (as_popup) + new_browser = new Browser( + Browser::CreateParams(Browser::TYPE_POPUP, browser()->profile(), + browser()->host_desktop_type())); + else + new_browser = new Browser(Browser::CreateParams( + browser()->profile(), browser()->host_desktop_type())); + AddBlankTabAndShow(new_browser); + return new_browser; +} + +int ExtensionWindowLastFocusedTest::GetTabId( + const base::DictionaryValue* value) const { + const base::ListValue* tabs = NULL; + if (!value->GetList(keys::kTabsKey, &tabs)) + return -2; + const base::Value* tab = NULL; + if (!tabs->Get(0, &tab)) + return -2; + const base::DictionaryValue* tab_dict = NULL; + if (!tab->GetAsDictionary(&tab_dict)) + return -2; + int tab_id = 0; + if (!tab_dict->GetInteger(keys::kIdKey, &tab_id)) + return -2; + return tab_id; +} + +base::Value* ExtensionWindowLastFocusedTest::RunFunction( + UIThreadExtensionFunction* function, + const std::string& params) { + function->set_extension(extension_.get()); + return utils::RunFunctionAndReturnSingleResult(function, params, browser()); +} + +IN_PROC_BROWSER_TEST_F(ExtensionWindowLastFocusedTest, + NoDevtoolsAndAppWindows) { + DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( + browser()->tab_strip_model()->GetWebContentsAt(0), false /* is_docked */); + { + int devtools_window_id = ExtensionTabUtil::GetWindowId( + DevToolsWindowTesting::Get(devtools)->browser()); + ActivateBrowserWindow(DevToolsWindowTesting::Get(devtools)->browser()); + + scoped_refptr<WindowsGetLastFocusedFunction> function = + new WindowsGetLastFocusedFunction(); + scoped_ptr<base::DictionaryValue> result(utils::ToDictionary( + RunFunction(function.get(), "[{\"populate\": true}]"))); + EXPECT_NE(devtools_window_id, + api_test_utils::GetInteger(result.get(), "id")); + } + + AppWindow* app_window = CreateTestAppWindow( + "{\"outerBounds\": " + "{\"width\": 300, \"height\": 300," + " \"minWidth\": 200, \"minHeight\": 200," + " \"maxWidth\": 400, \"maxHeight\": 400}}"); + { + ActivateAppWindow(app_window); + + scoped_refptr<WindowsGetLastFocusedFunction> get_current_app_function = + new WindowsGetLastFocusedFunction(); + scoped_ptr<base::DictionaryValue> result(utils::ToDictionary( + RunFunction(get_current_app_function.get(), "[{\"populate\": true}]"))); + int app_window_id = app_window->session_id().id(); + EXPECT_NE(app_window_id, api_test_utils::GetInteger(result.get(), "id")); + } + + DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); + CloseAppWindow(app_window); +} + +IN_PROC_BROWSER_TEST_F(ExtensionWindowLastFocusedTest, + NoTabIdForDevToolsAndAppWindows) { + Browser* normal_browser = CreateBrowserWithEmptyTab(false); + { + ActivateBrowserWindow(normal_browser); + + scoped_refptr<WindowsGetLastFocusedFunction> function = + new WindowsGetLastFocusedFunction(); + scoped_ptr<base::DictionaryValue> result(utils::ToDictionary( + RunFunction(function.get(), "[{\"populate\": true}]"))); + int normal_browser_window_id = + ExtensionTabUtil::GetWindowId(normal_browser); + EXPECT_EQ(normal_browser_window_id, + api_test_utils::GetInteger(result.get(), "id")); + EXPECT_NE(-1, GetTabId(result.get())); + EXPECT_EQ("normal", api_test_utils::GetString(result.get(), "type")); + } + + Browser* popup_browser = CreateBrowserWithEmptyTab(true); + { + ActivateBrowserWindow(popup_browser); + + scoped_refptr<WindowsGetLastFocusedFunction> function = + new WindowsGetLastFocusedFunction(); + scoped_ptr<base::DictionaryValue> result(utils::ToDictionary( + RunFunction(function.get(), "[{\"populate\": true}]"))); + int popup_browser_window_id = ExtensionTabUtil::GetWindowId(popup_browser); + EXPECT_EQ(popup_browser_window_id, + api_test_utils::GetInteger(result.get(), "id")); + EXPECT_NE(-1, GetTabId(result.get())); + EXPECT_EQ("popup", api_test_utils::GetString(result.get(), "type")); + } + + DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( + browser()->tab_strip_model()->GetWebContentsAt(0), false /* is_docked */); + { + ActivateBrowserWindow(DevToolsWindowTesting::Get(devtools)->browser()); + + scoped_refptr<WindowsGetLastFocusedFunction> function = + new WindowsGetLastFocusedFunction(); + scoped_ptr<base::DictionaryValue> result(utils::ToDictionary(RunFunction( + function.get(), + "[{\"populate\": true, \"windowTypes\": [ \"devtools\" ]}]"))); + int devtools_window_id = ExtensionTabUtil::GetWindowId( + DevToolsWindowTesting::Get(devtools)->browser()); + EXPECT_EQ(devtools_window_id, + api_test_utils::GetInteger(result.get(), "id")); + EXPECT_EQ(-1, GetTabId(result.get())); + EXPECT_EQ("devtools", api_test_utils::GetString(result.get(), "type")); + } + + AppWindow* app_window = CreateTestAppWindow( + "{\"outerBounds\": " + "{\"width\": 300, \"height\": 300," + " \"minWidth\": 200, \"minHeight\": 200," + " \"maxWidth\": 400, \"maxHeight\": 400}}"); + { + ActivateAppWindow(app_window); + + scoped_refptr<WindowsGetLastFocusedFunction> get_current_app_function = + new WindowsGetLastFocusedFunction(); + scoped_ptr<base::DictionaryValue> result(utils::ToDictionary( + RunFunction(get_current_app_function.get(), + "[{\"populate\": true, \"windowTypes\": [ \"app\" ]}]"))); + int app_window_id = app_window->session_id().id(); + EXPECT_EQ(app_window_id, api_test_utils::GetInteger(result.get(), "id")); + EXPECT_EQ(-1, GetTabId(result.get())); + EXPECT_EQ("app", api_test_utils::GetString(result.get(), "type")); + } + + chrome::CloseWindow(normal_browser); + chrome::CloseWindow(popup_browser); + DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); + CloseAppWindow(app_window); +} +#endif // !defined(OS_MACOSX) + IN_PROC_BROWSER_TEST_F(ExtensionWindowCreateTest, AcceptState) { scoped_refptr<WindowsCreateFunction> function(new WindowsCreateFunction()); scoped_refptr<Extension> extension(test_util::CreateEmptyExtension()); @@ -773,7 +1207,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DuplicateTabNoPermission) { IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, NoTabsEventOnDevTools) { extensions::ResultCatcher catcher; ExtensionTestMessageListener listener("ready", true); - ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("tabs/no_events"))); + ASSERT_TRUE( + LoadExtension(test_data_dir_.AppendASCII("api_test/tabs/no_events"))); ASSERT_TRUE(listener.WaitUntilSatisfied()); DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( @@ -786,6 +1221,64 @@ IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, NoTabsEventOnDevTools) { DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); } +IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, NoTabsAppWindow) { + extensions::ResultCatcher catcher; + ExtensionTestMessageListener listener("ready", true); + ASSERT_TRUE( + LoadExtension(test_data_dir_.AppendASCII("api_test/tabs/no_events"))); + ASSERT_TRUE(listener.WaitUntilSatisfied()); + + AppWindow* app_window = CreateTestAppWindow( + "{\"outerBounds\": " + "{\"width\": 300, \"height\": 300," + " \"minWidth\": 200, \"minHeight\": 200," + " \"maxWidth\": 400, \"maxHeight\": 400}}"); + + listener.Reply("stop"); + + ASSERT_TRUE(catcher.GetNextResult()); + + CloseAppWindow(app_window); +} + +IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, FilteredEvents) { + extensions::ResultCatcher catcher; + ExtensionTestMessageListener listener("ready", true); + ASSERT_TRUE( + LoadExtension(test_data_dir_.AppendASCII("api_test/windows/events"))); + ASSERT_TRUE(listener.WaitUntilSatisfied()); + + AppWindow* app_window = CreateTestAppWindow( + "{\"outerBounds\": " + "{\"width\": 300, \"height\": 300," + " \"minWidth\": 200, \"minHeight\": 200," + " \"maxWidth\": 400, \"maxHeight\": 400}}"); + + Browser* browser_window = new Browser(Browser::CreateParams( + browser()->profile(), browser()->host_desktop_type())); + AddBlankTabAndShow(browser_window); + + DevToolsWindow* devtools_window = + DevToolsWindowTesting::OpenDevToolsWindowSync( + browser()->tab_strip_model()->GetWebContentsAt(0), + false /* is_docked */); + + chrome::CloseWindow(browser_window); + DevToolsWindowTesting::CloseDevToolsWindowSync(devtools_window); + CloseAppWindow(app_window); + + // TODO(llandwerlin): It seems creating an app window on MacOSX + // won't create an activation event whereas it does on all other + // platform. Disable focus event tests for now. +#if defined(OS_MACOSX) + listener.Reply(""); +#else + listener.Reply("focus"); +#endif + + ASSERT_TRUE(catcher.GetNextResult()); +} + IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, ExecuteScriptOnDevTools) { scoped_ptr<base::DictionaryValue> test_extension_value( api_test_utils::ParseDictionary( diff --git a/chrome/browser/extensions/api/tabs/windows_event_router.cc b/chrome/browser/extensions/api/tabs/windows_event_router.cc index 6b2eb10..76eeda8 100644 --- a/chrome/browser/extensions/api/tabs/windows_event_router.cc +++ b/chrome/browser/extensions/api/tabs/windows_event_router.cc @@ -138,12 +138,16 @@ void WindowsEventRouter::Observe( } static bool WillDispatchWindowFocusedEvent( - BrowserContext* new_active_context, - int window_id, + WindowController* window_controller, BrowserContext* context, const Extension* extension, base::ListValue* event_args, const base::DictionaryValue* listener_filter) { + int window_id = window_controller ? window_controller->GetWindowId() + : extension_misc::kUnknownWindowId; + Profile* new_active_context = + window_controller ? window_controller->profile() : nullptr; + // When switching between windows in the default and incognito profiles, // dispatch WINDOW_ID_NONE to extensions whose profile lost focus that // can't see the new focused window across the incognito boundary. @@ -185,9 +189,12 @@ void WindowsEventRouter::OnActiveWindowChanged( windows::OnFocusChanged::kEventName, make_scoped_ptr(new base::ListValue()))); event->will_dispatch_callback = - base::Bind(&WillDispatchWindowFocusedEvent, - static_cast<BrowserContext*>(window_profile), - window_id); + base::Bind(&WillDispatchWindowFocusedEvent, window_controller); + // Set the window type to 'normal' if we don't have a window + // controller, so the event is not filtered. + event->filter_info.SetWindowType(window_controller + ? window_controller->GetWindowTypeText() + : keys::kWindowTypeValueNormal); EventRouter::Get(profile_)->BroadcastEvent(event.Pass()); } @@ -197,6 +204,7 @@ void WindowsEventRouter::DispatchEvent(events::HistogramValue histogram_value, scoped_ptr<base::ListValue> args) { scoped_ptr<Event> event(new Event(histogram_value, event_name, args.Pass())); event->restrict_to_browser_context = window_controller->profile(); + event->filter_info.SetWindowType(window_controller->GetWindowTypeText()); EventRouter::Get(profile_)->BroadcastEvent(event.Pass()); } diff --git a/chrome/browser/extensions/api/tabs/windows_util.cc b/chrome/browser/extensions/api/tabs/windows_util.cc index 0e2ead5..56830c9 100644 --- a/chrome/browser/extensions/api/tabs/windows_util.cc +++ b/chrome/browser/extensions/api/tabs/windows_util.cc @@ -2,12 +2,16 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <string> +#include <vector> + #include "chrome/browser/extensions/api/tabs/windows_util.h" #include "base/strings/string_number_conversions.h" #include "chrome/browser/extensions/api/tabs/tabs_constants.h" #include "chrome/browser/extensions/chrome_extension_function.h" #include "chrome/browser/extensions/chrome_extension_function_details.h" +#include "chrome/browser/extensions/extension_util.h" #include "chrome/browser/extensions/window_controller.h" #include "chrome/browser/extensions/window_controller_list.h" #include "chrome/browser/profiles/profile.h" @@ -15,11 +19,13 @@ #include "extensions/browser/extension_function_dispatcher.h" #include "extensions/common/constants.h" #include "extensions/common/error_utils.h" +#include "extensions/common/extension.h" namespace windows_util { bool GetWindowFromWindowID(UIThreadExtensionFunction* function, int window_id, + extensions::WindowController::TypeFilter filter, extensions::WindowController** controller) { if (window_id == extension_misc::kCurrentWindowId) { extensions::WindowController* extension_window_controller = @@ -37,8 +43,9 @@ bool GetWindowFromWindowID(UIThreadExtensionFunction* function, return false; } } else { - *controller = extensions::WindowControllerList::GetInstance() - ->FindWindowForFunctionById(function, window_id); + *controller = + extensions::WindowControllerList::GetInstance() + ->FindWindowForFunctionByIdWithFilter(function, window_id, filter); if (!(*controller)) { function->SetError(extensions::ErrorUtils::FormatErrorMessage( extensions::tabs_constants::kWindowNotFoundError, @@ -50,11 +57,10 @@ bool GetWindowFromWindowID(UIThreadExtensionFunction* function, } bool CanOperateOnWindow(const UIThreadExtensionFunction* function, - const extensions::WindowController* controller) { - if (function->extension() != NULL && - !controller->IsVisibleToExtension(function->extension())) { + const extensions::WindowController* controller, + extensions::WindowController::TypeFilter filter) { + if (!controller->MatchesFilter(filter)) return false; - } if (function->browser_context() == controller->profile()) return true; diff --git a/chrome/browser/extensions/api/tabs/windows_util.h b/chrome/browser/extensions/api/tabs/windows_util.h index 0e0a8d2..940d380 100644 --- a/chrome/browser/extensions/api/tabs/windows_util.h +++ b/chrome/browser/extensions/api/tabs/windows_util.h @@ -5,9 +5,16 @@ #ifndef CHROME_BROWSER_EXTENSIONS_API_TABS_WINDOWS_UTIL_H__ #define CHROME_BROWSER_EXTENSIONS_API_TABS_WINDOWS_UTIL_H__ +#include "chrome/browser/extensions/window_controller_list.h" + class UIThreadExtensionFunction; +namespace content { +class BrowserContext; +} + namespace extensions { +class Extension; class WindowController; } @@ -17,12 +24,18 @@ namespace windows_util { // returns false and sets UIThreadExtensionFunction error_. bool GetWindowFromWindowID(UIThreadExtensionFunction* function, int window_id, + extensions::WindowController::TypeFilter filter, extensions::WindowController** controller); // Returns true if |function| (and the profile and extension that it was // invoked from) can operate on the window wrapped by |window_controller|. +// If |all_window_types| is set this function will return true for any +// kind of window (including app and devtools), otherwise it will +// return true only for normal browser windows as well as windows +// created by the extension. bool CanOperateOnWindow(const UIThreadExtensionFunction* function, - const extensions::WindowController* controller); + const extensions::WindowController* controller, + extensions::WindowController::TypeFilter filter); } // namespace windows_util diff --git a/chrome/browser/extensions/extension_tab_util.cc b/chrome/browser/extensions/extension_tab_util.cc index 6ac0c78..384c94d 100644 --- a/chrome/browser/extensions/extension_tab_util.cc +++ b/chrome/browser/extensions/extension_tab_util.cc @@ -432,9 +432,10 @@ void ExtensionTabUtil::ScrubTabValueForExtension( WebContents* contents, const Extension* extension, base::DictionaryValue* tab_info) { - bool has_permission = extension && + int tab_id = GetTabId(contents); + bool has_permission = tab_id >= 0 && extension && extension->permissions_data()->HasAPIPermissionForTab( - GetTabId(contents), APIPermission::kTab); + tab_id, APIPermission::kTab); if (!has_permission) { tab_info->Remove(keys::kUrlKey, NULL); diff --git a/chrome/browser/extensions/window_controller.cc b/chrome/browser/extensions/window_controller.cc index 96f9eac..7c9a961 100644 --- a/chrome/browser/extensions/window_controller.cc +++ b/chrome/browser/extensions/window_controller.cc @@ -8,6 +8,7 @@ #include "chrome/browser/extensions/api/tabs/tabs_constants.h" #include "chrome/browser/extensions/window_controller_list.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/common/extensions/api/windows.h" #include "ui/base/base_window.h" #include "ui/gfx/geometry/rect.h" @@ -16,6 +17,34 @@ namespace extensions { /////////////////////////////////////////////////////////////////////////////// // WindowController +// static +WindowController::TypeFilter WindowController::GetAllWindowFilter() { + // This needs to be updated if there is a change to + // extensions::api::windows:WindowType. + COMPILE_ASSERT(api::windows::WINDOW_TYPE_LAST == 5, + Update_extensions_WindowController_to_match_WindowType); + return ((1 << api::windows::WINDOW_TYPE_NORMAL) | + (1 << api::windows::WINDOW_TYPE_PANEL) | + (1 << api::windows::WINDOW_TYPE_POPUP) | + (1 << api::windows::WINDOW_TYPE_APP) | + (1 << api::windows::WINDOW_TYPE_DEVTOOLS)); +} + +// static +WindowController::TypeFilter WindowController::GetDefaultWindowFilter() { + return ((1 << api::windows::WINDOW_TYPE_NORMAL) | + (1 << api::windows::WINDOW_TYPE_PANEL) | + (1 << api::windows::WINDOW_TYPE_POPUP)); +} + +WindowController::TypeFilter WindowController::GetFilterFromWindowTypes( + const std::vector<api::windows::WindowType>& types) { + WindowController::TypeFilter filter = 0; + for (auto& window_type : types) + filter |= 1 << window_type; + return filter; +} + WindowController::WindowController(ui::BaseWindow* window, Profile* profile) : window_(window), profile_(profile) { } @@ -63,4 +92,9 @@ base::DictionaryValue* WindowController::CreateWindowValue() const { return result; } +bool WindowController::MatchesFilter(TypeFilter filter) const { + TypeFilter type = 1 << api::windows::ParseWindowType(GetWindowTypeText()); + return (type & filter) != 0; +} + } // namespace extensions diff --git a/chrome/browser/extensions/window_controller.h b/chrome/browser/extensions/window_controller.h index f21a0fa..73d3907 100644 --- a/chrome/browser/extensions/window_controller.h +++ b/chrome/browser/extensions/window_controller.h @@ -9,6 +9,7 @@ #include "base/basictypes.h" #include "base/compiler_specific.h" +#include "chrome/common/extensions/api/windows.h" class Browser; // TODO(stevenjb) eliminate this dependency. class GURL; @@ -41,6 +42,21 @@ class WindowController { REASON_NOT_EDITABLE, }; + // A bitmaks used as filter on window types. + using TypeFilter = uint32_t; + + // Returns a filter allowing all window types to be manipulated + // through the chrome.windows APIs. + static TypeFilter GetAllWindowFilter(); + + // Returns the default filter to be used when operating on the windows + // from WindowControllerList when using the chrome.windows APIs. + static TypeFilter GetDefaultWindowFilter(); + + // Builds a filter out of a vector of window types. + static TypeFilter GetFilterFromWindowTypes( + const std::vector<api::windows::WindowType>& types); + WindowController(ui::BaseWindow* window, Profile* profile); virtual ~WindowController(); @@ -83,6 +99,9 @@ class WindowController { // need to define this behavior. virtual bool IsVisibleToExtension(const Extension* extension) const = 0; + // Returns true if the window type of the controller matches the |filter|. + bool MatchesFilter(TypeFilter filter) const; + private: ui::BaseWindow* window_; Profile* profile_; diff --git a/chrome/browser/extensions/window_controller_list.cc b/chrome/browser/extensions/window_controller_list.cc index cea69d5..c12646b 100644 --- a/chrome/browser/extensions/window_controller_list.cc +++ b/chrome/browser/extensions/window_controller_list.cc @@ -9,6 +9,7 @@ #include "chrome/browser/extensions/api/tabs/windows_util.h" #include "chrome/browser/extensions/chrome_extension_function_details.h" #include "chrome/browser/extensions/window_controller_list_observer.h" +#include "chrome/common/extensions/api/windows.h" #include "components/sessions/session_id.h" #include "extensions/browser/extension_function.h" #include "ui/base/base_window.h" @@ -55,30 +56,53 @@ void WindowControllerList::RemoveObserver( } WindowController* WindowControllerList::FindWindowById(int id) const { + return FindWindowByIdWithFilter(id, + WindowController::GetDefaultWindowFilter()); +} + +WindowController* WindowControllerList::FindWindowByIdWithFilter( + int id, + WindowController::TypeFilter filter) const { for (ControllerList::const_iterator iter = windows().begin(); iter != windows().end(); ++iter) { - if ((*iter)->GetWindowId() == id) + if ((*iter)->GetWindowId() == id && (*iter)->MatchesFilter(filter)) return *iter; } - return NULL; + return nullptr; } WindowController* WindowControllerList::FindWindowForFunctionById( const UIThreadExtensionFunction* function, int id) const { - WindowController* controller = FindWindowById(id); - if (controller && windows_util::CanOperateOnWindow(function, controller)) + return FindWindowForFunctionByIdWithFilter( + function, id, WindowController::GetDefaultWindowFilter()); +} + +WindowController* WindowControllerList::FindWindowForFunctionByIdWithFilter( + const UIThreadExtensionFunction* function, + int id, + WindowController::TypeFilter filter) const { + WindowController* controller = FindWindowByIdWithFilter(id, filter); + if (controller && + windows_util::CanOperateOnWindow(function, controller, filter)) return controller; - return NULL; + return nullptr; } WindowController* WindowControllerList::CurrentWindowForFunction( const UIThreadExtensionFunction* function) const { - WindowController* result = NULL; + return CurrentWindowForFunctionWithFilter( + function, WindowController::GetDefaultWindowFilter()); +} + +WindowController* WindowControllerList::CurrentWindowForFunctionWithFilter( + const UIThreadExtensionFunction* function, + WindowController::TypeFilter filter) const { + WindowController* result = nullptr; // Returns either the focused window (if any), or the last window in the list. for (ControllerList::const_iterator iter = windows().begin(); iter != windows().end(); ++iter) { - if (windows_util::CanOperateOnWindow(function, *iter)) { + if (windows_util::CanOperateOnWindow(function, *iter, filter)) { result = *iter; if (result->window()->IsActive()) break; // use focused window diff --git a/chrome/browser/extensions/window_controller_list.h b/chrome/browser/extensions/window_controller_list.h index c280e07..e593014 100644 --- a/chrome/browser/extensions/window_controller_list.h +++ b/chrome/browser/extensions/window_controller_list.h @@ -36,16 +36,34 @@ class WindowControllerList { // Returns a window matching |id|. WindowController* FindWindowById(int id) const; + // Returns a window matching |id| using |filter|. + WindowController* FindWindowByIdWithFilter( + int id, + WindowController::TypeFilter filter) const; + // Returns a window matching the context the function was invoked in. WindowController* FindWindowForFunctionById( const UIThreadExtensionFunction* function, int id) const; + // Returns a window matching the context the function was invoked in + // using |filter|. + WindowController* FindWindowForFunctionByIdWithFilter( + const UIThreadExtensionFunction* function, + int id, + WindowController::TypeFilter filter) const; + // Returns the focused or last added window matching the context the function // was invoked in. WindowController* CurrentWindowForFunction( const UIThreadExtensionFunction* function) const; + // Returns the focused or last added window matching the context the function + // was invoked in using |filter|. + WindowController* CurrentWindowForFunctionWithFilter( + const UIThreadExtensionFunction* function, + WindowController::TypeFilter filter) const; + const ControllerList& windows() const { return windows_; } static WindowControllerList* GetInstance(); diff --git a/chrome/browser/ui/panels/panel.cc b/chrome/browser/ui/panels/panel.cc index 5f52022..6501af4 100644 --- a/chrome/browser/ui/panels/panel.cc +++ b/chrome/browser/ui/panels/panel.cc @@ -96,7 +96,6 @@ PanelExtensionWindowController::CreateWindowValueWithTabs( const extensions::Extension* extension) const { base::DictionaryValue* result = CreateWindowValue(); - DCHECK(IsVisibleToExtension(extension)); base::DictionaryValue* tab_value = CreateTabValue(extension, 0); if (tab_value) { base::ListValue* tab_list = new base::ListValue(); @@ -115,7 +114,6 @@ base::DictionaryValue* PanelExtensionWindowController::CreateTabValue( if (!web_contents) return NULL; - DCHECK(IsVisibleToExtension(extension)); base::DictionaryValue* tab_value = new base::DictionaryValue(); tab_value->SetInteger(extensions::tabs_constants::kIdKey, SessionTabHelper::IdForTab(web_contents)); diff --git a/chrome/common/extensions/api/windows.json b/chrome/common/extensions/api/windows.json index bd79fe8..d63e906 100644 --- a/chrome/common/extensions/api/windows.json +++ b/chrome/common/extensions/api/windows.json @@ -78,7 +78,8 @@ "optional": true, "description": "", "properties": { - "populate": {"type": "boolean", "optional": true, "description": "If true, the $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission." } + "populate": {"type": "boolean", "optional": true, "description": "If true, the $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission." }, + "windowTypes": {"type": "array", "items": { "$ref": "WindowType" }, "optional": true, "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['normal', 'panel', 'popup']</code>." } } }, { @@ -103,7 +104,8 @@ "optional": true, "description": "", "properties": { - "populate": {"type": "boolean", "optional": true, "description": "If true, the $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission." } + "populate": {"type": "boolean", "optional": true, "description": "If true, the $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission." }, + "windowTypes": {"type": "array", "items": { "$ref": "WindowType" }, "optional": true, "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['normal', 'panel', 'popup']</code>." } } }, { @@ -128,7 +130,8 @@ "optional": true, "description": "", "properties": { - "populate": {"type": "boolean", "optional": true, "description": "If true, the $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission." } + "populate": {"type": "boolean", "optional": true, "description": "If true, the $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission." }, + "windowTypes": {"type": "array", "items": { "$ref": "WindowType" }, "optional": true, "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['normal', 'panel', 'popup']</code>." } } }, { @@ -153,7 +156,8 @@ "optional": true, "description": "", "properties": { - "populate": {"type": "boolean", "optional": true, "description": "If true, each $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects for that window. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission." } + "populate": {"type": "boolean", "optional": true, "description": "If true, each $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects for that window. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission." }, + "windowTypes": {"type": "array", "items": { "$ref": "WindowType" }, "optional": true, "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['normal', 'panel', 'popup']</code>." } } }, { @@ -267,6 +271,14 @@ "name": "onCreated", "type": "function", "description": "Fired when a window is created.", + "filters": [ + { + "name": "windowTypes", + "type": "array", + "items": { "$ref": "WindowType" }, + "description": "Conditions that the window's type being created must satisfy. By default it will satisfy <code>['normal', 'panel', 'popup']</code>." + } + ], "parameters": [ { "$ref": "Window", @@ -279,6 +291,14 @@ "name": "onRemoved", "type": "function", "description": "Fired when a window is removed (closed).", + "filters": [ + { + "name": "windowTypes", + "type": "array", + "items": { "$ref": "WindowType" }, + "description": "Conditions that the window's type being removed must satisfy. By default it will satisfy <code>['normal', 'panel', 'popup']</code>." + } + ], "parameters": [ {"type": "integer", "name": "windowId", "minimum": 0, "description": "ID of the removed window."} ] @@ -287,6 +307,14 @@ "name": "onFocusChanged", "type": "function", "description": "Fired when the currently focused window changes. Will be chrome.windows.WINDOW_ID_NONE if all chrome windows have lost focus. Note: On some Linux window managers, WINDOW_ID_NONE will always be sent immediately preceding a switch from one chrome window to another.", + "filters": [ + { + "name": "windowTypes", + "type": "array", + "items": { "$ref": "WindowType" }, + "description": "Conditions that the window's type being removed must satisfy. By default it will satisfy <code>['normal', 'panel', 'popup']</code>." + } + ], "parameters": [ {"type": "integer", "name": "windowId", "minimum": -1, "description": "ID of the newly focused window."} ] diff --git a/chrome/test/data/extensions/api_test/windows/events/background.html b/chrome/test/data/extensions/api_test/windows/events/background.html new file mode 100644 index 0000000..6ada66a --- /dev/null +++ b/chrome/test/data/extensions/api_test/windows/events/background.html @@ -0,0 +1,6 @@ +<!-- + * Copyright 2015 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. +--> +<script src="test.js"></script> diff --git a/chrome/test/data/extensions/api_test/windows/events/manifest.json b/chrome/test/data/extensions/api_test/windows/events/manifest.json new file mode 100644 index 0000000..cac457b --- /dev/null +++ b/chrome/test/data/extensions/api_test/windows/events/manifest.json @@ -0,0 +1,9 @@ +{ + "name": "chrome.tabs test", + "version": "0.1", + "manifest_version": 2, + "description": "end-to-end browser test for chrome.windows events API", + "background": { + "page": "background.html" + } +} diff --git a/chrome/test/data/extensions/api_test/windows/events/test.js b/chrome/test/data/extensions/api_test/windows/events/test.js new file mode 100644 index 0000000..a10aaf72 --- /dev/null +++ b/chrome/test/data/extensions/api_test/windows/events/test.js @@ -0,0 +1,82 @@ +// Copyright 2015 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. + +var results = { + filtered: {}, + unfiltered: {} +}; + +function recordEvents(category, name, windowId, windowType) { + if (!results[category][windowId]) + results[category][windowId] = { id: windowId, type: windowType }; + results[category][windowId][name] = true +} + +chrome.windows.onCreated.addListener(function(win) { + recordEvents('filtered', 'create', win.id, win.type); +}); +chrome.windows.onRemoved.addListener(function(id) { + recordEvents('filtered', 'remove', id, null); +}); +chrome.windows.onFocusChanged.addListener(function(id) { + recordEvents('filtered', 'focus', id, null); +}); + +var noFilter = { windowTypes: ['app', 'devtools', 'normal', 'panel', 'popup'] }; +chrome.windows.onCreated.addListener(function(win) { + recordEvents('unfiltered', 'create', win.id, win.type); +}, noFilter); +chrome.windows.onRemoved.addListener(function(id) { + recordEvents('unfiltered', 'remove', id, null); +}, noFilter); +chrome.windows.onFocusChanged.addListener(function(id) { + recordEvents('unfiltered', 'focus', id, null); +}, noFilter); + +chrome.test.sendMessage('ready', function (message) { + chrome.windows.getCurrent(function(currentWindow) { + var filteredCount = 0; + for (var i in results.filtered) { + var win = results.filtered[i]; + if (win.id == currentWindow.id || win.id == -1) + continue; + filteredCount++; + chrome.test.assertFalse(win.type == 'app' || win.type == 'devtools', + 'Unexpected window type "' + + win.type + '" in filtered events'); + chrome.test.assertTrue(win.create == true, + 'Missing create event for ' + win.type); + chrome.test.assertTrue(win.remove == true, + 'Missing remove event for ' + win.type); + chrome.test.assertTrue(win.focus == true, + 'Missing focus event for ' + win.type); + } + chrome.test.assertEq(1, filteredCount); + + var unfilteredCount = 0; + var includes_app = false, includes_devtools = false; + for (var i in results.unfiltered) { + var win = results.unfiltered[i]; + if (win.id == currentWindow.id || win.id == -1) + continue; + unfilteredCount++; + if (win.type == 'app') + includes_app = true; + if (win.type == 'devtools') + includes_devtools = true; + chrome.test.assertTrue(win.create == true, + 'Missing create event for ' + win.type); + chrome.test.assertTrue(win.remove == true, + 'Missing remove event for ' + win.type); + if (message == 'focus') + chrome.test.assertTrue(win.focus == true, + 'Missing focus event for ' + win.type); + } + chrome.test.assertEq(3, unfilteredCount); + chrome.test.assertTrue(includes_app && includes_devtools, + 'Could not find app or devtools windows'); + + chrome.test.notifyPass(); + }); +}); diff --git a/extensions/common/event_filtering_info.cc b/extensions/common/event_filtering_info.cc index 2180984..c585de9 100644 --- a/extensions/common/event_filtering_info.cc +++ b/extensions/common/event_filtering_info.cc @@ -12,12 +12,17 @@ namespace extensions { EventFilteringInfo::EventFilteringInfo() : has_url_(false), has_instance_id_(false), - instance_id_(0) { -} + instance_id_(0), + has_window_type_(false) {} EventFilteringInfo::~EventFilteringInfo() { } +void EventFilteringInfo::SetWindowType(const std::string& window_type) { + window_type_ = window_type; + has_window_type_ = true; +} + void EventFilteringInfo::SetURL(const GURL& url) { url_ = url; has_url_ = true; @@ -42,11 +47,15 @@ scoped_ptr<base::Value> EventFilteringInfo::AsValue() const { if (!service_type_.empty()) result->SetString("serviceType", service_type_); + if (has_window_type_) + result->SetString("windowType", window_type_); + return result.Pass(); } bool EventFilteringInfo::IsEmpty() const { - return !has_url_ && service_type_.empty() && !has_instance_id_; + return !has_window_type_ && !has_url_ && service_type_.empty() && + !has_instance_id_; } } // namespace extensions diff --git a/extensions/common/event_filtering_info.h b/extensions/common/event_filtering_info.h index a358798..f51e3d2 100644 --- a/extensions/common/event_filtering_info.h +++ b/extensions/common/event_filtering_info.h @@ -26,12 +26,20 @@ class EventFilteringInfo { public: EventFilteringInfo(); ~EventFilteringInfo(); + void SetWindowType(const std::string& window_type); void SetURL(const GURL& url); void SetInstanceID(int instance_id); void SetServiceType(const std::string& service_type) { service_type_ = service_type; } + // Note: window type is a Chrome concept, so arguably doesn't belong + // in the extensions module. If the number of Chrome concept grows, + // consider a delegation model with a ChromeEventFilteringInfo + // class. + bool has_window_type() const { return has_window_type_; } + const std::string& window_type() const { return window_type_; } + bool has_url() const { return has_url_; } const GURL& url() const { return url_; } @@ -52,6 +60,9 @@ class EventFilteringInfo { bool has_instance_id_; int instance_id_; + bool has_window_type_; + std::string window_type_; + // Allow implicit copy and assignment. }; diff --git a/extensions/common/event_matcher.cc b/extensions/common/event_matcher.cc index 8ae022a..0071111 100644 --- a/extensions/common/event_matcher.cc +++ b/extensions/common/event_matcher.cc @@ -2,12 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/callback.h" + #include "extensions/common/event_matcher.h" #include "extensions/common/event_filtering_info.h" namespace { const char kUrlFiltersKey[] = "url"; +const char kWindowTypesKey[] = "windowTypes"; + +const char* const kDefaultWindowTypes[] = {"normal", "panel", "popup"}; } namespace extensions { @@ -29,20 +34,30 @@ bool EventMatcher::MatchNonURLCriteria( return event_info.instance_id() == GetInstanceID(); } + if (event_info.has_window_type()) { + for (int i = 0; i < GetWindowTypeCount(); i++) { + std::string window_type; + if (GetWindowType(i, &window_type) && + window_type == event_info.window_type()) + return true; + } + return false; + } + const std::string& service_type_filter = GetServiceTypeFilter(); return service_type_filter.empty() || service_type_filter == event_info.service_type(); } int EventMatcher::GetURLFilterCount() const { - base::ListValue* url_filters = NULL; + base::ListValue* url_filters = nullptr; if (filter_->GetList(kUrlFiltersKey, &url_filters)) return url_filters->GetSize(); return 0; } bool EventMatcher::GetURLFilter(int i, base::DictionaryValue** url_filter_out) { - base::ListValue* url_filters = NULL; + base::ListValue* url_filters = nullptr; if (filter_->GetList(kUrlFiltersKey, &url_filters)) { return url_filters->GetDictionary(i, url_filter_out); } @@ -65,6 +80,25 @@ int EventMatcher::GetInstanceID() const { return instance_id; } +int EventMatcher::GetWindowTypeCount() const { + base::ListValue* window_type_filters = nullptr; + if (filter_->GetList(kWindowTypesKey, &window_type_filters)) + return window_type_filters->GetSize(); + return arraysize(kDefaultWindowTypes); +} + +bool EventMatcher::GetWindowType(int i, std::string* window_type_out) const { + base::ListValue* window_types = nullptr; + if (filter_->GetList(kWindowTypesKey, &window_types)) { + return window_types->GetString(i, window_type_out); + } + if (i >= 0 && i < static_cast<int>(arraysize(kDefaultWindowTypes))) { + *window_type_out = kDefaultWindowTypes[i]; + return true; + } + return false; +} + int EventMatcher::GetRoutingID() const { return routing_id_; } diff --git a/extensions/common/event_matcher.h b/extensions/common/event_matcher.h index bd0ce7b..0d134e6 100644 --- a/extensions/common/event_matcher.h +++ b/extensions/common/event_matcher.h @@ -31,6 +31,9 @@ class EventMatcher { int GetURLFilterCount() const; bool GetURLFilter(int i, base::DictionaryValue** url_filter_out); + int GetWindowTypeCount() const; + bool GetWindowType(int i, std::string* window_type_out) const; + std::string GetServiceTypeFilter() const; int HasURLFilters() const; diff --git a/extensions/renderer/event_bindings.cc b/extensions/renderer/event_bindings.cc index f27d123..a468ad9 100644 --- a/extensions/renderer/event_bindings.cc +++ b/extensions/renderer/event_bindings.cc @@ -97,6 +97,12 @@ EventFilteringInfo ParseFromObject(v8::Local<v8::Object> object, v8::Local<v8::Value> service_type_value(object->Get(service_type)); info.SetServiceType(*v8::String::Utf8Value(service_type_value)); } + v8::Local<v8::String> window_types( + v8::String::NewFromUtf8(isolate, "windowType")); + if (object->Has(window_types)) { + v8::Local<v8::Value> window_types_value(object->Get(window_types)); + info.SetWindowType(*v8::String::Utf8Value(window_types_value)); + } return info; } |