summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/app/generated_resources.grd6
-rw-r--r--chrome/browser/about_flags.cc7
-rw-r--r--chrome/browser/automation/automation_provider.cc10
-rw-r--r--chrome/browser/download/download_util.cc4
-rw-r--r--chrome/browser/extensions/convert_web_app.cc2
-rw-r--r--chrome/browser/extensions/convert_web_app_browsertest.cc68
-rw-r--r--chrome/browser/extensions/convert_web_app_unittest.cc2
-rw-r--r--chrome/browser/extensions/crx_installer.cc34
-rw-r--r--chrome/browser/extensions/crx_installer.h23
-rw-r--r--chrome/browser/extensions/crx_installer_browsertest.cc4
-rw-r--r--chrome/browser/extensions/extension_browsertest.cc2
-rw-r--r--chrome/browser/extensions/extensions_service.cc9
-rw-r--r--chrome/browser/extensions/extensions_service_unittest.cc4
-rw-r--r--chrome/browser/renderer_host/browser_render_process_host.cc3
-rw-r--r--chrome/browser/renderer_host/render_view_host.cc10
-rw-r--r--chrome/browser/renderer_host/render_view_host.h1
-rw-r--r--chrome/browser/renderer_host/render_view_host_delegate.h4
-rw-r--r--chrome/browser/tab_contents/tab_contents.cc5
-rw-r--r--chrome/browser/tab_contents/tab_contents.h1
-rw-r--r--chrome/browser/tab_contents/tab_contents_delegate.cc6
-rw-r--r--chrome/browser/tab_contents/tab_contents_delegate.h5
-rw-r--r--chrome/browser/ui/browser.cc15
-rw-r--r--chrome/browser/ui/browser.h3
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/chrome_switches.cc3
-rw-r--r--chrome/common/chrome_switches.h1
-rw-r--r--chrome/common/common_param_traits.cc4
-rw-r--r--chrome/common/render_messages_internal.h4
-rw-r--r--chrome/common/web_apps.cc23
-rw-r--r--chrome/common/web_apps.h4
-rw-r--r--chrome/common/web_apps_unittest.cc5
-rw-r--r--chrome/renderer/extensions/chrome_app_bindings.cc23
-rw-r--r--chrome/renderer/render_view.cc183
-rw-r--r--chrome/renderer/render_view.h44
-rw-r--r--chrome/test/data/extensions/convert_web_app/application.html10
-rw-r--r--chrome/test/data/extensions/convert_web_app/application_definition.json9
36 files changed, 469 insertions, 73 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 0cd1b57..1106d0b 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -4107,6 +4107,12 @@ Keep your key file in a safe place. You will need it to create new versions of y
<message name="IDS_FLAGS_CLOUD_PRINT_PROXY_DESCRIPTION" desc="Description of the Cloud Print Proxy lab">
Enables a background service that connects the <ph name="CLOUD_PRINT_NAME">Google Cloud Print</ph> service to any printers installed on this computer. Once this lab is enabled, you can turn <ph name="CLOUD_PRINT_NAME">Google Cloud Print</ph> on by logging in with your Google account in the Options/Preferences in the Under the Hood section.
</message>
+ <message name="IDS_FLAGS_CRXLESS_WEB_APPS_NAME" desc="Title of the CRX-less web apps lab">
+ CRX-less Web Apps
+ </message>
+ <message name="IDS_FLAGS_CRXLESS_WEB_APPS_DESCRIPTION" desc="Description of the CRX-less web apps lab">
+ Enables support for installing Chrome apps that are deployed using a manifest file on a web page, rather than by packaging the manifest and icons into a crx file.
+ </message>
<message name="IDS_FLAGS_CONFLICTS_CHECK_NAME" desc="Title of the run conflicts check flag">
Check for known conflicts with 3rd party modules.
</message>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index bb52310..5f24585 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -154,6 +154,13 @@ const Experiment kExperiments[] = {
switches::kEnableCloudPrintProxy
},
{
+ "crxless-web-apps",
+ IDS_FLAGS_CRXLESS_WEB_APPS_NAME,
+ IDS_FLAGS_CRXLESS_WEB_APPS_DESCRIPTION,
+ kOsAll,
+ switches::kEnableCrxlessWebApps
+ },
+ {
"match-preview", // FLAGS:RECORD_UMA
IDS_FLAGS_PREDICTIVE_INSTANT_NAME,
IDS_FLAGS_PREDICTIVE_INSTANT_DESCRIPTION,
diff --git a/chrome/browser/automation/automation_provider.cc b/chrome/browser/automation/automation_provider.cc
index 65dc5a3..bbfb3c5c 100644
--- a/chrome/browser/automation/automation_provider.cc
+++ b/chrome/browser/automation/automation_provider.cc
@@ -797,11 +797,8 @@ void AutomationProvider::InstallExtension(const FilePath& crx_path,
AutomationMsg_InstallExtension::ID,
reply_message);
- const FilePath& install_dir = service->install_directory();
scoped_refptr<CrxInstaller> installer(
- new CrxInstaller(install_dir,
- service,
- NULL)); // silent install, no UI
+ new CrxInstaller(service, NULL)); // silent install, no UI
installer->set_allow_privilege_increase(true);
installer->InstallCrx(crx_path);
} else {
@@ -870,10 +867,7 @@ void AutomationProvider::InstallExtensionAndGetHandle(
ExtensionInstallUI* client =
(with_ui ? new ExtensionInstallUI(profile_) : NULL);
- scoped_refptr<CrxInstaller> installer(
- new CrxInstaller(service->install_directory(),
- service,
- client));
+ scoped_refptr<CrxInstaller> installer(new CrxInstaller(service, client));
installer->set_allow_privilege_increase(true);
installer->InstallCrx(crx_path);
} else {
diff --git a/chrome/browser/download/download_util.cc b/chrome/browser/download/download_util.cc
index 2c856be..dd4a34c 100644
--- a/chrome/browser/download/download_util.cc
+++ b/chrome/browser/download/download_util.cc
@@ -239,9 +239,7 @@ void OpenChromeExtension(Profile* profile,
Details<GURL>(&nonconst_download_url));
scoped_refptr<CrxInstaller> installer(
- new CrxInstaller(service->install_directory(),
- service,
- new ExtensionInstallUI(profile)));
+ new CrxInstaller(service, new ExtensionInstallUI(profile)));
installer->set_delete_source(true);
if (UserScript::HasUserScriptFileExtension(download_item.url())) {
diff --git a/chrome/browser/extensions/convert_web_app.cc b/chrome/browser/extensions/convert_web_app.cc
index 47a9143..11303ff 100644
--- a/chrome/browser/extensions/convert_web_app.cc
+++ b/chrome/browser/extensions/convert_web_app.cc
@@ -34,7 +34,7 @@ using base::Time;
namespace {
-const char kIconsDirName[] = "_icons";
+const char kIconsDirName[] = "icons";
// Create the public key for the converted web app.
//
diff --git a/chrome/browser/extensions/convert_web_app_browsertest.cc b/chrome/browser/extensions/convert_web_app_browsertest.cc
new file mode 100644
index 0000000..a6cdb19
--- /dev/null
+++ b/chrome/browser/extensions/convert_web_app_browsertest.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "chrome/browser/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/notification_details.h"
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/notification_type.h"
+#include "chrome/test/in_process_browser_test.h"
+#include "chrome/test/ui_test_utils.h"
+
+class ExtensionFromWebAppTest
+ : public InProcessBrowserTest, public NotificationObserver {
+ protected:
+ ExtensionFromWebAppTest() : installed_extension_(NULL) {
+ }
+
+ std::string expected_extension_id_;
+ const Extension* installed_extension_;
+
+ private:
+ // InProcessBrowserTest
+ virtual void SetUpCommandLine(CommandLine* command_line) {
+ command_line->AppendSwitch(switches::kEnableCrxlessWebApps);
+ }
+
+ // NotificationObserver
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ if (type == NotificationType::EXTENSION_INSTALLED) {
+ const Extension* extension = Details<const Extension>(details).ptr();
+ if (extension->id() == expected_extension_id_) {
+ installed_extension_ = extension;
+ MessageLoopForUI::current()->Quit();
+ }
+ }
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(ExtensionFromWebAppTest, Basic) {
+ ASSERT_TRUE(test_server()->Start());
+ browser()->profile()->GetExtensionsService()->set_show_extensions_prompts(
+ false);
+
+ NotificationRegistrar registrar;
+ registrar.Add(this, NotificationType::EXTENSION_INSTALLED,
+ NotificationService::AllSources());
+
+ expected_extension_id_ = "fnpgoaochgbdfjndakichfafiocjjpmm";
+ ui_test_utils::NavigateToURL(
+ browser(),
+ test_server()->GetURL(
+ "files/extensions/convert_web_app/application.html"));
+
+ if (!installed_extension_)
+ ui_test_utils::RunMessageLoop();
+
+ EXPECT_TRUE(installed_extension_);
+ EXPECT_TRUE(installed_extension_->is_hosted_app());
+}
diff --git a/chrome/browser/extensions/convert_web_app_unittest.cc b/chrome/browser/extensions/convert_web_app_unittest.cc
index fc10347..eab9b8f 100644
--- a/chrome/browser/extensions/convert_web_app_unittest.cc
+++ b/chrome/browser/extensions/convert_web_app_unittest.cc
@@ -132,7 +132,7 @@ TEST(ExtensionFromWebApp, Basic) {
EXPECT_EQ(web_app.icons.size(), extension->icons().map().size());
for (size_t i = 0; i < web_app.icons.size(); ++i) {
- EXPECT_EQ(StringPrintf("_icons/%i.png", web_app.icons[i].width),
+ EXPECT_EQ(StringPrintf("icons/%i.png", web_app.icons[i].width),
extension->icons().Get(web_app.icons[i].width,
ExtensionIconSet::MATCH_EXACTLY));
ExtensionResource resource = extension->GetIconResource(
diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc
index d90bbbd..3a9a3df 100644
--- a/chrome/browser/extensions/crx_installer.cc
+++ b/chrome/browser/extensions/crx_installer.cc
@@ -14,6 +14,7 @@
#include "base/singleton.h"
#include "base/stl_util-inl.h"
#include "base/stringprintf.h"
+#include "base/time.h"
#include "base/task.h"
#include "base/thread_restrictions.h"
#include "base/utf_string_conversions.h"
@@ -21,6 +22,7 @@
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_thread.h"
#include "chrome/browser/extensions/convert_user_script.h"
+#include "chrome/browser/extensions/convert_web_app.h"
#include "chrome/browser/extensions/extensions_service.h"
#include "chrome/browser/extensions/extension_error_reporter.h"
#include "chrome/browser/profile.h"
@@ -75,11 +77,11 @@ bool CrxInstaller::ClearWhitelistedInstallId(const std::string& id) {
return false;
}
-CrxInstaller::CrxInstaller(const FilePath& install_directory,
- ExtensionsService* frontend,
+CrxInstaller::CrxInstaller(ExtensionsService* frontend,
ExtensionInstallUI* client)
- : install_directory_(install_directory),
+ : install_directory_(frontend->install_directory()),
install_source_(Extension::INTERNAL),
+ extensions_enabled_(frontend->extensions_enabled()),
delete_source_(false),
allow_privilege_increase_(false),
is_gallery_install_(false),
@@ -88,7 +90,6 @@ CrxInstaller::CrxInstaller(const FilePath& install_directory,
client_(client),
apps_require_extension_mime_type_(false),
allow_silent_install_(false) {
- extensions_enabled_ = frontend_->extensions_enabled();
}
CrxInstaller::~CrxInstaller() {
@@ -160,6 +161,29 @@ void CrxInstaller::ConvertUserScriptOnFileThread() {
OnUnpackSuccess(extension->path(), extension->path(), extension);
}
+void CrxInstaller::InstallWebApp(const WebApplicationInfo& web_app) {
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ NewRunnableMethod(this, &CrxInstaller::ConvertWebAppOnFileThread,
+ web_app));
+}
+
+void CrxInstaller::ConvertWebAppOnFileThread(
+ const WebApplicationInfo& web_app) {
+ std::string error;
+ scoped_refptr<Extension> extension(
+ ConvertWebAppToExtension(web_app, base::Time::Now()));
+ if (!extension) {
+ // Validation should have stopped any potential errors before getting here.
+ NOTREACHED() << "Could not convert web app to extension.";
+ return;
+ }
+
+ // TODO(aa): conversion data gets lost here :(
+
+ OnUnpackSuccess(extension->path(), extension->path(), extension);
+}
+
bool CrxInstaller::AllowInstall(const Extension* extension,
std::string* error) {
DCHECK(error);
@@ -362,7 +386,7 @@ void CrxInstaller::CompleteInstall() {
std::string error;
extension_ = extension_file_util::LoadExtension(
version_dir, install_source_, true, &error);
- DCHECK(error.empty());
+ CHECK(error.empty()) << error;
ReportSuccessFromFileThread();
}
diff --git a/chrome/browser/extensions/crx_installer.h b/chrome/browser/extensions/crx_installer.h
index 5426b1a..26bbbd8 100644
--- a/chrome/browser/extensions/crx_installer.h
+++ b/chrome/browser/extensions/crx_installer.h
@@ -13,6 +13,7 @@
#include "chrome/browser/extensions/extension_install_ui.h"
#include "chrome/browser/extensions/sandboxed_extension_unpacker.h"
#include "chrome/common/extensions/extension.h"
+#include "chrome/common/web_apps.h"
class ExtensionsService;
class SkBitmap;
@@ -60,22 +61,23 @@ class CrxInstaller
// only be called on the UI thread.
static bool ClearWhitelistedInstallId(const std::string& id);
- // Constructor. Extensions will be unpacked to |install_directory|.
- // Extension objects will be sent to |frontend|, and any UI will be shown
- // via |client|. For silent install, pass NULL for |client|.
- CrxInstaller(const FilePath& install_directory,
- ExtensionsService* frontend,
+ // Constructor. Extensions will be installed into
+ // frontend->install_directory() then registered with |frontend|. Any install
+ // UI will be displayed using |client|. Pass NULL for |client| for silent
+ // install.
+ CrxInstaller(ExtensionsService* frontend,
ExtensionInstallUI* client);
- // Install the crx in |source_file|. Note that this will most likely
- // complete asynchronously.
+ // Install the crx in |source_file|.
void InstallCrx(const FilePath& source_file);
- // Install the user script in |source_file|. Note that this will most likely
- // complete asynchronously.
+ // Convert the specified user script into an extension and install it.
void InstallUserScript(const FilePath& source_file,
const GURL& original_url);
+ // Convert the specified web app into an extension and install it.
+ void InstallWebApp(const WebApplicationInfo& web_app);
+
// Overridden from ExtensionInstallUI::Delegate:
virtual void InstallUIProceed();
virtual void InstallUIAbort();
@@ -122,6 +124,9 @@ class CrxInstaller
// Converts the source user script to an extension.
void ConvertUserScriptOnFileThread();
+ // Converts the source web app to an extension.
+ void ConvertWebAppOnFileThread(const WebApplicationInfo& web_app);
+
// Called after OnUnpackSuccess as a last check to see whether the install
// should complete.
bool AllowInstall(const Extension* extension, std::string* error);
diff --git a/chrome/browser/extensions/crx_installer_browsertest.cc b/chrome/browser/extensions/crx_installer_browsertest.cc
index c4adb21..64fee97 100644
--- a/chrome/browser/extensions/crx_installer_browsertest.cc
+++ b/chrome/browser/extensions/crx_installer_browsertest.cc
@@ -50,9 +50,7 @@ class ExtensionCrxInstallerTest : public ExtensionBrowserTest {
MockInstallUI* mock_install_ui = new MockInstallUI(browser()->profile());
scoped_refptr<CrxInstaller> installer(
- new CrxInstaller(service->install_directory(),
- service,
- mock_install_ui /* ownership transferred */));
+ new CrxInstaller(service, mock_install_ui /* ownership transferred */));
installer->set_allow_silent_install(true);
installer->set_is_gallery_install(true);
diff --git a/chrome/browser/extensions/extension_browsertest.cc b/chrome/browser/extensions/extension_browsertest.cc
index bea1c5e..ae90a66 100644
--- a/chrome/browser/extensions/extension_browsertest.cc
+++ b/chrome/browser/extensions/extension_browsertest.cc
@@ -144,7 +144,7 @@ bool ExtensionBrowserTest::InstallOrUpdateExtension(const std::string& id,
install_ui = new ExtensionInstallUI(browser()->profile());
scoped_refptr<CrxInstaller> installer(
- new CrxInstaller(service->install_directory(), service, install_ui));
+ new CrxInstaller(service, install_ui));
installer->set_expected_id(id);
installer->InstallCrx(path);
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
index 3357e45..627fe39 100644
--- a/chrome/browser/extensions/extensions_service.cc
+++ b/chrome/browser/extensions/extensions_service.cc
@@ -642,8 +642,7 @@ void ExtensionsService::Init() {
void ExtensionsService::InstallExtension(const FilePath& extension_path) {
scoped_refptr<CrxInstaller> installer(
- new CrxInstaller(install_directory_,
- this, // frontend
+ new CrxInstaller(this, // frontend
NULL)); // no client (silent install)
installer->set_allow_privilege_increase(true);
installer->InstallCrx(extension_path);
@@ -685,8 +684,7 @@ void ExtensionsService::UpdateExtension(const std::string& id,
NULL : new ExtensionInstallUI(profile_);
scoped_refptr<CrxInstaller> installer(
- new CrxInstaller(install_directory_,
- this, // frontend
+ new CrxInstaller(this, // frontend
client));
installer->set_expected_id(id);
if (is_pending_extension)
@@ -1879,8 +1877,7 @@ void ExtensionsService::OnExternalExtensionFileFound(
}
scoped_refptr<CrxInstaller> installer(
- new CrxInstaller(install_directory_,
- this, // frontend
+ new CrxInstaller(this, // frontend
NULL)); // no client (silent install)
installer->set_install_source(location);
installer->set_expected_id(id);
diff --git a/chrome/browser/extensions/extensions_service_unittest.cc b/chrome/browser/extensions/extensions_service_unittest.cc
index 54aaf78..e222c27 100644
--- a/chrome/browser/extensions/extensions_service_unittest.cc
+++ b/chrome/browser/extensions/extensions_service_unittest.cc
@@ -988,9 +988,7 @@ TEST_F(ExtensionsServiceTest, InstallUserScript) {
ASSERT_TRUE(file_util::PathExists(path));
scoped_refptr<CrxInstaller> installer(
- new CrxInstaller(service_->install_directory(),
- service_,
- NULL)); // silent install
+ new CrxInstaller(service_, NULL)); // silent install
installer->InstallUserScript(
path,
GURL("http://www.aaronboodman.com/scripts/user_script_basic.user.js"));
diff --git a/chrome/browser/renderer_host/browser_render_process_host.cc b/chrome/browser/renderer_host/browser_render_process_host.cc
index ed68d1e..07391cb 100644
--- a/chrome/browser/renderer_host/browser_render_process_host.cc
+++ b/chrome/browser/renderer_host/browser_render_process_host.cc
@@ -629,7 +629,8 @@ void BrowserRenderProcessHost::PropagateBrowserCommandLineToRenderer(
switches::kDisableFileSystem,
switches::kPpapiOutOfProcess,
switches::kEnablePrintPreview,
- switches::kEnableClientSidePhishingDetection
+ switches::kEnableClientSidePhishingDetection,
+ switches::kEnableCrxlessWebApps
};
renderer_cmd->CopySwitchesFrom(browser_cmd, kSwitchNames,
arraysize(kSwitchNames));
diff --git a/chrome/browser/renderer_host/render_view_host.cc b/chrome/browser/renderer_host/render_view_host.cc
index 9d701fc..9c1757d 100644
--- a/chrome/browser/renderer_host/render_view_host.cc
+++ b/chrome/browser/renderer_host/render_view_host.cc
@@ -852,6 +852,8 @@ void RenderViewHost::OnMessageReceived(const IPC::Message& msg) {
OnReceivedSerializedHtmlData);
IPC_MESSAGE_HANDLER(ViewHostMsg_DidGetApplicationInfo,
OnDidGetApplicationInfo);
+ IPC_MESSAGE_HANDLER(ViewHostMsg_InstallApplication,
+ OnInstallApplication);
IPC_MESSAGE_FORWARD(ViewHostMsg_JSOutOfMemory, delegate_,
RenderViewHostDelegate::OnJSOutOfMemory);
IPC_MESSAGE_HANDLER(ViewHostMsg_ShouldClose_ACK, OnMsgShouldCloseACK);
@@ -1656,6 +1658,14 @@ void RenderViewHost::OnDidGetApplicationInfo(
integration_delegate->OnDidGetApplicationInfo(page_id, info);
}
+void RenderViewHost::OnInstallApplication(
+ const WebApplicationInfo& info) {
+ RenderViewHostDelegate::BrowserIntegration* integration_delegate =
+ delegate_->GetBrowserIntegrationDelegate();
+ if (integration_delegate)
+ integration_delegate->OnInstallApplication(info);
+}
+
void RenderViewHost::GetSerializedHtmlDataForCurrentPageWithLocalLinks(
const std::vector<GURL>& links,
const std::vector<FilePath>& local_paths,
diff --git a/chrome/browser/renderer_host/render_view_host.h b/chrome/browser/renderer_host/render_view_host.h
index 077f7ea..19b2b17 100644
--- a/chrome/browser/renderer_host/render_view_host.h
+++ b/chrome/browser/renderer_host/render_view_host.h
@@ -670,6 +670,7 @@ class RenderViewHost : public RenderWidgetHost {
int32 status);
void OnDidGetApplicationInfo(int32 page_id, const WebApplicationInfo& info);
+ void OnInstallApplication(const WebApplicationInfo& info);
void OnMsgShouldCloseACK(bool proceed);
void OnQueryFormFieldAutoFill(int request_id,
bool form_autofilled,
diff --git a/chrome/browser/renderer_host/render_view_host_delegate.h b/chrome/browser/renderer_host/render_view_host_delegate.h
index 9bde9cd..a569480 100644
--- a/chrome/browser/renderer_host/render_view_host_delegate.h
+++ b/chrome/browser/renderer_host/render_view_host_delegate.h
@@ -290,6 +290,10 @@ class RenderViewHostDelegate {
int32 page_id,
const WebApplicationInfo& app_info) = 0;
+ // Notification when an application programmatically requests installation.
+ virtual void OnInstallApplication(
+ const WebApplicationInfo& app_info) = 0;
+
// Notification that the contents of the page has been loaded.
virtual void OnPageContents(const GURL& url,
int renderer_process_id,
diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc
index 3631575..c846d62 100644
--- a/chrome/browser/tab_contents/tab_contents.cc
+++ b/chrome/browser/tab_contents/tab_contents.cc
@@ -2061,6 +2061,11 @@ void TabContents::OnDidGetApplicationInfo(int32 page_id,
delegate()->OnDidGetApplicationInfo(this, page_id);
}
+void TabContents::OnInstallApplication(const WebApplicationInfo& info) {
+ if (delegate())
+ delegate()->OnInstallApplication(this, info);
+}
+
void TabContents::OnDisabledOutdatedPlugin(const string16& name,
const GURL& update_url) {
new DisabledPluginInfoBar(this, name, update_url);
diff --git a/chrome/browser/tab_contents/tab_contents.h b/chrome/browser/tab_contents/tab_contents.h
index 4d54db5..19a61a8 100644
--- a/chrome/browser/tab_contents/tab_contents.h
+++ b/chrome/browser/tab_contents/tab_contents.h
@@ -868,6 +868,7 @@ class TabContents : public PageNavigator,
virtual void OnCrashedWorker();
virtual void OnDidGetApplicationInfo(int32 page_id,
const WebApplicationInfo& info);
+ virtual void OnInstallApplication(const WebApplicationInfo& info);
virtual void OnDisabledOutdatedPlugin(const string16& name,
const GURL& update_url);
virtual void OnPageContents(const GURL& url,
diff --git a/chrome/browser/tab_contents/tab_contents_delegate.cc b/chrome/browser/tab_contents/tab_contents_delegate.cc
index 89eef5c..6c54933 100644
--- a/chrome/browser/tab_contents/tab_contents_delegate.cc
+++ b/chrome/browser/tab_contents/tab_contents_delegate.cc
@@ -162,6 +162,12 @@ void TabContentsDelegate::OnDidGetApplicationInfo(TabContents* tab_contents,
int32 page_id) {
}
+// Notification when an application programmatically requests installation.
+void TabContentsDelegate::OnInstallApplication(
+ TabContents* tab_contents,
+ const WebApplicationInfo& app_info) {
+}
+
gfx::NativeWindow TabContentsDelegate::GetFrameNativeWindow() {
return NULL;
}
diff --git a/chrome/browser/tab_contents/tab_contents_delegate.h b/chrome/browser/tab_contents/tab_contents_delegate.h
index 5d9ee37..e748229 100644
--- a/chrome/browser/tab_contents/tab_contents_delegate.h
+++ b/chrome/browser/tab_contents/tab_contents_delegate.h
@@ -38,6 +38,7 @@ class RenderViewHost;
class TabContents;
class TemplateURL;
class TemplateURLModel;
+struct WebApplicationInfo;
// Objects implement this interface to get notified about changes in the
// TabContents and to provide necessary functionality.
@@ -274,6 +275,10 @@ class TabContentsDelegate : public AutomationResourceRoutingDelegate {
virtual void OnDidGetApplicationInfo(TabContents* tab_contents,
int32 page_id);
+ // Notification when an application programmatically requests installation.
+ virtual void OnInstallApplication(TabContents* tab_contents,
+ const WebApplicationInfo& app_info);
+
// Returns the native window framing the view containing the tab contents.
virtual gfx::NativeWindow GetFrameNativeWindow();
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index b113408..24f7c6f 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -46,6 +46,7 @@
#include "chrome/browser/download/download_shelf.h"
#include "chrome/browser/download/download_started_animation.h"
#include "chrome/browser/extensions/crashed_extension_infobar.h"
+#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_browser_event_router.h"
#include "chrome/browser/extensions/extension_disabled_infobar_delegate.h"
#include "chrome/browser/extensions/extension_host.h"
@@ -97,6 +98,7 @@
#include "chrome/common/page_transition_types.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
+#include "chrome/common/web_apps.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
@@ -3143,6 +3145,19 @@ void Browser::OnDidGetApplicationInfo(TabContents* tab_contents,
pending_web_app_action_ = NONE;
}
+void Browser::OnInstallApplication(TabContents* source,
+ const WebApplicationInfo& web_app) {
+ ExtensionsService* extensions_service = profile()->GetExtensionsService();
+ if (!extensions_service)
+ return;
+
+ scoped_refptr<CrxInstaller> installer(
+ new CrxInstaller(extensions_service,
+ extensions_service->show_extensions_prompts() ?
+ new ExtensionInstallUI(profile()) : NULL));
+ installer->InstallWebApp(web_app);
+}
+
void Browser::ContentRestrictionsChanged(TabContents* source) {
UpdateCommandsForContentRestrictionState();
}
diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h
index 79bde6d..faa316c 100644
--- a/chrome/browser/ui/browser.h
+++ b/chrome/browser/ui/browser.h
@@ -47,6 +47,7 @@ class SkBitmap;
class StatusBubble;
class TabNavigation;
class TabStripModel;
+struct WebApplicationInfo;
namespace gfx {
class Point;
}
@@ -776,6 +777,8 @@ class Browser : public TabHandlerDelegate,
NavigationType::Type navigation_type);
virtual void OnDidGetApplicationInfo(TabContents* tab_contents,
int32 page_id);
+ virtual void OnInstallApplication(TabContents* tab_contents,
+ const WebApplicationInfo& app_info);
virtual void ContentRestrictionsChanged(TabContents* source);
// Overridden from SelectFileDialog::Listener:
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index de8eaeb..538df0f 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1983,6 +1983,7 @@
'browser/extensions/browser_action_test_util_views.cc',
'browser/extensions/content_script_all_frames_apitest.cc',
'browser/extensions/content_script_extension_process_apitest.cc',
+ 'browser/extensions/convert_web_app_browsertest.cc',
'browser/extensions/cross_origin_xhr_apitest.cc',
'browser/extensions/crx_installer_browsertest.cc',
'browser/extensions/events_apitest.cc',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 9e2c79f..1149b86 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -436,6 +436,9 @@ const char kEnableConnectBackupJobs[] = "enable-connect-backup-jobs";
// Link: headers.
const char kEnableContentPrefetch[] = "enable-content-prefetch";
+// Enables web developers to create apps for Chrome without using crx packages.
+const char kEnableCrxlessWebApps[] = "enable-crxless-web-apps";
+
// Whether default apps should be installed in this profile. This flag has no
// effect on Chrome OS because default apps are always enabled there.
const char kEnableDefaultApps[] = "enable-default-apps";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index d97692c..b9bec76 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -131,6 +131,7 @@ extern const char kEnableCloudPrint[];
extern const char kEnableConfirmToQuit[];
extern const char kEnableConnectBackupJobs[];
extern const char kEnableContentPrefetch[];
+extern const char kEnableCrxlessWebApps[];
extern const char kEnableDefaultApps[];
extern const char kEnableDeviceMotion[];
extern const char kEnableDNSCertProvenanceChecking[];
diff --git a/chrome/common/common_param_traits.cc b/chrome/common/common_param_traits.cc
index ca5e90b..165224c 100644
--- a/chrome/common/common_param_traits.cc
+++ b/chrome/common/common_param_traits.cc
@@ -237,6 +237,7 @@ void ParamTraits<WebApplicationInfo>::Write(Message* m,
WriteParam(m, p.icons[i].url);
WriteParam(m, p.icons[i].width);
WriteParam(m, p.icons[i].height);
+ WriteParam(m, p.icons[i].data);
}
}
@@ -255,7 +256,8 @@ bool ParamTraits<WebApplicationInfo>::Read(
result =
ReadParam(m, iter, &icon_info.url) &&
ReadParam(m, iter, &icon_info.width) &&
- ReadParam(m, iter, &icon_info.height);
+ ReadParam(m, iter, &icon_info.height) &&
+ ReadParam(m, iter, &icon_info.data);
if (!result)
return false;
r->icons.push_back(icon_info);
diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h
index ef392b4..41b9dca 100644
--- a/chrome/common/render_messages_internal.h
+++ b/chrome/common/render_messages_internal.h
@@ -1913,6 +1913,10 @@ IPC_BEGIN_MESSAGES(ViewHost)
int32 /* page_id */,
WebApplicationInfo)
+ // Sent by the renderer to implement chrome.app.installApplication().
+ IPC_MESSAGE_ROUTED1(ViewHostMsg_InstallApplication,
+ WebApplicationInfo)
+
// Provides the result from running OnMsgShouldClose. |proceed| matches the
// return value of the the frame's shouldClose method (which includes the
// onbeforeunload handler): true if the user decided to proceed with leaving
diff --git a/chrome/common/web_apps.cc b/chrome/common/web_apps.cc
index cde9690..b9c390c 100644
--- a/chrome/common/web_apps.cc
+++ b/chrome/common/web_apps.cc
@@ -205,7 +205,7 @@ bool ParseWebAppFromWebDocument(WebFrame* frame,
return true;
}
-bool ParseWebAppFromDefinitionFile(const DictionaryValue& definition,
+bool ParseWebAppFromDefinitionFile(Value* definition_value,
WebApplicationInfo* web_app,
string16* error) {
CHECK(web_app->manifest_url.is_valid());
@@ -230,17 +230,22 @@ bool ParseWebAppFromDefinitionFile(const DictionaryValue& definition,
// and for forward compat with ourselves.
validator.set_default_allow_additional_properties(true);
- if (!validator.Validate(const_cast<DictionaryValue*>(&definition))) {
+ if (!validator.Validate(definition_value)) {
*error = UTF8ToUTF16(
validator.errors()[0].path + ": " + validator.errors()[0].message);
return false;
}
+ // This must be true because the schema requires the root value to be a
+ // dictionary.
+ CHECK(definition_value->IsType(Value::TYPE_DICTIONARY));
+ DictionaryValue* definition = static_cast<DictionaryValue*>(definition_value);
+
// Parse launch URL. It must be a valid URL in the same origin as the
// manifest.
std::string app_url_string;
GURL app_url;
- CHECK(definition.GetString("launch_url", &app_url_string));
+ CHECK(definition->GetString("launch_url", &app_url_string));
if (!(app_url = web_app->manifest_url.Resolve(app_url_string)).is_valid() ||
app_url.GetOrigin() != web_app->manifest_url.GetOrigin()) {
*error = UTF8ToUTF16(WebApplicationInfo::kInvalidLaunchURL);
@@ -250,7 +255,7 @@ bool ParseWebAppFromDefinitionFile(const DictionaryValue& definition,
// Parse out the permissions if present.
std::vector<std::string> permissions;
ListValue* permissions_value = NULL;
- if (definition.GetList("permissions", &permissions_value)) {
+ if (definition->GetList("permissions", &permissions_value)) {
for (size_t i = 0; i < permissions_value->GetSize(); ++i) {
std::string permission;
CHECK(permissions_value->GetString(i, &permission));
@@ -261,7 +266,7 @@ bool ParseWebAppFromDefinitionFile(const DictionaryValue& definition,
// Parse out the URLs if present.
std::vector<GURL> urls;
ListValue* urls_value = NULL;
- if (definition.GetList("urls", &urls_value)) {
+ if (definition->GetList("urls", &urls_value)) {
for (size_t i = 0; i < urls_value->GetSize(); ++i) {
std::string url_string;
GURL url;
@@ -280,7 +285,7 @@ bool ParseWebAppFromDefinitionFile(const DictionaryValue& definition,
// Parse out the icons if present.
std::vector<WebApplicationInfo::IconInfo> icons;
DictionaryValue* icons_value = NULL;
- if (definition.GetDictionary("icons", &icons_value)) {
+ if (definition->GetDictionary("icons", &icons_value)) {
for (DictionaryValue::key_iterator iter = icons_value->begin_keys();
iter != icons_value->end_keys(); ++iter) {
// Ignore unknown properties. Better for forward compat.
@@ -308,9 +313,9 @@ bool ParseWebAppFromDefinitionFile(const DictionaryValue& definition,
}
}
- CHECK(definition.GetString("name", &web_app->title));
- definition.GetString("description", &web_app->description);
- definition.GetString("launch_container", &web_app->launch_container);
+ CHECK(definition->GetString("name", &web_app->title));
+ definition->GetString("description", &web_app->description);
+ definition->GetString("launch_container", &web_app->launch_container);
web_app->app_url = app_url;
web_app->urls = urls;
web_app->permissions = permissions;
diff --git a/chrome/common/web_apps.h b/chrome/common/web_apps.h
index 6d40883..91f2023 100644
--- a/chrome/common/web_apps.h
+++ b/chrome/common/web_apps.h
@@ -19,7 +19,7 @@ class WebDocument;
class WebFrame;
}
-class DictionaryValue;
+class Value;
// Structure used when installing a web page as an app.
struct WebApplicationInfo {
@@ -100,7 +100,7 @@ bool ParseWebAppFromWebDocument(WebKit::WebFrame* frame,
// Parses |web_app| information out of |definition|. Returns true on success, or
// false and |error| on failure. This function assumes that |web_app| has a
// valid manifest_url.
-bool ParseWebAppFromDefinitionFile(const DictionaryValue& definition,
+bool ParseWebAppFromDefinitionFile(Value* definition,
WebApplicationInfo* web_app,
string16* error);
diff --git a/chrome/common/web_apps_unittest.cc b/chrome/common/web_apps_unittest.cc
index 3a6980e..fc8b121 100644
--- a/chrome/common/web_apps_unittest.cc
+++ b/chrome/common/web_apps_unittest.cc
@@ -52,7 +52,7 @@ WebApplicationInfo* ParseFromDefinitionAndExpectSuccess(
web_app->manifest_url = GURL("http://example.com/");
string16 error;
- if (!web_apps::ParseWebAppFromDefinitionFile(*defintion, web_app.get(),
+ if (!web_apps::ParseWebAppFromDefinitionFile(defintion.get(), web_app.get(),
&error)) {
ADD_FAILURE() << "Error parsing " << name << ": " << UTF16ToUTF8(error);
return NULL;
@@ -71,7 +71,8 @@ void ParseFromDefinitionAndExpectFailure(const std::string& name,
web_app.manifest_url = GURL("http://example.com/");
string16 error;
- if (web_apps::ParseWebAppFromDefinitionFile(*definition, &web_app, &error)) {
+ if (web_apps::ParseWebAppFromDefinitionFile(definition.get(), &web_app,
+ &error)) {
ADD_FAILURE() << "Expected error parsing " << name
<< " but parse succeeded.";
return;
diff --git a/chrome/renderer/extensions/chrome_app_bindings.cc b/chrome/renderer/extensions/chrome_app_bindings.cc
index f30b4f3..23d99b0 100644
--- a/chrome/renderer/extensions/chrome_app_bindings.cc
+++ b/chrome/renderer/extensions/chrome_app_bindings.cc
@@ -4,8 +4,12 @@
#include "chrome/renderer/extensions/chrome_app_bindings.h"
+#include "base/string16.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/renderer/extensions/bindings_utils.h"
#include "chrome/renderer/extensions/extension_renderer_info.h"
#include "chrome/renderer/render_thread.h"
+#include "chrome/renderer/render_view.h"
#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h"
#include "v8/include/v8.h"
@@ -25,7 +29,9 @@ class ChromeAppExtensionWrapper : public v8::Extension {
"if (!chrome.app) {"
" chrome.app = new function() {"
" native function GetIsInstalled();"
+ " native function Install();"
" this.__defineGetter__('isInstalled', GetIsInstalled);"
+ " this.install = Install;"
" };"
"}") {}
@@ -33,8 +39,11 @@ class ChromeAppExtensionWrapper : public v8::Extension {
v8::Handle<v8::String> name) {
if (name->Equals(v8::String::New("GetIsInstalled"))) {
return v8::FunctionTemplate::New(GetIsInstalled);
+ } else if (name->Equals(v8::String::New("Install"))) {
+ return v8::FunctionTemplate::New(Install);
+ } else {
+ return v8::Handle<v8::FunctionTemplate>();
}
- return v8::Handle<v8::FunctionTemplate>();
}
static v8::Handle<v8::Value> GetIsInstalled(const v8::Arguments& args) {
@@ -51,6 +60,18 @@ class ChromeAppExtensionWrapper : public v8::Extension {
bool has_web_extent = (ExtensionRendererInfo::GetByURL(url) != NULL);
return v8::Boolean::New(has_web_extent);
}
+
+ static v8::Handle<v8::Value> Install(const v8::Arguments& args) {
+ WebFrame* frame = WebFrame::frameForCurrentContext();
+ RenderView* render_view = bindings_utils::GetRenderViewForCurrentContext();
+ if (frame && render_view) {
+ string16 error;
+ if (!render_view->InstallWebApplicationUsingDefinitionFile(frame, &error))
+ v8::ThrowException(v8::String::New(UTF16ToUTF8(error).c_str()));
+ }
+
+ return v8::Undefined();
+ }
};
v8::Extension* ChromeAppExtension::Get() {
diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc
index 7c21e1b..c98e082 100644
--- a/chrome/renderer/render_view.cc
+++ b/chrome/renderer/render_view.cc
@@ -34,6 +34,7 @@
#include "chrome/common/extensions/extension.h"
#include "chrome/common/file_system/file_system_dispatcher.h"
#include "chrome/common/file_system/webfilesystem_callback_dispatcher.h"
+#include "chrome/common/json_value_serializer.h"
#include "chrome/common/jstemplate_builder.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/page_zoom.h"
@@ -172,6 +173,7 @@
#include "webkit/glue/plugins/webplugin_delegate_impl.h"
#include "webkit/glue/plugins/webplugin_impl.h"
#include "webkit/glue/plugins/webview_plugin.h"
+#include "webkit/glue/resource_fetcher.h"
#include "webkit/glue/site_isolation_metrics.h"
#include "webkit/glue/webaccessibility.h"
#include "webkit/glue/webdropdata.h"
@@ -266,6 +268,7 @@ using webkit_glue::FormField;
using webkit_glue::ImageResourceFetcher;
using webkit_glue::PasswordForm;
using webkit_glue::PasswordFormDomManager;
+using webkit_glue::ResourceFetcher;
using webkit_glue::SiteIsolationMetrics;
using webkit_glue::WebAccessibility;
@@ -512,6 +515,7 @@ RenderView::RenderView(RenderThreadBase* render_thread,
ALLOW_THIS_IN_INITIALIZER_LIST(
notification_provider_(new NotificationProvider(this))),
accessibility_ack_pending_(false),
+ pending_app_icon_requests_(0),
session_storage_namespace_id_(session_storage_namespace_id),
decrement_shared_popup_at_destruction_(false) {
#if defined(OS_MACOSX)
@@ -544,12 +548,6 @@ RenderView::~RenderView() {
if (decrement_shared_popup_at_destruction_)
shared_popup_counter_->data--;
- // Dispose of un-disposed image fetchers.
- for (ImageResourceFetcherSet::iterator i = image_fetchers_.begin();
- i != image_fetchers_.end(); ++i) {
- delete *i;
- }
-
// If file chooser is still waiting for answer, dispatch empty answer.
while (!file_chooser_completions_.empty()) {
if (file_chooser_completions_.front()->completion) {
@@ -626,7 +624,7 @@ RenderView* RenderView::Create(
return view;
}
-/*static*/
+// static
void RenderView::SetNextPageID(int32 next_page_id) {
// This method should only be called during process startup, and the given
// page id had better not exceed our current next page id!
@@ -652,6 +650,140 @@ void RenderView::UserMetricsRecordAction(const std::string& action) {
Send(new ViewHostMsg_UserMetricsRecordAction(action));
}
+bool RenderView::InstallWebApplicationUsingDefinitionFile(WebFrame* frame,
+ string16* error) {
+ // There is an issue of drive-by installs with the below implementation. A web
+ // site could force a user to install an app by timing the dialog to come up
+ // just before the user clicks.
+ //
+ // We do show a success UI that allows users to uninstall, but it seems that
+ // we might still want to put up an infobar before showing the install dialog.
+ //
+ // TODO(aa): Figure out this issue before removing the kEnableCrxlessWebApps
+ // switch.
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableCrxlessWebApps)) {
+ *error = ASCIIToUTF16("CRX-less web apps aren't enabled.");
+ return false;
+ }
+
+ if (frame != frame->top()) {
+ *error = ASCIIToUTF16("Applications can only be installed from the top "
+ "frame.");
+ return false;
+ }
+
+ if (pending_app_info_.get()) {
+ *error = ASCIIToUTF16("An application install is already in progress.");
+ return false;
+ }
+
+ pending_app_info_.reset(new WebApplicationInfo());
+ if (!web_apps::ParseWebAppFromWebDocument(frame, pending_app_info_.get(),
+ error)) {
+ return false;
+ }
+
+ if (!pending_app_info_->manifest_url.is_valid()) {
+ *error = ASCIIToUTF16("Web application definition not found or invalid.");
+ return false;
+ }
+
+ app_definition_fetcher_.reset(new ResourceFetcher(
+ pending_app_info_->manifest_url, webview()->mainFrame(),
+ NewCallback(this, &RenderView::DidDownloadApplicationDefinition)));
+ return true;
+}
+
+void RenderView::DidDownloadApplicationDefinition(
+ const WebKit::WebURLResponse& response,
+ const std::string& data) {
+ scoped_ptr<WebApplicationInfo> app_info(
+ pending_app_info_.release());
+
+ JSONStringValueSerializer serializer(data);
+ int error_code = 0;
+ std::string error_message;
+ scoped_ptr<Value> result(serializer.Deserialize(&error_code, &error_message));
+ if (!result.get()) {
+ AddErrorToRootConsole(UTF8ToUTF16(error_message));
+ return;
+ }
+
+ string16 error_message_16;
+ if (!web_apps::ParseWebAppFromDefinitionFile(result.get(), app_info.get(),
+ &error_message_16)) {
+ AddErrorToRootConsole(error_message_16);
+ return;
+ }
+
+ if (!app_info->icons.empty()) {
+ pending_app_info_.reset(app_info.release());
+ pending_app_icon_requests_ =
+ static_cast<int>(pending_app_info_->icons.size());
+ for (size_t i = 0; i < pending_app_info_->icons.size(); ++i) {
+ app_icon_fetchers_.push_back(linked_ptr<ImageResourceFetcher>(
+ new ImageResourceFetcher(
+ pending_app_info_->icons[i].url,
+ webview()->mainFrame(),
+ static_cast<int>(i),
+ pending_app_info_->icons[i].width,
+ NewCallback(this, &RenderView::DidDownloadApplicationIcon))));
+ }
+ } else {
+ Send(new ViewHostMsg_InstallApplication(routing_id_, *app_info));
+ }
+}
+
+void RenderView::DidDownloadApplicationIcon(ImageResourceFetcher* fetcher,
+ const SkBitmap& image) {
+ pending_app_info_->icons[fetcher->id()].data = image;
+
+ // Remove the image fetcher from our pending list. We're in the callback from
+ // ImageResourceFetcher, best to delay deletion.
+ for (ImageResourceFetcherList::iterator iter = app_icon_fetchers_.begin();
+ iter != app_icon_fetchers_.end(); ++iter) {
+ if (iter->get() == fetcher) {
+ iter->release();
+ app_icon_fetchers_.erase(iter);
+ break;
+ }
+ }
+
+ // We're in the callback from the ImageResourceFetcher, best to delay
+ // deletion.
+ MessageLoop::current()->DeleteSoon(FROM_HERE, fetcher);
+
+ if (--pending_app_icon_requests_ > 0)
+ return;
+
+ // There is a maximum size of IPC on OS X and Linux that we have run into in
+ // some situations. We're not sure what it is, but our hypothesis is in the
+ // neighborhood of 1 MB.
+ //
+ // To be on the safe side, we give ourselves 128 KB for just the image data.
+ // This should be more than enough for 128, 48, and 16 px 32-bit icons. If we
+ // want to start allowing larger icons (see bug 63406), we'll have to either
+ // experiment mor ewith this and find the real limit, or else come up with
+ // some alternative way to transmit the icon data to the browser process.
+ //
+ // See also: bug 63729.
+ const int kMaxIconSize = 1024 * 128;
+ int actual_icon_size = 0;
+ for (size_t i = 0; i < pending_app_info_->icons.size(); ++i) {
+ actual_icon_size += pending_app_info_->icons[i].data.getSize();
+ }
+
+ if (actual_icon_size > kMaxIconSize) {
+ AddErrorToRootConsole(ASCIIToUTF16(
+ "Icons are too large. Maximum total size for app icons is 128 KB."));
+ return;
+ }
+
+ Send(new ViewHostMsg_InstallApplication(routing_id_, *pending_app_info_));
+ pending_app_info_.reset(NULL);
+}
+
void RenderView::PluginCrashed(const FilePath& plugin_path) {
Send(new ViewHostMsg_CrashedPlugin(routing_id_, plugin_path));
}
@@ -3013,6 +3145,13 @@ void RenderView::didCompleteClientRedirect(
}
void RenderView::didCreateDataSource(WebFrame* frame, WebDataSource* ds) {
+ // If there are any app-related fetches in progress, they can be cancelled now
+ // since we have navigated away from the page that created them.
+ if (!frame->parent()) {
+ app_icon_fetchers_.clear();
+ app_definition_fetcher_.reset(NULL);
+ }
+
// The rest of RenderView assumes that a WebDataSource will always have a
// non-null NavigationState.
bool content_initiated = !pending_navigation_state_.get();
@@ -3890,9 +4029,10 @@ bool RenderView::DownloadImage(int id, const GURL& image_url, int image_size) {
if (!webview())
return false;
// Create an image resource fetcher and assign it with a call back object.
- image_fetchers_.insert(new ImageResourceFetcher(
- image_url, webview()->mainFrame(), id, image_size,
- NewCallback(this, &RenderView::DidDownloadImage)));
+ image_fetchers_.push_back(linked_ptr<ImageResourceFetcher>(
+ new ImageResourceFetcher(
+ image_url, webview()->mainFrame(), id, image_size,
+ NewCallback(this, &RenderView::DidDownloadImage))));
return true;
}
@@ -3904,11 +4044,17 @@ void RenderView::DidDownloadImage(ImageResourceFetcher* fetcher,
fetcher->image_url(),
image.isNull(),
image));
- // Dispose of the image fetcher.
- DCHECK(image_fetchers_.find(fetcher) != image_fetchers_.end());
- image_fetchers_.erase(fetcher);
- // We're in the callback from the ImageResourceFetcher, best to delay
- // deletion.
+
+ // Remove the image fetcher from our pending list. We're in the callback from
+ // ImageResourceFetcher, best to delay deletion.
+ for (ImageResourceFetcherList::iterator iter = image_fetchers_.begin();
+ iter != image_fetchers_.end(); ++iter) {
+ if (iter->get() == fetcher) {
+ iter->release();
+ image_fetchers_.erase(iter);
+ break;
+ }
+ }
MessageLoop::current()->DeleteSoon(FROM_HERE, fetcher);
}
@@ -5534,3 +5680,10 @@ void RenderView::OnSelectPopupMenuItem(int selected_index) {
external_popup_menu_.reset();
}
#endif
+
+void RenderView::AddErrorToRootConsole(const string16& message) {
+ if (webview() && webview()->mainFrame()) {
+ webview()->mainFrame()->addMessageToConsole(
+ WebConsoleMessage(WebConsoleMessage::LevelError, message));
+ }
+}
diff --git a/chrome/renderer/render_view.h b/chrome/renderer/render_view.h
index ba7b5a9..e8067df 100644
--- a/chrome/renderer/render_view.h
+++ b/chrome/renderer/render_view.h
@@ -107,6 +107,7 @@ class ImageResourceFetcher;
struct FileUploadData;
struct FormData;
struct PasswordFormFillData;
+class ResourceFetcher;
}
namespace WebKit {
@@ -285,6 +286,13 @@ class RenderView : public RenderWidget,
// aggregated to the user metrics service.
void UserMetricsRecordAction(const std::string& action);
+ // Starts installation of the page in the specified frame as a web app. The
+ // page must link to an external 'definition file'. This is different from
+ // the 'application shortcuts' feature where we pull the application
+ // definition out of optional meta tags in the page.
+ bool InstallWebApplicationUsingDefinitionFile(WebKit::WebFrame* frame,
+ string16* error);
+
// Extensions ----------------------------------------------------------------
void SendExtensionRequest(const ViewHostMsg_DomMessage_Params& params);
@@ -683,6 +691,11 @@ class RenderView : public RenderWidget,
typedef std::map<GURL, ContentSettings> HostContentSettings;
typedef std::map<GURL, double> HostZoomLevels;
+ // Cannot use std::set unfortunately since linked_ptr<> does not support
+ // operator<.
+ typedef std::vector<linked_ptr<webkit_glue::ImageResourceFetcher> >
+ ImageResourceFetcherList;
+
// Identifies an accessibility notification from webkit.
struct RendererAccessibilityNotification {
public:
@@ -1000,6 +1013,16 @@ class RenderView : public RenderWidget,
void DidDownloadImage(webkit_glue::ImageResourceFetcher* fetcher,
const SkBitmap& image);
+ // Callback triggered when we finish downloading the application definition
+ // file.
+ void DidDownloadApplicationDefinition(const WebKit::WebURLResponse& response,
+ const std::string& data);
+
+ // Callback triggered after each icon referenced by the application definition
+ // is downloaded.
+ void DidDownloadApplicationIcon(webkit_glue::ImageResourceFetcher* fetcher,
+ const SkBitmap& image);
+
// Requests to download an image. When done, the RenderView is
// notified by way of DidDownloadImage. Returns true if the request was
// successfully started, false otherwise. id is used to uniquely identify the
@@ -1085,6 +1108,9 @@ class RenderView : public RenderWidget,
// If |url| is empty, show |fallback_url|.
void UpdateTargetURL(const GURL& url, const GURL& fallback_url);
+ // Helper to add an error message to the root frame's console.
+ void AddErrorToRootConsole(const string16& message);
+
// ---------------------------------------------------------------------------
// ADDING NEW FUNCTIONS? Please keep private functions alphabetized and put
// it in the same order in the .cc file as it was in the header.
@@ -1382,8 +1408,22 @@ class RenderView : public RenderWidget,
pending_code_execution_queue_;
// ImageResourceFetchers schedule via DownloadImage.
- typedef std::set<webkit_glue::ImageResourceFetcher*> ImageResourceFetcherSet;
- ImageResourceFetcherSet image_fetchers_;
+ ImageResourceFetcherList image_fetchers_;
+
+ // The app info that we are processing. This is used when installing an app
+ // via application definition. The in-progress web app is stored here while
+ // its manifest and icons are downloaded.
+ scoped_ptr<WebApplicationInfo> pending_app_info_;
+
+ // Used to download the application definition file.
+ scoped_ptr<webkit_glue::ResourceFetcher> app_definition_fetcher_;
+
+ // Used to download the icons for an application.
+ ImageResourceFetcherList app_icon_fetchers_;
+
+ // The number of app icon requests outstanding. When this reaches zero, we're
+ // done processing an app definition file.
+ int pending_app_icon_requests_;
// The SessionStorage namespace that we're assigned to has an ID, and that ID
// is passed to us upon creation. WebKit asks for this ID upon first use and
diff --git a/chrome/test/data/extensions/convert_web_app/application.html b/chrome/test/data/extensions/convert_web_app/application.html
new file mode 100644
index 0000000..bd4b474
--- /dev/null
+++ b/chrome/test/data/extensions/convert_web_app/application.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<link rel="chrome-application-definition" href="application_definition.json">
+</head>
+<body>
+<script>
+chrome.app.install();
+</script>
+</body>
+</html>
diff --git a/chrome/test/data/extensions/convert_web_app/application_definition.json b/chrome/test/data/extensions/convert_web_app/application_definition.json
new file mode 100644
index 0000000..6d4864d
--- /dev/null
+++ b/chrome/test/data/extensions/convert_web_app/application_definition.json
@@ -0,0 +1,9 @@
+{
+ "name": "Test application",
+ "launch_url": "launch.html",
+ "icons": {
+ "16": "16.png",
+ "48": "48.png",
+ "128": "128.png"
+ }
+}