diff options
59 files changed, 1480 insertions, 24 deletions
diff --git a/apps/launcher.cc b/apps/launcher.cc index 933d37c..aae298f 100644 --- a/apps/launcher.cc +++ b/apps/launcher.cc @@ -31,6 +31,7 @@ #include "content/public/browser/web_contents.h" #include "net/base/mime_util.h" #include "net/base/net_util.h" +#include "url/gurl.h" #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/drive/file_errors.h" @@ -386,4 +387,13 @@ void RestartPlatformApp(Profile* profile, const Extension* extension) { LaunchPlatformAppWithNoData(profile, extension); } +void LaunchPlatformAppWithUrl(Profile* profile, + const Extension* extension, + const std::string& handler_id, + const GURL& url, + const GURL& referrer_url) { + extensions::AppEventRouter::DispatchOnLaunchedEventWithUrl( + profile, extension, handler_id, url, referrer_url); +} + } // namespace apps diff --git a/apps/launcher.h b/apps/launcher.h index 9412c11..8da998d 100644 --- a/apps/launcher.h +++ b/apps/launcher.h @@ -8,6 +8,7 @@ #include <string> class CommandLine; +class GURL; class Profile; namespace base { @@ -29,8 +30,8 @@ void LaunchPlatformAppWithCommandLine(Profile* profile, const CommandLine* command_line, const base::FilePath& current_directory); -// Launches the platform app |extension| with the contents of |file_path| -// available through the launch data. +// Launches the platform app |extension| by issuing an onLaunched event +// with the contents of |file_path| available through the launch data. void LaunchPlatformAppWithPath(Profile* profile, const extensions::Extension* extension, const base::FilePath& file_path); @@ -39,13 +40,25 @@ void LaunchPlatformAppWithPath(Profile* profile, void LaunchPlatformApp(Profile* profile, const extensions::Extension* extension); -// Launches the platform app |extension| with the contents of |file_path| -// available through the launch data. +// Launches the platform app |extension| with |handler_id| and the contents of +// |file_path| available through the launch data. |handler_id| corresponds to +// the id of the file_handlers item in the manifest that resulted in a match +// that triggered this launch. void LaunchPlatformAppWithFileHandler(Profile* profile, const extensions::Extension* extension, const std::string& handler_id, const base::FilePath& file_path); +// Launches the platform app |extension| with |handler_id|, |url| and +// |referrer_url| available through the launch data. |handler_id| corresponds to +// the id of the file_handlers item in the manifest that resulted in a match +// that triggered this launch. +void LaunchPlatformAppWithUrl(Profile* profile, + const extensions::Extension* extension, + const std::string& handler_id, + const GURL& url, + const GURL& referrer_url); + void RestartPlatformApp(Profile* profile, const extensions::Extension* extension); diff --git a/chrome/browser/apps/app_browsertest.cc b/chrome/browser/apps/app_browsertest.cc index eef7244..70c3db9 100644 --- a/chrome/browser/apps/app_browsertest.cc +++ b/chrome/browser/apps/app_browsertest.cc @@ -13,8 +13,6 @@ #include "base/prefs/pref_service.h" #include "base/stl_util.h" #include "base/strings/utf_string_conversions.h" -#include "base/test/test_timeouts.h" -#include "base/threading/platform_thread.h" #include "chrome/app/chrome_command_ids.h" #include "chrome/browser/apps/app_browsertest_util.h" #include "chrome/browser/automation/automation_util.h" diff --git a/chrome/browser/apps/app_browsertest_util.cc b/chrome/browser/apps/app_browsertest_util.cc index 7a2f9ce..afe32b0 100644 --- a/chrome/browser/apps/app_browsertest_util.cc +++ b/chrome/browser/apps/app_browsertest_util.cc @@ -60,15 +60,22 @@ const Extension* PlatformAppBrowserTest::LoadAndLaunchPlatformApp( return extension; } +const Extension* PlatformAppBrowserTest::InstallPlatformApp( + const char* name) { + const Extension* extension = InstallExtension( + test_data_dir_.AppendASCII("platform_apps").AppendASCII(name), 1); + EXPECT_TRUE(extension); + + return extension; +} + const Extension* PlatformAppBrowserTest::InstallAndLaunchPlatformApp( const char* name) { content::WindowedNotificationObserver app_loaded_observer( content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME, content::NotificationService::AllSources()); - const Extension* extension = InstallExtension( - test_data_dir_.AppendASCII("platform_apps").AppendASCII(name), 1); - EXPECT_TRUE(extension); + const Extension* extension = InstallPlatformApp(name); chrome::OpenApplication(chrome::AppLaunchParams(browser()->profile(), extension, diff --git a/chrome/browser/apps/app_browsertest_util.h b/chrome/browser/apps/app_browsertest_util.h index aa4efcd1..2b9d958 100644 --- a/chrome/browser/apps/app_browsertest_util.h +++ b/chrome/browser/apps/app_browsertest_util.h @@ -8,6 +8,7 @@ #include "apps/shell_window.h" #include "chrome/browser/extensions/extension_apitest.h" +#include "content/public/common/page_transition_types.h" namespace content { class WebContents; @@ -29,6 +30,9 @@ class PlatformAppBrowserTest : public ExtensionApiTest { // until it is launched. const Extension* LoadAndLaunchPlatformApp(const char* name); + // Installs the app named |name| out of the platform_apps subdirectory. + const Extension* InstallPlatformApp(const char* name); + // Installs and runs the app named |name| out of the platform_apps // subdirectory. Waits until it is launched. const Extension* InstallAndLaunchPlatformApp(const char* name); diff --git a/chrome/browser/apps/app_url_redirector.cc b/chrome/browser/apps/app_url_redirector.cc new file mode 100644 index 0000000..d973fdd --- /dev/null +++ b/chrome/browser/apps/app_url_redirector.cc @@ -0,0 +1,98 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/apps/app_url_redirector.h" + +#include "apps/launcher.h" +#include "base/bind.h" +#include "base/logging.h" +#include "chrome/browser/extensions/extension_info_map.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_io_data.h" +#include "chrome/common/extensions/api/url_handlers/url_handlers_parser.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_messages.h" +#include "chrome/common/extensions/extension_set.h" +#include "components/navigation_interception/intercept_navigation_resource_throttle.h" +#include "components/navigation_interception/navigation_params.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/resource_throttle.h" +#include "content/public/browser/web_contents.h" +#include "net/url_request/url_request.h" + +using content::BrowserThread; +using content::WebContents; +using extensions::Extension; +using extensions::UrlHandlers; +using extensions::UrlHandlerInfo; + +namespace { + +bool LaunchAppWithUrl( + const scoped_refptr<const Extension> app, + const std::string& handler_id, + content::RenderViewHost* source, + const navigation_interception::NavigationParams& params) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // Redirect top-level navigations only. This excludes iframes and webviews + // in particular. + if (source->IsSubframe()) + return false; + + // These are guaranteed by CreateThrottleFor below. + DCHECK(!params.is_post()); + DCHECK(UrlHandlers::CanExtensionHandleUrl(app, params.url())); + + WebContents* web_contents = WebContents::FromRenderViewHost(source); + if (!web_contents) + return false; + + Profile* profile = + Profile::FromBrowserContext(web_contents->GetBrowserContext()); + + apps::LaunchPlatformAppWithUrl( + profile, app, handler_id, params.url(), params.referrer().url); + + return true; +} + +} // namespace + +// static +content::ResourceThrottle* +AppUrlRedirector::MaybeCreateThrottleFor(net::URLRequest* request, + ProfileIOData* profile_io_data) { + // Support only GET for now. + if (request->method() != "GET") + return NULL; + + if (!request->url().SchemeIsHTTPOrHTTPS()) + return NULL; + + // Never redirect URLs to apps in incognito. Technically, apps are not + // supported in incognito, but that may change in future. + // See crbug.com/240879, which tracks incognito support for v2 apps. + if (profile_io_data->is_incognito()) + return NULL; + + const ExtensionSet& extensions = + profile_io_data->GetExtensionInfoMap()->extensions(); + for (ExtensionSet::const_iterator iter = extensions.begin(); + iter != extensions.end(); + ++iter) { + const UrlHandlerInfo* handler = + UrlHandlers::FindMatchingUrlHandler(*iter, request->url()); + if (handler) { + return new navigation_interception::InterceptNavigationResourceThrottle( + request, + base::Bind(&LaunchAppWithUrl, + scoped_refptr<const Extension>(*iter), + handler->id)); + } + } + + return NULL; +} diff --git a/chrome/browser/apps/app_url_redirector.h b/chrome/browser/apps/app_url_redirector.h new file mode 100644 index 0000000..331d7b4 --- /dev/null +++ b/chrome/browser/apps/app_url_redirector.h @@ -0,0 +1,32 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_APPS_APP_URL_REDIRECTOR_H_ +#define CHROME_BROWSER_APPS_APP_URL_REDIRECTOR_H_ + +#include "base/basictypes.h" + +namespace content { +class ResourceThrottle; +} + +namespace net { +class URLRequest; +} + +class ProfileIOData; + +// This class creates resource throttles that redirect URLs to apps that +// have a matching URL handler in the 'url_handlers' manifest key. +class AppUrlRedirector { + public: + static content::ResourceThrottle* MaybeCreateThrottleFor( + net::URLRequest* request, + ProfileIOData* profile_io_data); + + private: + DISALLOW_COPY_AND_ASSIGN(AppUrlRedirector); +}; + +#endif // CHROME_BROWSER_APPS_APP_URL_REDIRECTOR_H_ diff --git a/chrome/browser/apps/app_url_redirector_browsertest.cc b/chrome/browser/apps/app_url_redirector_browsertest.cc new file mode 100644 index 0000000..efa10d0 --- /dev/null +++ b/chrome/browser/apps/app_url_redirector_browsertest.cc @@ -0,0 +1,458 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/apps/app_browsertest_util.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/extensions/extension_test_message_listener.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/common/page_transition_types.h" +#include "content/public/test/browser_test_base.h" +#include "content/public/test/browser_test_utils.h" +#include "net/test/embedded_test_server/embedded_test_server.h" + +namespace extensions { + +class PlatformAppUrlRedirectorBrowserTest : public PlatformAppBrowserTest { + public: + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE; + + protected: + // Performs the following sequence: + // - installs the app |handler| (a relative path under the platform_apps + // subdirectory); + // - navigates the current tab to the HTML page |lancher_page| (ditto); + // - then waits for |handler| to launch and send back a |handler_ack_message|; + // - finally checks that the resulting shell window count is as expected. + // The |launcher_page| is supposed to trigger a navigation matching one of the + // url_handlers in the |handler|'s manifest, and thereby launch the |handler|. + void TestNavigationInTab(const char* launcher_page, + const char* handler, + const char* handler_start_message); + + // The same as above, but does not expect the |handler| to launch. Verifies + // that it didn't, and that the navigation has opened in a new tab instead. + void TestMismatchingNavigationInTab(const char* launcher_page, + const char* success_tab_title, + const char* handler); + + // - installs the app |handler|; + // - opens the page |xhr_opening_page| in the current tab; + // - the page sends an XHR to a local resouce, whose URL matches one of the + // url_handlers in |handler|; + // - waits until |xhr_opening_page| gets a response back and changes the tab's + // title to a value indicating success/failure of the XHR; + // - verifies that no shell windows have been opened, i.e. |handler| wasn't + // launched even though its url_handlers match the URL. + void TestNegativeXhrInTab(const char* xhr_opening_page, + const char* success_tab_title, + const char* failure_tab_title, + const char* handler); + + // Performs the following sequence: + // - installs the app |handler| (a relative path under the platform_apps + // subdirectory); + // - loads and launches the app |launcher| (ditto); + // - waits for the |launcher| to launch and send back a |launcher_ack_message| + // (to make sure it's not the failing entity, if the test fails overall); + // - waits for the |handler| to launch and send back a |handler_ack_message|; + // - finally checks that the resulting shell window count is as expected. + // The |launcher| is supposed to trigger a navigation matching one of the + // url_handlers in the |handler|'s manifest, and thereby launch the |handler|. + void TestNavigationInApp(const char* launcher, + const char* launcher_done_message, + const char* handler, + const char* handler_start_message); + + // The same as above, but does not expect the |handler| to launch. Verifies + // that it didn't, and that the navigation has opened in a new tab instead. + void TestMismatchingNavigationInApp(const char* launcher, + const char* launcher_done_message, + const char* handler); + + // - installs the |handler| app; + // - loads and launches the |launcher| app; + // - waits until the |launcher| sends back a |launcher_done_message|; + // - the launcher performs a navigation to a URL that mismatches the + // |handler|'s url_handlers; + // - verifies that the |handler| hasn't been launched as a result of the + // navigation. + void TestNegativeNavigationInApp(const char* launcher, + const char* launcher_done_message, + const char* handler); + + // - installs the app |handler|; + // - navigates the current tab to the HTML page |matching_target_page| with + // page transition |transition|; + // - waits for |handler| to launch and send back a |handler_start_message|; + // - finally checks that the resulting shell window count is as expected. + void TestNavigationInBrowser(const char* matching_target_page, + content::PageTransition transition, + const char* handler, + const char* handler_start_message); + + // Same as above, but does not expect |handler| to launch. This is used, e.g. + // for form submissions, where the URL would normally match the url_handlers + // but should not launch it. + void TestNegativeNavigationInBrowser(const char* matching_target_page, + content::PageTransition transition, + const char* handler); + + // Same as above, but expects the |mismatching_target_page| should not match + // any of the |handler|'s url_handlers, and therefor not launch the app. + void TestMismatchingNavigationInBrowser(const char* mismatching_target_page, + content::PageTransition transition, + const char* handler); +}; + + +void PlatformAppUrlRedirectorBrowserTest::SetUpCommandLine( + CommandLine* command_line) { + PlatformAppBrowserTest::SetUpCommandLine(command_line); + command_line->AppendSwitch(::switches::kDisablePopupBlocking); +} + +// TODO(sergeygs): Factor out common functionality from TestXyz, +// TestNegativeXyz, and TestMismatchingXyz versions. + +// TODO(sergeys): Return testing::AssertionErrors from these methods to +// preserve line numbers and (if applicable) failure messages. + +void PlatformAppUrlRedirectorBrowserTest::TestNavigationInTab( + const char* launcher_page, + const char* handler, + const char* handler_start_message) { + ASSERT_TRUE(StartEmbeddedTestServer()); + + InstallPlatformApp(handler); + + ExtensionTestMessageListener handler_listener(handler_start_message, false); + + ui_test_utils::NavigateToURLWithDisposition( + browser(), + embedded_test_server()->GetURL(base::StringPrintf( + "/extensions/platform_apps/%s", launcher_page)), + CURRENT_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + + ASSERT_TRUE(handler_listener.WaitUntilSatisfied()); + + ASSERT_EQ(1U, GetShellWindowCount()); +} + +void PlatformAppUrlRedirectorBrowserTest::TestMismatchingNavigationInTab( + const char* launcher_page, + const char* success_tab_title, + const char* handler) { + ASSERT_TRUE(StartEmbeddedTestServer()); + + InstallPlatformApp(handler); + + const string16 success_title = ASCIIToUTF16(success_tab_title); + content::WebContents* tab = + browser()->tab_strip_model()->GetActiveWebContents(); + content::TitleWatcher title_watcher(tab, success_title); + + ui_test_utils::NavigateToURLWithDisposition( + browser(), + embedded_test_server()->GetURL(base::StringPrintf( + "/extensions/platform_apps/%s", launcher_page)), + CURRENT_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + + ASSERT_EQ(success_title, title_watcher.WaitAndGetTitle()); + ASSERT_EQ(1, browser()->tab_strip_model()->count()); + ASSERT_EQ(0U, GetShellWindowCount()); +} + +void PlatformAppUrlRedirectorBrowserTest::TestNegativeXhrInTab( + const char* launcher_page, + const char* success_tab_title, + const char* failure_tab_title, + const char* handler) { + ASSERT_TRUE(StartEmbeddedTestServer()); + + InstallPlatformApp(handler); + + const string16 success_title = ASCIIToUTF16(success_tab_title); + const string16 failure_title = ASCIIToUTF16(failure_tab_title); + content::WebContents* tab = + browser()->tab_strip_model()->GetActiveWebContents(); + content::TitleWatcher title_watcher(tab, success_title); + title_watcher.AlsoWaitForTitle(failure_title); + + ui_test_utils::NavigateToURLWithDisposition( + browser(), + embedded_test_server()->GetURL(base::StringPrintf( + "/extensions/platform_apps/%s", launcher_page)), + CURRENT_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); + + ASSERT_EQ(success_title, title_watcher.WaitAndGetTitle()); + ASSERT_EQ(1, browser()->tab_strip_model()->count()); + ASSERT_EQ(0U, GetShellWindowCount()); +} + +void PlatformAppUrlRedirectorBrowserTest::TestNavigationInApp( + const char* launcher, + const char* launcher_done_message, + const char* handler, + const char* handler_start_message) { + ASSERT_TRUE(StartEmbeddedTestServer()); + + InstallPlatformApp(handler); + + ExtensionTestMessageListener launcher_listener(launcher_done_message, false); + ExtensionTestMessageListener handler_listener(handler_start_message, false); + + LoadAndLaunchPlatformApp(launcher); + + ASSERT_TRUE(launcher_listener.WaitUntilSatisfied()); + ASSERT_TRUE(handler_listener.WaitUntilSatisfied()); + + ASSERT_EQ(2U, GetShellWindowCount()); +} + +void PlatformAppUrlRedirectorBrowserTest::TestNegativeNavigationInApp( + const char* launcher, + const char* launcher_done_message, + const char* handler) { + ASSERT_TRUE(StartEmbeddedTestServer()); + + InstallPlatformApp(handler); + + content::WindowedNotificationObserver observer( + chrome::NOTIFICATION_TAB_ADDED, + content::Source<content::WebContentsDelegate>(browser())); + + ExtensionTestMessageListener launcher_done_listener(launcher_done_message, + false); + LoadAndLaunchPlatformApp(launcher); + + ASSERT_TRUE(launcher_done_listener.WaitUntilSatisfied()); + + observer.Wait(); + + ASSERT_EQ(1U, GetShellWindowCount()); +} + +void PlatformAppUrlRedirectorBrowserTest::TestMismatchingNavigationInApp( + const char* launcher, + const char* launcher_done_message, + const char* handler) { + ASSERT_TRUE(StartEmbeddedTestServer()); + + InstallPlatformApp(handler); + + content::WindowedNotificationObserver observer( + chrome::NOTIFICATION_TAB_ADDED, + content::Source<content::WebContentsDelegate>(browser())); + + ExtensionTestMessageListener launcher_listener(launcher_done_message, false); + LoadAndLaunchPlatformApp(launcher); + + ASSERT_TRUE(launcher_listener.WaitUntilSatisfied()); + + observer.Wait(); + ASSERT_EQ(1U, GetShellWindowCount()); + ASSERT_EQ(2, browser()->tab_strip_model()->count()); +} + +void PlatformAppUrlRedirectorBrowserTest::TestNavigationInBrowser( + const char* matching_target_page, + content::PageTransition transition, + const char* handler, + const char* handler_start_message) { + ASSERT_TRUE(StartEmbeddedTestServer()); + + InstallPlatformApp(handler); + + ExtensionTestMessageListener handler_listener(handler_start_message, false); + + chrome::NavigateParams params( + browser(), + embedded_test_server()->GetURL(base::StringPrintf( + "/extensions/platform_apps/%s", matching_target_page)), + transition); + ui_test_utils::NavigateToURL(¶ms); + + ASSERT_TRUE(handler_listener.WaitUntilSatisfied()); + + ASSERT_EQ(1U, GetShellWindowCount()); +} + +void PlatformAppUrlRedirectorBrowserTest::TestNegativeNavigationInBrowser( + const char* matching_target_page, + content::PageTransition transition, + const char* handler) { + ASSERT_TRUE(StartEmbeddedTestServer()); + + InstallPlatformApp(handler); + + chrome::NavigateParams params( + browser(), + embedded_test_server()->GetURL(base::StringPrintf( + "/extensions/platform_apps/%s", matching_target_page)), + transition); + ui_test_utils::NavigateToURL(¶ms); + + ASSERT_EQ(0U, GetShellWindowCount()); +} + +void PlatformAppUrlRedirectorBrowserTest::TestMismatchingNavigationInBrowser( + const char* mismatching_target_page, + content::PageTransition transition, + const char* handler) { + TestNegativeNavigationInBrowser(mismatching_target_page, transition, handler); +} + +// Test that a click on a regular link in a tab launches an app that has +// matching url_handlers. +IN_PROC_BROWSER_TEST_F(PlatformAppUrlRedirectorBrowserTest, + ClickInTabIntercepted) { + TestNavigationInTab( + "url_handlers/launching_pages/click_link.html", + "url_handlers/handlers/simple", + "Handler launched"); +} + +// Test that a click on a target='_blank' link in a tab launches an app that has +// matching url_handlers. +IN_PROC_BROWSER_TEST_F(PlatformAppUrlRedirectorBrowserTest, + BlankClickInTabIntercepted) { + TestNavigationInTab( + "url_handlers/launching_pages/click_blank_link.html", + "url_handlers/handlers/simple", + "Handler launched"); +} + +// Test that a call to window.open() in a tab launches an app that has +// matching url_handlers. +IN_PROC_BROWSER_TEST_F(PlatformAppUrlRedirectorBrowserTest, + WindowOpenInTabIntercepted) { + TestNavigationInTab( + "url_handlers/launching_pages/call_window_open.html", + "url_handlers/handlers/simple", + "Handler launched"); +} + +// Test that a click on a regular link in a tab launches an app that has +// matching url_handlers. +IN_PROC_BROWSER_TEST_F(PlatformAppUrlRedirectorBrowserTest, + MismatchingClickInTabNotIntercepted) { + TestMismatchingNavigationInTab( + "url_handlers/launching_pages/click_mismatching_link.html", + "Mismatching link target loaded", + "url_handlers/handlers/simple"); +} + +// Test that a click on target='_blank' link in an app's window launches +// another app that has matching url_handlers. +IN_PROC_BROWSER_TEST_F(PlatformAppUrlRedirectorBrowserTest, + BlankClickInAppIntercepted) { + TestNavigationInApp( + "url_handlers/launchers/click_blank_link", + "Launcher done", + "url_handlers/handlers/simple", + "Handler launched"); +} + +// Test that a call to window.open() in the app's foreground page launches +// another app that has matching url_handlers. +IN_PROC_BROWSER_TEST_F(PlatformAppUrlRedirectorBrowserTest, + WindowOpenInAppIntercepted) { + TestNavigationInApp( + "url_handlers/launchers/call_window_open", + "Launcher done", + "url_handlers/handlers/simple", + "Handler launched"); +} + +// Test that an app with url_handlers does not intercept a mismatching +// click on a target='_blank' link in another app's window. +IN_PROC_BROWSER_TEST_F(PlatformAppUrlRedirectorBrowserTest, + MismatchingWindowOpenInAppNotIntercepted) { + TestMismatchingNavigationInApp( + "url_handlers/launchers/call_mismatching_window_open", + "Launcher done", + "url_handlers/handlers/simple"); +} + +// Test that a webview in an app can be navigated to a URL without interception +// even when there are other (or the same) apps that have matching url_handlers. +IN_PROC_BROWSER_TEST_F(PlatformAppUrlRedirectorBrowserTest, + WebviewNavigationNotIntercepted) { + // The launcher clicks on a link, which gets intercepted and launches the + // handler. The handler also redirects an embedded webview to the URL. The + // webview should just navigate without creating an endless loop of + // navigate-intercept-launch sequences with multiplying handler's windows. + // There should be 2 windows only: launcher's and handler's. + TestNavigationInApp( + "url_handlers/launchers/click_blank_link", + "Launcher done", + "url_handlers/handlers/navigate_webview_to_url", + "Handler launched"); +} + +// Test that a webview in an app can be navigated to a URL without interception +// even when there are other (or the same) apps that have matching url_handlers. +IN_PROC_BROWSER_TEST_F(PlatformAppUrlRedirectorBrowserTest, + MismatchingBlankClickInAppNotIntercepted) { + // The launcher clicks on a link, which gets intercepted and launches the + // handler. The handler also redirects an embedded webview to the URL. The + // webview should just navigate without creating an endless loop of + // navigate-intercept-launch sequences with multiplying handler's windows. + // There should be 2 windows only: launcher's and handler's. + TestMismatchingNavigationInApp( + "url_handlers/launchers/click_mismatching_blank_link", + "Launcher done", + "url_handlers/handlers/simple"); +} + +// Test that a URL entry in the omnibar launches an app that has matching +// url_handlers. +IN_PROC_BROWSER_TEST_F(PlatformAppUrlRedirectorBrowserTest, + EntryInOmnibarIntercepted) { + TestNavigationInBrowser( + "url_handlers/common/target.html", + content::PAGE_TRANSITION_TYPED, + "url_handlers/handlers/simple", + "Handler launched"); +} + +// Test that an app with url_handlers does not intercept a mismatching +// URL entry in the omnibar. +IN_PROC_BROWSER_TEST_F(PlatformAppUrlRedirectorBrowserTest, + MismatchingEntryInOmnibarNotIntercepted) { + TestMismatchingNavigationInBrowser( + "url_handlers/common/mismatching_target.html", + content::PAGE_TRANSITION_TYPED, + "url_handlers/handlers/simple"); +} + +// Test that a form submission in a page is never subject to interception +// by apps even with matching url_handlers. +IN_PROC_BROWSER_TEST_F(PlatformAppUrlRedirectorBrowserTest, + FormSubmissionInTabNotIntercepted) { + TestNegativeNavigationInBrowser( + "url_handlers/common/target.html", + content::PAGE_TRANSITION_FORM_SUBMIT, + "url_handlers/handlers/simple"); +} + +// Test that a form submission in a page is never subject to interception +// by apps even with matching url_handlers. +IN_PROC_BROWSER_TEST_F(PlatformAppUrlRedirectorBrowserTest, + XhrInTabNotIntercepted) { + TestNegativeXhrInTab( + "url_handlers/xhr_downloader/main.html", + "XHR succeeded", + "XHR failed", + "url_handlers/handlers/steal_xhr_target"); +} + +} // namespace extensions diff --git a/chrome/browser/extensions/api/app_runtime/app_runtime_api.cc b/chrome/browser/extensions/api/app_runtime/app_runtime_api.cc index b976dc7..f48164d 100644 --- a/chrome/browser/extensions/api/app_runtime/app_runtime_api.cc +++ b/chrome/browser/extensions/api/app_runtime/app_runtime_api.cc @@ -61,6 +61,8 @@ void AppEventRouter::DispatchOnLaunchedEventWithFileEntry( Profile* profile, const Extension* extension, const std::string& handler_id, const std::string& mime_type, const extensions::app_file_handler_util::GrantedFileEntry& file_entry) { + // TODO(sergeygs): Use the same way of creating an event (using the generated + // boilerplate) as below in DispatchOnLaunchedEventWithUrl. scoped_ptr<base::ListValue> args(new base::ListValue()); base::DictionaryValue* launch_data = new base::DictionaryValue(); launch_data->SetString("id", handler_id); @@ -76,4 +78,20 @@ void AppEventRouter::DispatchOnLaunchedEventWithFileEntry( DispatchOnLaunchedEventImpl(extension->id(), args.Pass(), profile); } +// static. +void AppEventRouter::DispatchOnLaunchedEventWithUrl( + Profile* profile, + const Extension* extension, + const std::string& handler_id, + const GURL& url, + const GURL& referrer_url) { + api::app_runtime::LaunchData launch_data; + launch_data.id.reset(new std::string(handler_id)); + launch_data.url.reset(new std::string(url.spec())); + launch_data.referrer_url.reset(new std::string(referrer_url.spec())); + scoped_ptr<ListValue> args(new ListValue()); + args->Append(launch_data.ToValue().release()); + DispatchOnLaunchedEventImpl(extension->id(), args.Pass(), profile); +} + } // namespace extensions diff --git a/chrome/browser/extensions/api/app_runtime/app_runtime_api.h b/chrome/browser/extensions/api/app_runtime/app_runtime_api.h index 220f0ca..c06a51b 100644 --- a/chrome/browser/extensions/api/app_runtime/app_runtime_api.h +++ b/chrome/browser/extensions/api/app_runtime/app_runtime_api.h @@ -28,6 +28,9 @@ class AppEventRouter { // data. static void DispatchOnLaunchedEvent(Profile* profile, const Extension* extension); + + // Dispatches the onRestarted event to the given app, providing a list of + // restored file entries from the previous run. static void DispatchOnRestartedEvent(Profile* profile, const Extension* extension); @@ -43,15 +46,24 @@ class AppEventRouter { // } // } - // launchData.intent.data and launchData.intent.postResults are created in a - // custom dispatch event in javascript. The FileEntry is created from - // |file_system_id| and |base_name|. + // The FileEntry is created from |file_system_id| and |base_name|. + // |handler_id| corresponds to the id of the file_handlers item in the + // manifest that resulted in a match which triggered this launch. static void DispatchOnLaunchedEventWithFileEntry( Profile* profile, const Extension* extension, const std::string& handler_id, const std::string& mime_type, const extensions::app_file_handler_util::GrantedFileEntry& file_entry); + + // |handler_id| corresponds to the id of the url_handlers item + // in the manifest that resulted in a match which triggered this launch. + static void DispatchOnLaunchedEventWithUrl( + Profile* profile, + const Extension* extension, + const std::string& handler_id, + const GURL& url, + const GURL& referrer_url); }; } // namespace extensions diff --git a/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc b/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc index 4e75cc9..063460e 100644 --- a/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc +++ b/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc @@ -65,6 +65,8 @@ #if defined(OS_ANDROID) #include "chrome/browser/android/intercept_download_resource_throttle.h" #include "components/navigation_interception/intercept_navigation_delegate.h" +#else +#include "chrome/browser/apps/app_url_redirector.h" #endif #if defined(OS_CHROMEOS) @@ -263,12 +265,23 @@ void ChromeResourceDispatcherHostDelegate::RequestBeginning( request->SetPriority(net::IDLE); } -#if defined(OS_ANDROID) + ProfileIOData* io_data = ProfileIOData::FromResourceContext( + resource_context); + if (!is_prerendering && resource_type == ResourceType::MAIN_FRAME) { +#if defined(OS_ANDROID) throttles->push_back( InterceptNavigationDelegate::CreateThrottleFor(request)); - } +#else + // Redirect some navigations to apps that have registered matching URL + // handlers ('url_handlers' in the manifest). + content::ResourceThrottle* url_to_app_throttle = + AppUrlRedirector::MaybeCreateThrottleFor(request, io_data); + if (url_to_app_throttle) + throttles->push_back(url_to_app_throttle); #endif + } + #if defined(OS_CHROMEOS) if (resource_type == ResourceType::MAIN_FRAME) { // We check offline first, then check safe browsing so that we still can @@ -289,8 +302,6 @@ void ChromeResourceDispatcherHostDelegate::RequestBeginning( if (!request->is_pending()) { net::HttpRequestHeaders headers; headers.CopyFrom(request->extra_request_headers()); - ProfileIOData* io_data = ProfileIOData::FromResourceContext( - resource_context); bool incognito = io_data->is_incognito(); chrome_variations::VariationsHttpHeaderProvider::GetInstance()-> AppendHeaders(request->url(), @@ -311,7 +322,6 @@ void ChromeResourceDispatcherHostDelegate::RequestBeginning( resource_type, throttles); - ProfileIOData* io_data = ProfileIOData::FromResourceContext(resource_context); if (io_data->resource_prefetch_predictor_observer()) { io_data->resource_prefetch_predictor_observer()->OnRequestStarted( request, resource_type, child_id, route_id); diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index 4f86fca..a5f65e9 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -64,6 +64,8 @@ '../extensions/browser/pref_names.h', '../extensions/browser/view_type_utils.cc', '../extensions/browser/view_type_utils.h', + 'browser/apps/app_url_redirector.cc', + 'browser/apps/app_url_redirector.h', 'browser/apps/shortcut_manager.cc', 'browser/apps/shortcut_manager.h', 'browser/apps/shortcut_manager_factory.cc', diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi index c50836c..a7a9ae1 100644 --- a/chrome/chrome_common.gypi +++ b/chrome/chrome_common.gypi @@ -219,6 +219,8 @@ 'common/extensions/api/storage/storage_schema_manifest_handler.h', 'common/extensions/api/system_indicator/system_indicator_handler.cc', 'common/extensions/api/system_indicator/system_indicator_handler.h', + 'common/extensions/api/url_handlers/url_handlers_parser.cc', + 'common/extensions/api/url_handlers/url_handlers_parser.h', 'common/extensions/background_info.cc', 'common/extensions/background_info.h', 'common/extensions/chrome_extensions_client.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index c2d0ff6..0b6009e 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1195,6 +1195,7 @@ 'browser/apps/app_browsertest.cc', 'browser/apps/app_browsertest_util.cc', 'browser/apps/app_browsertest_util.h', + 'browser/apps/app_url_redirector_browsertest.cc', 'browser/apps/web_view_browsertest.cc', 'browser/apps/window_controls_browsertest.cc', 'browser/autocomplete/autocomplete_browsertest.cc', diff --git a/chrome/common/extensions/api/_manifest_features.json b/chrome/common/extensions/api/_manifest_features.json index 4565899..81a8427 100644 --- a/chrome/common/extensions/api/_manifest_features.json +++ b/chrome/common/extensions/api/_manifest_features.json @@ -361,6 +361,10 @@ "channel": "stable", "extension_types": "all" }, + "url_handlers": { + "channel": "dev", + "extension_types": ["platform_app"] + }, "version": { "channel": "stable", "extension_types": "all" diff --git a/chrome/common/extensions/api/app_runtime.idl b/chrome/common/extensions/api/app_runtime.idl index da8ddb8..51b3862 100644 --- a/chrome/common/extensions/api/app_runtime.idl +++ b/chrome/common/extensions/api/app_runtime.idl @@ -15,12 +15,26 @@ namespace app.runtime { DOMString type; }; - // Optional data for the launch. + // Optional data for the launch. Either <code>items</code>, or + // the pair (<code>url, referrerUrl</code>) can be present for any given + // launch. [inline_doc] dictionary LaunchData { - // The id of the file handler that the app is being invoked with. + // The ID of the file or URL handler that the app is being invoked with. + // Handler IDs are the top-level keys in the <code>file_handlers</code> + // and/or <code>url_handlers</code> dictionaries in the manifest. DOMString? id; + // The file entries for the <code>onLaunched</code> event triggered by a + // matching file handler in the <code>file_handlers</code> manifest key. LaunchItem[]? items; + + // The URL for the <code>onLaunched</code> event triggered by a matching + // URL handler in the <code>url_handlers</code> manifest key. + DOMString? url; + + // The referrer URL for the <code>onLaunched</code> event triggered by a + // matching URL handler in the <code>url_handlers</code> manifest key. + DOMString? referrerUrl; }; interface Events { diff --git a/chrome/common/extensions/api/url_handlers/url_handlers_parser.cc b/chrome/common/extensions/api/url_handlers/url_handlers_parser.cc new file mode 100644 index 0000000..68bd302 --- /dev/null +++ b/chrome/common/extensions/api/url_handlers/url_handlers_parser.cc @@ -0,0 +1,170 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/extensions/api/url_handlers/url_handlers_parser.h" + +#include "base/command_line.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/common/extensions/extension_manifest_constants.h" +#include "chrome/common/extensions/manifest_handlers/offline_enabled_info.h" +#include "extensions/common/error_utils.h" +#include "extensions/common/switches.h" +#include "net/base/network_change_notifier.h" +#include "url/gurl.h" + +using base::ASCIIToUTF16; +using net::NetworkChangeNotifier; + +namespace mkeys = extensions::manifest_keys; +namespace merrors = extension_manifest_errors; + +// TODO(sergeygs): Use the same strategy that externally_connectable does for +// parsing the manifest: declare a schema for the manifest entry in +// manifest_types.json, then use it here. +// +// See: +// chrome/common/extensions/api/manifest_types.json +// chrome/common/extensions/manifest_handlers/externally_connectable.* +// +// Do the same in (at least) file_handlers_parser.cc as well. + +namespace extensions { + +UrlHandlerInfo::UrlHandlerInfo() { +} + +UrlHandlerInfo::~UrlHandlerInfo() { +} + +UrlHandlers::UrlHandlers() { +} + +UrlHandlers::~UrlHandlers() { +} + +// static +const std::vector<UrlHandlerInfo>* UrlHandlers::GetUrlHandlers( + const Extension* extension) { + UrlHandlers* info = static_cast<UrlHandlers*>( + extension->GetManifestData(mkeys::kUrlHandlers)); + return info ? &info->handlers : NULL; +} + +// static +bool UrlHandlers::CanExtensionHandleUrl( + const Extension* extension, + const GURL& url) { + return FindMatchingUrlHandler(extension, url) != NULL; +} + +// static +const UrlHandlerInfo* UrlHandlers::FindMatchingUrlHandler( + const Extension* extension, + const GURL& url) { + const std::vector<UrlHandlerInfo>* handlers = GetUrlHandlers(extension); + if (!handlers) + return NULL; + + if (NetworkChangeNotifier::IsOffline() && + !OfflineEnabledInfo::IsOfflineEnabled(extension)) + return NULL; + + for (std::vector<extensions::UrlHandlerInfo>::const_iterator it = + handlers->begin(); it != handlers->end(); it++) { + if (it->patterns.MatchesURL(url)) + return &(*it); + } + + return NULL; +} + +UrlHandlersParser::UrlHandlersParser() { +} + +UrlHandlersParser::~UrlHandlersParser() { +} + +bool ParseUrlHandler(const std::string& handler_id, + const DictionaryValue& handler_info, + std::vector<UrlHandlerInfo>* url_handlers, + string16* error) { + DCHECK(error); + + UrlHandlerInfo handler; + handler.id = handler_id; + + if (!handler_info.GetString(mkeys::kUrlHandlerTitle, &handler.title)) { + *error = ASCIIToUTF16(merrors::kInvalidURLHandlerTitle); + return false; + } + + const ListValue* manif_patterns = NULL; + if (!handler_info.GetList(mkeys::kMatches, &manif_patterns) || + manif_patterns->GetSize() == 0) { + *error = ErrorUtils::FormatErrorMessageUTF16( + merrors::kInvalidURLHandlerPattern, handler_id); + return false; + } + + for (ListValue::const_iterator it = manif_patterns->begin(); + it != manif_patterns->end(); ++it) { + std::string str_pattern; + (*it)->GetAsString(&str_pattern); + // TODO(sergeygs): Limit this to non-top-level domains. + // TODO(sergeygs): Also add a verification to the CWS installer that the + // URL patterns claimed here belong to the app's author verified sites. + URLPattern pattern(URLPattern::SCHEME_HTTP | + URLPattern::SCHEME_HTTPS); + if (pattern.Parse(str_pattern) != URLPattern::PARSE_SUCCESS) { + *error = ErrorUtils::FormatErrorMessageUTF16( + merrors::kInvalidURLHandlerPatternElement, handler_id); + return false; + } + handler.patterns.AddPattern(pattern); + } + + url_handlers->push_back(handler); + + return true; +} + +bool UrlHandlersParser::Parse(Extension* extension, string16* error) { + scoped_ptr<UrlHandlers> info(new UrlHandlers); + const DictionaryValue* all_handlers = NULL; + if (!extension->manifest()->GetDictionary( + mkeys::kUrlHandlers, &all_handlers)) { + *error = ASCIIToUTF16(merrors::kInvalidURLHandlers); + return false; + } + + DCHECK(extension->is_platform_app()); + + for (DictionaryValue::Iterator iter(*all_handlers); !iter.IsAtEnd(); + iter.Advance()) { + // A URL handler entry is a title and a list of URL patterns to handle. + const DictionaryValue* handler = NULL; + if (!iter.value().GetAsDictionary(&handler)) { + *error = ASCIIToUTF16(merrors::kInvalidURLHandlerPatternElement); + return false; + } + + if (!ParseUrlHandler(iter.key(), *handler, &info->handlers, error)) { + // Text in |error| is set by ParseUrlHandler. + return false; + } + } + + extension->SetManifestData(mkeys::kUrlHandlers, info.release()); + + return true; +} + +const std::vector<std::string> UrlHandlersParser::Keys() const { + return SingleKey(mkeys::kUrlHandlers); +} + +} // namespace extensions diff --git a/chrome/common/extensions/api/url_handlers/url_handlers_parser.h b/chrome/common/extensions/api/url_handlers/url_handlers_parser.h new file mode 100644 index 0000000..4b2ed4d --- /dev/null +++ b/chrome/common/extensions/api/url_handlers/url_handlers_parser.h @@ -0,0 +1,71 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_EXTENSIONS_API_URL_HANDLERS_URL_HANDLERS_PARSER_H_ +#define CHROME_COMMON_EXTENSIONS_API_URL_HANDLERS_URL_HANDLERS_PARSER_H_ + +#include <string> +#include <vector> + +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/manifest_handler.h" +#include "extensions/common/url_pattern.h" + +class GURL; + +namespace extensions { + +struct UrlHandlerInfo { + UrlHandlerInfo(); + ~UrlHandlerInfo(); + + // ID identifying this handler in the manifest. + std::string id; + // Handler title to display in all relevant UI. + std::string title; + // URL patterns associated with this handler. + URLPatternSet patterns; +}; + +struct UrlHandlers : public Extension::ManifestData { + UrlHandlers(); + virtual ~UrlHandlers(); + + // Returns an array of URL handlers |extension| has defined in its manifest. + static const std::vector<UrlHandlerInfo>* GetUrlHandlers( + const Extension* extension); + + // Determines whether |extension| has at least one URL handler that matches + // |url|. + static bool CanExtensionHandleUrl( + const Extension* extension, + const GURL& url); + + // Finds a matching URL handler for |extension|, if any. Returns NULL in none + // are found. + static const UrlHandlerInfo* FindMatchingUrlHandler( + const Extension* extension, + const GURL& url); + + std::vector<UrlHandlerInfo> handlers; +}; + +// Parses the "url_handlers" manifest key. +class UrlHandlersParser : public ManifestHandler { + public: + UrlHandlersParser(); + virtual ~UrlHandlersParser(); + + // ManifestHandler API + virtual bool Parse(Extension* extension, string16* error) OVERRIDE; + + private: + virtual const std::vector<std::string> Keys() const OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(UrlHandlersParser); +}; + +} // namespace extensions + +#endif // CHROME_COMMON_EXTENSIONS_API_URL_HANDLERS_URL_HANDLERS_PARSER_H_ diff --git a/chrome/common/extensions/chrome_manifest_handlers.cc b/chrome/common/extensions/chrome_manifest_handlers.cc index 1881868..07137d9 100644 --- a/chrome/common/extensions/chrome_manifest_handlers.cc +++ b/chrome/common/extensions/chrome_manifest_handlers.cc @@ -23,6 +23,7 @@ #include "chrome/common/extensions/api/speech/tts_engine_manifest_handler.h" #include "chrome/common/extensions/api/spellcheck/spellcheck_handler.h" #include "chrome/common/extensions/api/system_indicator/system_indicator_handler.h" +#include "chrome/common/extensions/api/url_handlers/url_handlers_parser.h" #include "chrome/common/extensions/background_info.h" #include "chrome/common/extensions/csp_handler.h" #include "chrome/common/extensions/incognito_handler.h" @@ -91,6 +92,7 @@ void RegisterChromeManifestHandlers() { (new ThemeHandler)->Register(); (new TtsEngineManifestHandler)->Register(); (new UpdateURLHandler)->Register(); + (new UrlHandlersParser)->Register(); (new URLOverridesHandler)->Register(); (new WebAccessibleResourcesHandler)->Register(); ManifestHandler::FinalizeRegistration(); diff --git a/chrome/common/extensions/extension_manifest_constants.cc b/chrome/common/extensions/extension_manifest_constants.cc index 92c82c4..888aa69 100644 --- a/chrome/common/extensions/extension_manifest_constants.cc +++ b/chrome/common/extensions/extension_manifest_constants.cc @@ -410,6 +410,14 @@ const char kInvalidTtsVoicesVoiceName[] = "Invalid value for 'tts_engine.voices[*].voice_name'."; const char kInvalidUpdateURL[] = "Invalid value for update url: '[*]'."; +const char kInvalidURLHandlers[] = + "Invalid value for 'url_handlers'."; +const char kInvalidURLHandlerPatternElement[] = + "Invalid value for 'url_handlers[*]'."; +const char kInvalidURLHandlerTitle[] = + "Invalid value for 'url_handlers[*].title'."; +const char kInvalidURLHandlerPattern[] = + "Invalid value for 'url_handlers[*].matches[*]'."; const char kInvalidURLPatternError[] = "Invalid url pattern '*'"; const char kInvalidVersion[] = diff --git a/chrome/common/extensions/extension_manifest_constants.h b/chrome/common/extensions/extension_manifest_constants.h index 37500da..d42714f 100644 --- a/chrome/common/extensions/extension_manifest_constants.h +++ b/chrome/common/extensions/extension_manifest_constants.h @@ -237,6 +237,10 @@ namespace extension_manifest_errors { extern const char kInvalidTtsVoicesVoiceName[]; extern const char kInvalidUpdateURL[]; extern const char kInvalidURLPatternError[]; + extern const char kInvalidURLHandlers[]; + extern const char kInvalidURLHandlerPatternElement[]; + extern const char kInvalidURLHandlerTitle[]; + extern const char kInvalidURLHandlerPattern[]; extern const char kInvalidVersion[]; extern const char kInvalidWebAccessibleResourcesList[]; extern const char kInvalidWebAccessibleResource[]; diff --git a/chrome/renderer/resources/extensions/app_runtime_custom_bindings.js b/chrome/renderer/resources/extensions/app_runtime_custom_bindings.js index 0411d32..b6af67f 100644 --- a/chrome/renderer/resources/extensions/app_runtime_custom_bindings.js +++ b/chrome/renderer/resources/extensions/app_runtime_custom_bindings.js @@ -19,8 +19,15 @@ eventBindings.registerArgumentMassager('app.runtime.onLaunched', function(args, dispatch) { var launchData = args[0]; - if (launchData && typeof launchData.id !== 'undefined') { - // new-style dispatch. + if (!launchData) { + // An onLaunched corresponding to launching directly or from + // the app launcher or browser. + dispatch([]); + return; + } + + if (launchData.items) { + // An onLaunched corresponding to file_handlers in the app's manifest. var items = [] var numItems = launchData.items.length; var itemLoaded = function(err, item) { @@ -47,10 +54,10 @@ eventBindings.registerArgumentMassager('app.runtime.onLaunched', itemLoaded(fileError); }); }); - } else if (launchData) { - dispatch([launchData]); } else { - dispatch([]); + // Default case. This currently covers an onLaunched corresponding to + // url_handlers in the app's manifest. + dispatch([launchData]); } }); diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/common/mismatching_target.html b/chrome/test/data/extensions/platform_apps/url_handlers/common/mismatching_target.html new file mode 100644 index 0000000..ecf52da --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/common/mismatching_target.html @@ -0,0 +1,16 @@ +<!-- + * Copyright (c) 2012 The Chromium Authors. All rights reserved. Use of this + * source code is governed by a BSD-style license that can be found in the + * LICENSE file. +--> +<html> +<body> +I am a target of a remote link. +The handler app should NOT launch with me, even when it is installed, +because my URL does not match its url_handlers. +I should always be loaded in the current tab or opened in a new tab instead. +<script> + document.title = "Mismatching link target loaded"; +</script> +</body> +</html> diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/common/target.html b/chrome/test/data/extensions/platform_apps/url_handlers/common/target.html new file mode 100644 index 0000000..f1b3dfe --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/common/target.html @@ -0,0 +1,12 @@ +<!-- + * Copyright (c) 2012 The Chromium Authors. All rights reserved. Use of this + * source code is governed by a BSD-style license that can be found in the + * LICENSE file. +--> +<html> +<body> +I am a target of a remote link. +The handler app should launch with me, when it is installed. +I will be opened in a new tab, if it isn't or redirection fails. +</body> +</html> diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/handlers/navigate_webview_to_url/main.html b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/navigate_webview_to_url/main.html new file mode 100644 index 0000000..26218db --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/navigate_webview_to_url/main.html @@ -0,0 +1,13 @@ +<!-- + * Copyright 2013 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. +--> +<html> +<body> + <div>I'm a URL handler. I'll try to redirect the webview below to the URL + I've been launched with:</div> + <div><a id='link' href='about:blank' target='_blank'></a><div> + <div><webview id='wv'></webview> +</body> +</html> diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/handlers/navigate_webview_to_url/manifest.json b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/navigate_webview_to_url/manifest.json new file mode 100644 index 0000000..797fb8e --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/navigate_webview_to_url/manifest.json @@ -0,0 +1,22 @@ +{ + "name": "Handler: navigate webview", + "version": "1", + "app": { + "background": { + "scripts": ["test.js"] + } + }, + + "permissions": [ + "webview" + ], + + "url_handlers": { + "my_doc_url": { + "title": "Open my doc", + "matches": [ + "http://localhost:*/extensions/platform_apps/url_handlers/*" + ] + } + } +} diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/handlers/navigate_webview_to_url/test.js b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/navigate_webview_to_url/test.js new file mode 100644 index 0000000..d18f596 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/navigate_webview_to_url/test.js @@ -0,0 +1,30 @@ +// Copyright 2013 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. + +chrome.app.runtime.onLaunched.addListener(function (launchData) { + // Complete correctness of launchData is tested in another test. + chrome.test.assertTrue(typeof launchData !== 'undefined'); + + chrome.app.window.create( + "main.html", + {}, + function(win) { + win.contentWindow.onload = function() { + // Redirect the embedded webview to the same URL we've been launched + // with. This should not create an endless loop of redirecting on + // ourselves with multiplying windows. + var webview = this.document.getElementById('wv'); + webview.src = launchData.url; + + webview.addEventListener("loadstop", function() { + // Give webview plenty of time to navigate to make sure that doesn't + // relaunch the handler. + setTimeout(function() { + chrome.test.sendMessage("Handler launched"); + }, 500); + }); + } + }.bind(this) + ); +}); diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/handlers/simple/main.html b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/simple/main.html new file mode 100644 index 0000000..29e3b1ae --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/simple/main.html @@ -0,0 +1,11 @@ +<!-- + * Copyright 2013 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. +--> +<html> +<body> + <div>I'm a URL handler. I've been launched with:</div> + <div><a id='link' href='about:blank' target='_blank'></a><div> +</body> +</html> diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/handlers/simple/manifest.json b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/simple/manifest.json new file mode 100644 index 0000000..60c6ee8 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/simple/manifest.json @@ -0,0 +1,19 @@ +{ + "name": "Handler: simple", + "version": "1", + "app": { + "background": { + "scripts": ["test.js"] + } + }, + + "url_handlers": { + "my_doc_url": { + "title": "Open my doc", + "matches": [ + "http://localhost:*/extensions/platform_apps/url_handlers/*/target.html", + "http://127.0.0.1:*/extensions/platform_apps/url_handlers/*/target.html" + ] + } + } +} diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/handlers/simple/test.js b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/simple/test.js new file mode 100644 index 0000000..fd27aa9 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/simple/test.js @@ -0,0 +1,36 @@ +// Copyright 2013 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. + +chrome.app.runtime.onLaunched.addListener(function (launchData) { + // Test that the id and items fields in FileEntry can be read. + chrome.test.runTests([ + function testUrlHandler() { + chrome.test.assertTrue(typeof launchData != 'undefined', "No launchData"); + chrome.test.assertEq(launchData.id, "my_doc_url", + "launchData.id incorrect"); + chrome.test.assertTrue(typeof launchData.items == 'undefined', + "Launched for file_handlers, not url_handlers"); + chrome.test.assertTrue(typeof launchData.url != 'undefined', + "No url in launchData"); + chrome.test.assertTrue(typeof launchData.referrerUrl != 'undefined', + "No referrerUrl in launchData"); + chrome.test.assertFalse( + !launchData.url.match( + /http:\/\/.*:.*\/extensions\/platform_apps\/url_handlers\/.*/), + "Wrong launchData.url"); + } + ]); + + chrome.app.window.create( + "main.html", + { }, + function(win) { + win.contentWindow.onload = function() { + var link = this.document.querySelector('#link'); + link.href = launchData.url; + link.innerText = launchData.url; + chrome.test.sendMessage("Handler launched"); + } + }); +}); diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/handlers/steal_xhr_target/main.html b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/steal_xhr_target/main.html new file mode 100644 index 0000000..0bdcbac --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/steal_xhr_target/main.html @@ -0,0 +1,12 @@ +<!-- + * Copyright 2013 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. +--> +<html> +<body> +I'm a URL handler. +I'm attempting to steal URLs that the launcher loads via XHR. +I should never succeed in that. +</body> +</html> diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/handlers/steal_xhr_target/manifest.json b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/steal_xhr_target/manifest.json new file mode 100644 index 0000000..fa101c4 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/steal_xhr_target/manifest.json @@ -0,0 +1,19 @@ +{ + "name": "Handler: steal XHR targets", + "version": "1", + "app": { + "background": { + "scripts": ["test.js"] + } + }, + + "url_handlers": { + "my_doc_url": { + "title": "Steal somebody else's doc", + "matches": [ + "http://localhost:*/extensions/platform_apps/url_handlers/*/target.html", + "http://127.0.0.1:*/extensions/platform_apps/url_handlers/*/target.html" + ] + } + } +} diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/handlers/steal_xhr_target/test.js b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/steal_xhr_target/test.js new file mode 100644 index 0000000..a112d6c --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/handlers/steal_xhr_target/test.js @@ -0,0 +1,8 @@ +// Copyright 2013 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. + +chrome.app.runtime.onLaunched.addListener(function (launchData) { + chrome.app.window.create("main.html", {}, function() {}); + chrome.test.fail("This handler shouldn't have launched"); +}); diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_mismatching_window_open/main.html b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_mismatching_window_open/main.html new file mode 100644 index 0000000..701aeb2 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_mismatching_window_open/main.html @@ -0,0 +1,11 @@ +<!-- + * Copyright 2013 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. +--> +<html> +<body> +<div>I'm the app's main window page. I will click the link below.</div> +<script src="main.js"></script> +</body> +</html> diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_mismatching_window_open/main.js b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_mismatching_window_open/main.js new file mode 100644 index 0000000..cd13c7a --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_mismatching_window_open/main.js @@ -0,0 +1,15 @@ +// Copyright 2013 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. + +chrome.test.getConfig(function(config) { + var port = config ? config.testServer.port : '0'; + var href = + 'http://localhost:' + port + + '/extensions/platform_apps/url_handlers/common/mismatching_target.html'; + // This click should be intercepted and redirected to the handler app + // (pre-installed by the CPP side of the test before launching this). + window.open(href); + + chrome.test.sendMessage("Launcher done"); +}); diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_mismatching_window_open/manifest.json b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_mismatching_window_open/manifest.json new file mode 100644 index 0000000..ecf8266 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_mismatching_window_open/manifest.json @@ -0,0 +1,9 @@ +{ + "name": "Launcher: mismatching blank link", + "version": "1", + "app": { + "background": { + "scripts": ["test.js"] + } + } +} diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_mismatching_window_open/test.js b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_mismatching_window_open/test.js new file mode 100644 index 0000000..2f9f855 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_mismatching_window_open/test.js @@ -0,0 +1,7 @@ +// Copyright 2013 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. + +chrome.app.runtime.onLaunched.addListener(function() { + chrome.app.window.create('main.html', {}, function () {}); +}); diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_window_open/main.html b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_window_open/main.html new file mode 100644 index 0000000..701aeb2 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_window_open/main.html @@ -0,0 +1,11 @@ +<!-- + * Copyright 2013 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. +--> +<html> +<body> +<div>I'm the app's main window page. I will click the link below.</div> +<script src="main.js"></script> +</body> +</html> diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_window_open/main.js b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_window_open/main.js new file mode 100644 index 0000000..ad66fed --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_window_open/main.js @@ -0,0 +1,15 @@ +// Copyright 2013 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. + +chrome.test.getConfig(function(config) { + var port = config ? config.testServer.port : '0'; + var href = + 'http://localhost:' + port + + '/extensions/platform_apps/url_handlers/common/target.html'; + // This click should be intercepted and redirected to the handler app + // (pre-installed by the CPP side of the test before launching this). + window.open(href); + + chrome.test.sendMessage("Launcher done"); +}); diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_window_open/manifest.json b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_window_open/manifest.json new file mode 100644 index 0000000..52fcb8d --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_window_open/manifest.json @@ -0,0 +1,9 @@ +{ + "name": "Launcher: window.open()", + "version": "1", + "app": { + "background": { + "scripts": ["test.js"] + } + } +} diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_window_open/test.js b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_window_open/test.js new file mode 100644 index 0000000..2f9f855 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/call_window_open/test.js @@ -0,0 +1,7 @@ +// Copyright 2013 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. + +chrome.app.runtime.onLaunched.addListener(function() { + chrome.app.window.create('main.html', {}, function () {}); +}); diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_blank_link/main.html b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_blank_link/main.html new file mode 100644 index 0000000..683d70c --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_blank_link/main.html @@ -0,0 +1,12 @@ +<!-- + * Copyright 2013 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. +--> +<html> +<body> +<div>I'm the app's main window page. I will click the link below.</div> +<div><a id="link" target="_blank">Blank link</a></div> +<script src="main.js"></script> +</body> +</html> diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_blank_link/main.js b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_blank_link/main.js new file mode 100644 index 0000000..df891ba --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_blank_link/main.js @@ -0,0 +1,22 @@ +// Copyright 2013 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. + +chrome.test.getConfig(function(config) { + var port = config ? config.testServer.port : '0'; + + var link = document.getElementById('link'); + link.href = + 'http://localhost:' + port + + '/extensions/platform_apps/url_handlers/common/target.html'; + + // This click should be intercepted and redirected to the handler app + // (pre-installed by the CPP side of the test before launching this). + var clickEvent = document.createEvent('MouseEvents'); + clickEvent.initMouseEvent('click', true, true, window, + 0, 0, 0, 0, 0, false, false, + false, false, 0, null); + link.dispatchEvent(clickEvent); + + chrome.test.sendMessage("Launcher done"); +}); diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_blank_link/manifest.json b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_blank_link/manifest.json new file mode 100644 index 0000000..764a60f --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_blank_link/manifest.json @@ -0,0 +1,9 @@ +{ + "name": "Launcher: blank link", + "version": "1", + "app": { + "background": { + "scripts": ["test.js"] + } + } +} diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_blank_link/test.js b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_blank_link/test.js new file mode 100644 index 0000000..2f9f855 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_blank_link/test.js @@ -0,0 +1,7 @@ +// Copyright 2013 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. + +chrome.app.runtime.onLaunched.addListener(function() { + chrome.app.window.create('main.html', {}, function () {}); +}); diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_mismatching_blank_link/main.html b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_mismatching_blank_link/main.html new file mode 100644 index 0000000..2b53a63 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_mismatching_blank_link/main.html @@ -0,0 +1,12 @@ +<!-- + * Copyright 2013 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. +--> +<html> +<body> +<div>I'm the app's main window page. I will click the link below. It should open in a tab.</div> +<div><a id="mismatching_link" target="_blank">Mismatching blank link</a></div> +<script src="main.js"></script> +</body> +</html> diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_mismatching_blank_link/main.js b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_mismatching_blank_link/main.js new file mode 100644 index 0000000..1c106b5 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_mismatching_blank_link/main.js @@ -0,0 +1,22 @@ +// Copyright 2013 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. + +chrome.test.getConfig(function(config) { + var port = config ? config.testServer.port : '0'; + + var link = document.getElementById('mismatching_link'); + link.href = + 'http://localhost:' + port + + '/extensions/platform_apps/url_handlers/common/mismatching_target.html'; + + // This click should be intercepted and redirected to the handler app + // (pre-installed by the CPP side of the test before launching this). + var clickEvent = document.createEvent('MouseEvents'); + clickEvent.initMouseEvent('click', true, true, window, + 0, 0, 0, 0, 0, false, false, + false, false, 0, null); + link.dispatchEvent(clickEvent); + + chrome.test.sendMessage("Launcher done"); +}); diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_mismatching_blank_link/manifest.json b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_mismatching_blank_link/manifest.json new file mode 100644 index 0000000..764a60f --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_mismatching_blank_link/manifest.json @@ -0,0 +1,9 @@ +{ + "name": "Launcher: blank link", + "version": "1", + "app": { + "background": { + "scripts": ["test.js"] + } + } +} diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_mismatching_blank_link/test.js b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_mismatching_blank_link/test.js new file mode 100644 index 0000000..2f9f855 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launchers/click_mismatching_blank_link/test.js @@ -0,0 +1,7 @@ +// Copyright 2013 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. + +chrome.app.runtime.onLaunched.addListener(function() { + chrome.app.window.create('main.html', {}, function () {}); +}); diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launching_pages/call_window_open.html b/chrome/test/data/extensions/platform_apps/url_handlers/launching_pages/call_window_open.html new file mode 100644 index 0000000..7ce197d --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launching_pages/call_window_open.html @@ -0,0 +1,11 @@ +<!-- + * Copyright 2013 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. +--> +<html> +<body> +I'm a page that calls window.open(). +<script src="navigate.js"></script> +</body> +</html> diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launching_pages/click_blank_link.html b/chrome/test/data/extensions/platform_apps/url_handlers/launching_pages/click_blank_link.html new file mode 100644 index 0000000..fdbfe17 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launching_pages/click_blank_link.html @@ -0,0 +1,12 @@ +<!-- + * Copyright 2013 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. +--> +<html> +<body> +I'm a page that clicks a target='_blank' link. +<div><a id="link" target="_blank">Blank link</a></div> +<script src="navigate.js"></script> +</body> +</html> diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launching_pages/click_link.html b/chrome/test/data/extensions/platform_apps/url_handlers/launching_pages/click_link.html new file mode 100644 index 0000000..358bdd4 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launching_pages/click_link.html @@ -0,0 +1,12 @@ +<!-- + * Copyright 2013 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. +--> +<html> +<body> +I'm a page that click a regular link. +<div><a id="link">Regular link</a></div> +<script src="navigate.js"></script> +</body> +</html> diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launching_pages/click_mismatching_link.html b/chrome/test/data/extensions/platform_apps/url_handlers/launching_pages/click_mismatching_link.html new file mode 100644 index 0000000..42fc7f5 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launching_pages/click_mismatching_link.html @@ -0,0 +1,12 @@ +<!-- + * Copyright 2013 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. +--> +<html> +<body> +I'm a page that click a mismatching regular link. +<div><a id="mismatching_link">Mismatching regular link</a></div> +<script src="navigate.js"></script> +</body> +</html> diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/launching_pages/navigate.js b/chrome/test/data/extensions/platform_apps/url_handlers/launching_pages/navigate.js new file mode 100644 index 0000000..865b91e --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/launching_pages/navigate.js @@ -0,0 +1,35 @@ +// Copyright 2013 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. + +(function() { + var target_dir = '/extensions/platform_apps/url_handlers/common/'; + var link = document.getElementById('link'); + var mismatching_link = document.getElementById('mismatching_link'); + + if (link || mismatching_link) { + var clickEvent = document.createEvent('MouseEvents'); + clickEvent.initMouseEvent('click', true, true, window, + 0, 0, 0, 0, 0, false, false, + false, false, 0, null); + if (link) { + console.log("Clicking a matching link"); + link.href = target_dir + 'target.html'; + // This click should open the handler app (pre-installed in the browser by + // the CPP test before launching this) with link.href. + link.dispatchEvent(clickEvent); + } + + if (mismatching_link) { + console.log("Clicking a mismatching link"); + mismatching_link.href = target_dir + 'mismatching_target.html'; + // This click should NOT open the handler app, because the URL does not + // match the url_handlers of the app. It should open the link in a new + // tab instead. + mismatching_link.dispatchEvent(clickEvent); + } + } else { + console.log("Calling window.open()"); + window.open(target_dir + 'target.html'); + } +})(); diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/xhr_downloader/download.js b/chrome/test/data/extensions/platform_apps/url_handlers/xhr_downloader/download.js new file mode 100644 index 0000000..38ff074 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/xhr_downloader/download.js @@ -0,0 +1,21 @@ +// Copyright 2013 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. + +(function() { + var href = + '/extensions/platform_apps/url_handlers/xhr_downloader/target.html'; + var xhr = new XMLHttpRequest(); + xhr.onload = function() { + document.querySelector('body').innerText = + 'XHR succeeded:\n' + xhr.responseText; + document.title = "XHR succeeded"; + }; + xhr.onerror = function() { + document.querySelector('body').innerText = + 'XHR failed with status ' + xhr.status; + document.title = "XHR failed"; + } + xhr.open('GET', href, true); + xhr.send(); +})(); diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/xhr_downloader/main.html b/chrome/test/data/extensions/platform_apps/url_handlers/xhr_downloader/main.html new file mode 100644 index 0000000..6021e82 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/xhr_downloader/main.html @@ -0,0 +1,12 @@ +<!-- + * Copyright 2013 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. +--> +<html> +<body> +<div>I'm the app's main window page. +I will download an HTML page using XMLHttpRequest.</div> +<script src="download.js"></script> +</body> +</html> diff --git a/chrome/test/data/extensions/platform_apps/url_handlers/xhr_downloader/target.html b/chrome/test/data/extensions/platform_apps/url_handlers/xhr_downloader/target.html new file mode 100644 index 0000000..fc8a565 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/url_handlers/xhr_downloader/target.html @@ -0,0 +1,10 @@ +<!-- + * Copyright 2013 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. +--> +<html> +<body> +<div>I'm a page to be downloaded via XHR</div> +</body> +</html> diff --git a/extensions/common/manifest_constants.cc b/extensions/common/manifest_constants.cc index 0402c40..cc73f02 100644 --- a/extensions/common/manifest_constants.cc +++ b/extensions/common/manifest_constants.cc @@ -143,6 +143,8 @@ const char kTtsVoicesLang[] = "lang"; const char kTtsVoicesVoiceName[] = "voice_name"; const char kType[] = "type"; const char kUpdateURL[] = "update_url"; +const char kUrlHandlers[] = "url_handlers"; +const char kUrlHandlerTitle[] = "title"; const char kVersion[] = "version"; const char kWebAccessibleResources[] = "web_accessible_resources"; const char kWebURLs[] = "app.urls"; diff --git a/extensions/common/manifest_constants.h b/extensions/common/manifest_constants.h index e08348e..de60410 100644 --- a/extensions/common/manifest_constants.h +++ b/extensions/common/manifest_constants.h @@ -148,6 +148,8 @@ namespace manifest_keys { extern const char kTtsVoicesVoiceName[]; extern const char kType[]; extern const char kUpdateURL[]; + extern const char kUrlHandlers[]; + extern const char kUrlHandlerTitle[]; extern const char kVersion[]; extern const char kWebAccessibleResources[]; extern const char kWebURLs[]; |