diff options
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" + } +} |