summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authoraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-01 01:20:59 +0000
committeraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-01 01:20:59 +0000
commit4fdbc1492aa5003c6fdc0df7f95b3ae9046380e2 (patch)
treee806806102de20b0fab1c1d497d1332839b6a77f /chrome
parent17d40f00b7d7a078649fb142961f612c29553ec0 (diff)
downloadchromium_src-4fdbc1492aa5003c6fdc0df7f95b3ae9046380e2.zip
chromium_src-4fdbc1492aa5003c6fdc0df7f95b3ae9046380e2.tar.gz
chromium_src-4fdbc1492aa5003c6fdc0df7f95b3ae9046380e2.tar.bz2
Add the concept of browse extent.
BUG=46636 Review URL: http://codereview.chromium.org/2862034 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@51327 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/browser.cc5
-rw-r--r--chrome/browser/extensions/app_process_apitest.cc12
-rw-r--r--chrome/browser/extensions/extensions_service.cc2
-rw-r--r--chrome/browser/renderer_host/browser_render_process_host.cc7
-rw-r--r--chrome/browser/resources/calendar_app/manifest.json3
-rw-r--r--chrome/browser/resources/docs_app/manifest.json3
-rw-r--r--chrome/browser/resources/gmail_app/manifest.json3
-rw-r--r--chrome/common/extensions/extension.cc32
-rw-r--r--chrome/common/extensions/extension.h10
-rw-r--r--chrome/common/extensions/extension_constants.cc5
-rw-r--r--chrome/common/extensions/extension_constants.h3
-rw-r--r--chrome/common/extensions/extension_manifests_unittest.cc24
-rw-r--r--chrome/common/render_messages.h29
-rw-r--r--chrome/renderer/render_thread.cc17
-rw-r--r--chrome/renderer/render_thread.h5
-rw-r--r--chrome/renderer/render_view.cc13
-rw-r--r--chrome/test/data/extensions/api_test/app_process/manifest.json3
-rw-r--r--chrome/test/data/extensions/api_test/app_process/path4/empty.html2
-rw-r--r--chrome/test/data/extensions/manifest_tests/browse_urls_default.json13
-rw-r--r--chrome/test/data/extensions/manifest_tests/browse_urls_invalid_1.json12
-rw-r--r--chrome/test/data/extensions/manifest_tests/browse_urls_invalid_2.json12
-rw-r--r--chrome/test/data/extensions/manifest_tests/browse_urls_invalid_3.json12
-rw-r--r--chrome/test/data/extensions/manifest_tests/browse_urls_valid.json16
-rw-r--r--chrome/test/data/extensions/manifest_tests/browse_urls_wrong_type.json10
24 files changed, 227 insertions, 26 deletions
diff --git a/chrome/browser/browser.cc b/chrome/browser/browser.cc
index a4fc69d..97c0870 100644
--- a/chrome/browser/browser.cc
+++ b/chrome/browser/browser.cc
@@ -3606,6 +3606,11 @@ bool Browser::HandleCrossAppNavigation(TabContents* source,
if (source_extension == destination_extension)
return false;
+ // If there is a source extension and the new URL is part of its browse
+ // extent, also do nothing.
+ if (source_extension && source_extension->browse_extent().ContainsURL(url))
+ return false;
+
if (destination_extension) {
// Search for an existing app window for this app.
for (BrowserList::const_iterator iter = BrowserList::begin();
diff --git a/chrome/browser/extensions/app_process_apitest.cc b/chrome/browser/extensions/app_process_apitest.cc
index 99bfa9d..7cdc80b 100644
--- a/chrome/browser/extensions/app_process_apitest.cc
+++ b/chrome/browser/extensions/app_process_apitest.cc
@@ -107,4 +107,16 @@ IN_PROC_BROWSER_TEST_F(AppApiTest, AppProcess) {
browser()->GetTabContentsAt(1)->render_view_host()->process());
EXPECT_EQ(host->render_process_host(),
browser()->GetTabContentsAt(3)->render_view_host()->process());
+
+ // Navigate the non-app tab into the browse extent. It should not enter the
+ // app process.
+ // Navigate the app tab into the browse extent. It should stay in the app
+ // process.
+ const GURL& browse_url(base_url.Resolve("path4/empty.html"));
+ NavigateTabHelper(browser()->GetTabContentsAt(1), browse_url);
+ NavigateTabHelper(browser()->GetTabContentsAt(3), browse_url);
+ EXPECT_NE(host->render_process_host(),
+ browser()->GetTabContentsAt(1)->render_view_host()->process());
+ EXPECT_EQ(host->render_process_host(),
+ browser()->GetTabContentsAt(3)->render_view_host()->process());
}
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
index 6518b6f..81c56f3 100644
--- a/chrome/browser/extensions/extensions_service.cc
+++ b/chrome/browser/extensions/extensions_service.cc
@@ -455,7 +455,7 @@ void ExtensionsService::LoadComponentExtensions() {
*static_cast<DictionaryValue*>(manifest.get()),
true, // require key
&error)) {
- NOTREACHED();
+ NOTREACHED() << error;
return;
}
diff --git a/chrome/browser/renderer_host/browser_render_process_host.cc b/chrome/browser/renderer_host/browser_render_process_host.cc
index 38b8b01..1afe34a 100644
--- a/chrome/browser/renderer_host/browser_render_process_host.cc
+++ b/chrome/browser/renderer_host/browser_render_process_host.cc
@@ -665,8 +665,11 @@ void BrowserRenderProcessHost::SendExtensionExtentsUpdate() {
for (size_t i = 0; i < service->extensions()->size(); ++i) {
Extension* extension = service->extensions()->at(i);
if (!extension->web_extent().is_empty()) {
- params.extension_apps.push_back(
- make_pair(extension->id(), extension->web_extent()));
+ ViewMsg_ExtensionExtentInfo info;
+ info.extension_id = extension->id();
+ info.web_extent = extension->web_extent();
+ info.browse_extent = extension->browse_extent();
+ params.extension_apps.push_back(info);
}
}
diff --git a/chrome/browser/resources/calendar_app/manifest.json b/chrome/browser/resources/calendar_app/manifest.json
index df388fa..8bcf843 100644
--- a/chrome/browser/resources/calendar_app/manifest.json
+++ b/chrome/browser/resources/calendar_app/manifest.json
@@ -13,6 +13,9 @@
"urls": [
"*://www.google.com/calendar/"
],
+ "browse_urls": [
+ "https://www.google.com/accounts/"
+ ],
"launch": {
"container": "tab",
"web_url": "https://www.google.com/calendar/"
diff --git a/chrome/browser/resources/docs_app/manifest.json b/chrome/browser/resources/docs_app/manifest.json
index 1df3c5c..10e965c 100644
--- a/chrome/browser/resources/docs_app/manifest.json
+++ b/chrome/browser/resources/docs_app/manifest.json
@@ -34,6 +34,9 @@
"*://spreadsheets8.google.com/",
"*://spreadsheets9.google.com/"
],
+ "browse_urls": [
+ "https://www.google.com/accounts/"
+ ],
"launch": {
"web_url": "https://docs.google.com/"
}
diff --git a/chrome/browser/resources/gmail_app/manifest.json b/chrome/browser/resources/gmail_app/manifest.json
index 4a90be2..9dfcf98 100644
--- a/chrome/browser/resources/gmail_app/manifest.json
+++ b/chrome/browser/resources/gmail_app/manifest.json
@@ -15,6 +15,9 @@
"*://gmail.com/",
"*://www.gmail.com/"
],
+ "browse_urls": [
+ "https://www.google.com/accounts/"
+ ],
"launch": {
"web_url": "https://mail.google.com/mail/"
}
diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc
index a1b5255..a164ea4 100644
--- a/chrome/common/extensions/extension.cc
+++ b/chrome/common/extensions/extension.cc
@@ -532,14 +532,18 @@ bool Extension::LoadIsApp(const DictionaryValue* manifest,
return true;
}
-bool Extension::LoadWebURLs(const DictionaryValue* manifest,
- std::string* error) {
+bool Extension::LoadExtent(const DictionaryValue* manifest,
+ const wchar_t* key,
+ ExtensionExtent* extent,
+ const char* list_error,
+ const char* value_error,
+ std::string* error) {
Value* temp = NULL;
- if (!manifest->Get(keys::kWebURLs, &temp))
+ if (!manifest->Get(key, &temp))
return true;
if (temp->GetType() != Value::TYPE_LIST) {
- *error = errors::kInvalidWebURLs;
+ *error = list_error;
return false;
}
@@ -547,28 +551,28 @@ bool Extension::LoadWebURLs(const DictionaryValue* manifest,
for (size_t i = 0; i < pattern_list->GetSize(); ++i) {
std::string pattern_string;
if (!pattern_list->GetString(i, &pattern_string)) {
- *error = ExtensionErrorUtils::FormatErrorMessage(
- errors::kInvalidWebURL, UintToString(i));
+ *error = ExtensionErrorUtils::FormatErrorMessage(value_error,
+ UintToString(i));
return false;
}
URLPattern pattern(kValidWebExtentSchemes);
if (!pattern.Parse(pattern_string)) {
- *error = ExtensionErrorUtils::FormatErrorMessage(
- errors::kInvalidWebURL, UintToString(i));
+ *error = ExtensionErrorUtils::FormatErrorMessage(value_error,
+ UintToString(i));
return false;
}
// We do not allow authors to put wildcards in their paths. Instead, we
// imply one at the end.
if (pattern.path().find('*') != std::string::npos) {
- *error = ExtensionErrorUtils::FormatErrorMessage(
- errors::kInvalidWebURL, UintToString(i));
+ *error = ExtensionErrorUtils::FormatErrorMessage(value_error,
+ UintToString(i));
return false;
}
pattern.set_path(pattern.path() + '*');
- web_extent_.AddPattern(pattern);
+ extent->AddPattern(pattern);
}
return true;
@@ -1466,7 +1470,11 @@ bool Extension::InitFromValue(const DictionaryValue& source, bool require_key,
}
if (!LoadIsApp(manifest_value_.get(), error) ||
- !LoadWebURLs(manifest_value_.get(), error) ||
+ !LoadExtent(manifest_value_.get(), keys::kWebURLs, &web_extent_,
+ errors::kInvalidWebURLs, errors::kInvalidWebURL, error) ||
+ !LoadExtent(manifest_value_.get(), keys::kBrowseURLs, &browse_extent_,
+ errors::kInvalidBrowseURLs, errors::kInvalidBrowseURL,
+ error) ||
!LoadLaunchURL(manifest_value_.get(), error) ||
!LoadLaunchContainer(manifest_value_.get(), error) ||
!LoadLaunchFullscreen(manifest_value_.get(), error)) {
diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h
index 15aca06..5dc807f 100644
--- a/chrome/common/extensions/extension.h
+++ b/chrome/common/extensions/extension.h
@@ -342,6 +342,7 @@ class Extension {
bool is_app() const { return is_app_; }
const ExtensionExtent& web_extent() const { return web_extent_; }
+ const ExtensionExtent& browse_extent() const { return browse_extent_; }
const std::string& launch_local_path() const { return launch_local_path_; }
const std::string& launch_web_url() const { return launch_web_url_; }
LaunchContainer launch_container() const { return launch_container_; }
@@ -409,7 +410,9 @@ class Extension {
// Helpers to load various chunks of the manifest.
bool LoadIsApp(const DictionaryValue* manifest, std::string* error);
- bool LoadWebURLs(const DictionaryValue* manifest, std::string* error);
+ bool LoadExtent(const DictionaryValue* manifest, const wchar_t* key,
+ ExtensionExtent* extent, const char* list_error,
+ const char* value_error, std::string* error);
bool LoadLaunchContainer(const DictionaryValue* manifest, std::string* error);
bool LoadLaunchFullscreen(const DictionaryValue* manifest,
std::string* error);
@@ -534,6 +537,11 @@ class Extension {
// Defines the set of URLs in the extension's web content.
ExtensionExtent web_extent_;
+ // Defines an extra set of URLs beyond web_extent_ which will stay in the app
+ // if browsed to from a page that is already in the app, but which will not
+ // launch the app if browse to from outside.
+ ExtensionExtent browse_extent_;
+
// The local path inside the extension to use with the launcher.
std::string launch_local_path_;
diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc
index e4f368a..724198a 100644
--- a/chrome/common/extensions/extension_constants.cc
+++ b/chrome/common/extensions/extension_constants.cc
@@ -9,6 +9,7 @@ namespace extension_manifest_keys {
const wchar_t* kAllFrames = L"all_frames";
const wchar_t* kApp = L"app";
const wchar_t* kBackground = L"background_page";
+const wchar_t* kBrowseURLs = L"app.browse_urls";
const wchar_t* kBrowserAction = L"browser_action";
const wchar_t* kChromeURLOverrides = L"chrome_url_overrides";
const wchar_t* kContentScripts = L"content_scripts";
@@ -86,6 +87,10 @@ const char* kChromeVersionTooLow =
"This extension requires * version * or greater.";
const char* kInvalidAllFrames =
"Invalid value for 'content_scripts[*].all_frames'.";
+const char* kInvalidBrowseURL =
+ "Invalid value for 'app.browse_urls[*]'.";
+const char* kInvalidBrowseURLs =
+ "Invalid value for 'app.browse_urls'.";
const char* kInvalidBrowserAction =
"Invalid value for 'browser_action'.";
const char* kInvalidChromeURLOverrides =
diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h
index a2a972b..cc2a017 100644
--- a/chrome/common/extensions/extension_constants.h
+++ b/chrome/common/extensions/extension_constants.h
@@ -11,6 +11,7 @@ namespace extension_manifest_keys {
extern const wchar_t* kApp;
extern const wchar_t* kBackground;
extern const wchar_t* kBrowserAction;
+ extern const wchar_t* kBrowseURLs;
extern const wchar_t* kMinimumChromeVersion;
extern const wchar_t* kChromeURLOverrides;
extern const wchar_t* kContentScripts;
@@ -85,6 +86,8 @@ namespace extension_manifest_errors {
extern const char* kChromeVersionTooLow;
extern const char* kInvalidAllFrames;
extern const char* kInvalidBackground;
+ extern const char* kInvalidBrowseURL;
+ extern const char* kInvalidBrowseURLs;
extern const char* kInvalidBrowserAction;
extern const char* kInvalidChromeURLOverrides;
extern const char* kInvalidContentScript;
diff --git a/chrome/common/extensions/extension_manifests_unittest.cc b/chrome/common/extensions/extension_manifests_unittest.cc
index 86a6bb1..8e6402c 100644
--- a/chrome/common/extensions/extension_manifests_unittest.cc
+++ b/chrome/common/extensions/extension_manifests_unittest.cc
@@ -117,6 +117,30 @@ TEST_F(ManifestTest, AppWebUrls) {
extension->web_extent().patterns()[0].GetAsString());
}
+TEST_F(ManifestTest, AppBrowseUrls) {
+ LoadAndExpectError("browse_urls_wrong_type.json",
+ errors::kInvalidBrowseURLs);
+ LoadAndExpectError("browse_urls_invalid_1.json",
+ ExtensionErrorUtils::FormatErrorMessage(
+ errors::kInvalidBrowseURL, "0"));
+ LoadAndExpectError("browse_urls_invalid_2.json",
+ ExtensionErrorUtils::FormatErrorMessage(
+ errors::kInvalidBrowseURL, "0"));
+ LoadAndExpectError("browse_urls_invalid_3.json",
+ ExtensionErrorUtils::FormatErrorMessage(
+ errors::kInvalidBrowseURL, "0"));
+
+ scoped_ptr<Extension> extension(
+ LoadAndExpectSuccess("browse_urls_default.json"));
+ EXPECT_EQ(0u, extension->browse_extent().patterns().size());
+
+ extension.reset(
+ LoadAndExpectSuccess("browse_urls_valid.json"));
+ ASSERT_EQ(1u, extension->browse_extent().patterns().size());
+ EXPECT_EQ("https://www.google.com/accounts/*",
+ extension->browse_extent().patterns()[0].GetAsString());
+}
+
TEST_F(ManifestTest, AppLaunchContainer) {
scoped_ptr<Extension> extension;
diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h
index 3274a02..618b5e7 100644
--- a/chrome/common/render_messages.h
+++ b/chrome/common/render_messages.h
@@ -716,10 +716,15 @@ struct ViewHostMsg_RunFileChooser_Params {
FilePath default_file_name;
};
+struct ViewMsg_ExtensionExtentInfo {
+ std::string extension_id;
+ ExtensionExtent web_extent;
+ ExtensionExtent browse_extent;
+};
+
struct ViewMsg_ExtensionExtentsUpdated_Params {
- // A list of (extension_id, web_extent) pairs that describe the installed
- // extension apps and the URLs they cover.
- std::vector< std::pair<std::string, ExtensionExtent> > extension_apps;
+ // Describes the installed extension apps and the URLs they cover.
+ std::vector<ViewMsg_ExtensionExtentInfo> extension_apps;
};
// Values that may be OR'd together to form the 'flags' parameter of the
@@ -2740,6 +2745,24 @@ struct ParamTraits<ExtensionExtent> {
};
template <>
+struct ParamTraits<ViewMsg_ExtensionExtentInfo> {
+ typedef ViewMsg_ExtensionExtentInfo param_type;
+ static void Write(Message* m, const param_type& p) {
+ WriteParam(m, p.extension_id);
+ WriteParam(m, p.web_extent);
+ WriteParam(m, p.browse_extent);
+ }
+ static bool Read(const Message* m, void** iter, param_type* p) {
+ return ReadParam(m, iter, &p->extension_id) &&
+ ReadParam(m, iter, &p->web_extent) &&
+ ReadParam(m, iter, &p->browse_extent);
+ }
+ static void Log(const param_type& p, std::wstring* l) {
+ LogParam(p.extension_id, l);
+ }
+};
+
+template <>
struct ParamTraits<ViewMsg_ExtensionExtentsUpdated_Params> {
typedef ViewMsg_ExtensionExtentsUpdated_Params param_type;
static void Write(Message* m, const param_type& p) {
diff --git a/chrome/renderer/render_thread.cc b/chrome/renderer/render_thread.cc
index 87155c3b..d0d9eed 100644
--- a/chrome/renderer/render_thread.cc
+++ b/chrome/renderer/render_thread.cc
@@ -484,8 +484,10 @@ void RenderThread::OnExtensionExtentsUpdated(
const ViewMsg_ExtensionExtentsUpdated_Params& params) {
extension_extents_.resize(params.extension_apps.size());
for (size_t i = 0; i < params.extension_apps.size(); ++i) {
- extension_extents_[i].extension_id = params.extension_apps[i].first;
- extension_extents_[i].web_extent = params.extension_apps[i].second;
+ extension_extents_[i].extension_id = params.extension_apps[i].extension_id;
+ extension_extents_[i].web_extent = params.extension_apps[i].web_extent;
+ extension_extents_[i].browse_extent =
+ params.extension_apps[i].browse_extent;
}
}
@@ -1040,7 +1042,7 @@ void RenderThread::OnGpuChannelEstablished(
}
}
-std::string RenderThread::GetExtensionIdForURL(const GURL& url) {
+std::string RenderThread::GetExtensionIdByURL(const GURL& url) {
if (url.SchemeIs(chrome::kExtensionScheme))
return url.host();
@@ -1051,3 +1053,12 @@ std::string RenderThread::GetExtensionIdForURL(const GURL& url) {
return std::string();
}
+
+std::string RenderThread::GetExtensionIdByBrowseExtent(const GURL& url) {
+ for (size_t i = 0; i < extension_extents_.size(); ++i) {
+ if (extension_extents_[i].browse_extent.ContainsURL(url))
+ return extension_extents_[i].extension_id;
+ }
+
+ return std::string();
+}
diff --git a/chrome/renderer/render_thread.h b/chrome/renderer/render_thread.h
index 5b8d024..03a865b 100644
--- a/chrome/renderer/render_thread.h
+++ b/chrome/renderer/render_thread.h
@@ -220,7 +220,9 @@ class RenderThread : public RenderThreadBase,
// none. This includes web URLs that are part of an extension's web extent.
// TODO(mpcomplete): this doesn't feel like it belongs here. Find a better
// place.
- std::string GetExtensionIdForURL(const GURL& url);
+ std::string GetExtensionIdByURL(const GURL& url);
+
+ std::string GetExtensionIdByBrowseExtent(const GURL& url);
private:
// Contains extension-related data that the renderer needs to know about.
@@ -229,6 +231,7 @@ class RenderThread : public RenderThreadBase,
struct ExtensionInfo {
std::string extension_id;
ExtensionExtent web_extent;
+ ExtensionExtent browse_extent;
};
virtual void OnControlMessageReceived(const IPC::Message& msg);
diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc
index 59ec64a..d66d791 100644
--- a/chrome/renderer/render_view.cc
+++ b/chrome/renderer/render_view.cc
@@ -344,10 +344,17 @@ static bool CrossesExtensionExtents(WebFrame* frame, const GURL& new_url) {
old_url = frame->opener()->url();
std::string old_extension =
- RenderThread::current()->GetExtensionIdForURL(old_url);
+ RenderThread::current()->GetExtensionIdByURL(old_url);
+ if (!old_extension.empty()) {
+ if (RenderThread::current()->GetExtensionIdByBrowseExtent(new_url) ==
+ old_extension) {
+ return false;
+ }
+ }
+
std::string new_extension =
- RenderThread::current()->GetExtensionIdForURL(new_url);
- return (old_extension != new_extension);
+ RenderThread::current()->GetExtensionIdByURL(new_url);
+ return old_extension != new_extension;
}
// Returns the ISO 639_1 language code of the specified |text|, or 'unknown'
diff --git a/chrome/test/data/extensions/api_test/app_process/manifest.json b/chrome/test/data/extensions/api_test/app_process/manifest.json
index eb41ee5..67b54e0 100644
--- a/chrome/test/data/extensions/api_test/app_process/manifest.json
+++ b/chrome/test/data/extensions/api_test/app_process/manifest.json
@@ -9,6 +9,9 @@
"http://localhost/files/extensions/api_test/app_process/path1",
"http://localhost/files/extensions/api_test/app_process/path2"
],
+ "browse_urls": [
+ "http://localhost/files/extensions/api_test/app_process/path4"
+ ],
"launch": {
"web_url": "http://localhost:1337/files/extensions/api_test/app_process/path1/foo.html"
}
diff --git a/chrome/test/data/extensions/api_test/app_process/path4/empty.html b/chrome/test/data/extensions/api_test/app_process/path4/empty.html
new file mode 100644
index 0000000..fdc6289
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/app_process/path4/empty.html
@@ -0,0 +1,2 @@
+<title>Unmodified</title>
+
diff --git a/chrome/test/data/extensions/manifest_tests/browse_urls_default.json b/chrome/test/data/extensions/manifest_tests/browse_urls_default.json
new file mode 100644
index 0000000..b882e8b
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/browse_urls_default.json
@@ -0,0 +1,13 @@
+{
+ "name": "test",
+ "version": "1",
+ "app": {
+ "launch": {
+ "container": "window",
+ "web_url": "http://www.google.com/mail/"
+ }
+ },
+ "permissions": [
+ "notifications"
+ ]
+}
diff --git a/chrome/test/data/extensions/manifest_tests/browse_urls_invalid_1.json b/chrome/test/data/extensions/manifest_tests/browse_urls_invalid_1.json
new file mode 100644
index 0000000..bb6bffdd
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/browse_urls_invalid_1.json
@@ -0,0 +1,12 @@
+{
+ "name": "test",
+ "version": "1",
+ "app": {
+ "browse_urls": [
+ 42
+ ],
+ "launch": {
+ "web_url": "http://www.google.com/foo.html"
+ }
+ }
+}
diff --git a/chrome/test/data/extensions/manifest_tests/browse_urls_invalid_2.json b/chrome/test/data/extensions/manifest_tests/browse_urls_invalid_2.json
new file mode 100644
index 0000000..018cd44
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/browse_urls_invalid_2.json
@@ -0,0 +1,12 @@
+{
+ "name": "test",
+ "version": "1",
+ "app": {
+ "browse_urls": [
+ "monkey"
+ ],
+ "launch": {
+ "web_url": "http://www.google.com/foo.html"
+ }
+ }
+}
diff --git a/chrome/test/data/extensions/manifest_tests/browse_urls_invalid_3.json b/chrome/test/data/extensions/manifest_tests/browse_urls_invalid_3.json
new file mode 100644
index 0000000..1ea8732
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/browse_urls_invalid_3.json
@@ -0,0 +1,12 @@
+{
+ "name": "test",
+ "version": "1",
+ "app": {
+ "browse_urls": [
+ "http://www.google.com/mon*key"
+ ],
+ "launch": {
+ "web_url": "http://www.google.com/foo.html"
+ }
+ }
+}
diff --git a/chrome/test/data/extensions/manifest_tests/browse_urls_valid.json b/chrome/test/data/extensions/manifest_tests/browse_urls_valid.json
new file mode 100644
index 0000000..5a1579b
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/browse_urls_valid.json
@@ -0,0 +1,16 @@
+{
+ "name": "test",
+ "version": "1",
+ "app": {
+ "launch": {
+ "container": "window",
+ "web_url": "http://www.google.com/mail/"
+ },
+ "browse_urls": [
+ "https://www.google.com/accounts/"
+ ]
+ },
+ "permissions": [
+ "notifications"
+ ]
+}
diff --git a/chrome/test/data/extensions/manifest_tests/browse_urls_wrong_type.json b/chrome/test/data/extensions/manifest_tests/browse_urls_wrong_type.json
new file mode 100644
index 0000000..223be9c
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/browse_urls_wrong_type.json
@@ -0,0 +1,10 @@
+{
+ "name": "test",
+ "version": "1",
+ "app": {
+ "browse_urls": 42,
+ "launch": {
+ "web_url": "http://www.google.com/foo.html"
+ }
+ }
+}