Commit f2fe87c2 authored by jstritar@chromium.org's avatar jstritar@chromium.org

Add an API for hosted apps to check their install and running states.

BUG=107216
TEST=ChromeAppAPITest.*

Review URL: http://codereview.chromium.org/10174001

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@133722 0039d316-1c4b-4281-b951-d872f2087c98
parent 2b11f5ef
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
......@@ -10,6 +10,8 @@
#include "base/string_number_conversions.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension.h"
......@@ -21,14 +23,41 @@
class ChromeAppAPITest : public ExtensionBrowserTest {
protected:
bool IsAppInstalled() {
bool IsAppInstalled() { return IsAppInstalled(L""); }
bool IsAppInstalled(const std::wstring& frame_xpath) {
std::wstring get_app_is_installed =
L"window.domAutomationController.send(window.chrome.app.isInstalled);";
bool result;
CHECK(
ui_test_utils::ExecuteJavaScriptAndExtractBool(
browser()->GetSelectedWebContents()->GetRenderViewHost(),
L"", get_app_is_installed, &result));
frame_xpath, get_app_is_installed, &result));
return result;
}
std::string InstallState() { return InstallState(L""); }
std::string InstallState(const std::wstring& frame_xpath) {
std::wstring get_app_install_state =
L"window.chrome.app.installState("
L"function(s) { window.domAutomationController.send(s); });";
std::string result;
CHECK(
ui_test_utils::ExecuteJavaScriptAndExtractString(
browser()->GetSelectedWebContents()->GetRenderViewHost(),
frame_xpath, get_app_install_state, &result));
return result;
}
std::string RunningState() { return RunningState(L""); }
std::string RunningState(const std::wstring& frame_xpath) {
std::wstring get_app_install_state =
L"window.domAutomationController.send("
L"window.chrome.app.runningState());";
std::string result;
CHECK(
ui_test_utils::ExecuteJavaScriptAndExtractString(
browser()->GetSelectedWebContents()->GetRenderViewHost(),
frame_xpath, get_app_install_state, &result));
return result;
}
......@@ -181,3 +210,97 @@ IN_PROC_BROWSER_TEST_F(ChromeAppAPITest, GetDetailsForFrame) {
EXPECT_TRUE(app_details.get());
EXPECT_TRUE(app_details->Equals(extension->manifest()->value()));
}
IN_PROC_BROWSER_TEST_F(ChromeAppAPITest, InstallAndRunningState) {
std::string app_host("app.com");
std::string non_app_host("nonapp.com");
host_resolver()->AddRule(app_host, "127.0.0.1");
host_resolver()->AddRule(non_app_host, "127.0.0.1");
ASSERT_TRUE(test_server()->Start());
GURL test_file_url(test_server()->GetURL(
"files/extensions/get_app_details_for_frame.html"));
GURL::Replacements replace_host;
replace_host.SetHostStr(app_host);
GURL app_url(test_file_url.ReplaceComponents(replace_host));
replace_host.SetHostStr(non_app_host);
GURL non_app_url(test_file_url.ReplaceComponents(replace_host));
// Before the app is installed, app.com does not think that it is installed
ui_test_utils::NavigateToURL(browser(), app_url);
EXPECT_EQ("not_installed", InstallState());
EXPECT_EQ("cannot_run", RunningState());
EXPECT_FALSE(IsAppInstalled());
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("app_dot_com_app"));
ASSERT_TRUE(extension);
EXPECT_EQ("installed", InstallState());
EXPECT_EQ("ready_to_run", RunningState());
EXPECT_FALSE(IsAppInstalled());
// Reloading the page should put the tab in an app process.
ui_test_utils::NavigateToURL(browser(), app_url);
EXPECT_EQ("installed", InstallState());
EXPECT_EQ("running", RunningState());
EXPECT_TRUE(IsAppInstalled());
// Disable the extension and verify the state.
browser()->profile()->GetExtensionService()->DisableExtension(
extension->id(), Extension::DISABLE_PERMISSIONS_INCREASE);
ui_test_utils::NavigateToURL(browser(), app_url);
EXPECT_EQ("disabled", InstallState());
EXPECT_EQ("cannot_run", RunningState());
EXPECT_FALSE(IsAppInstalled());
browser()->profile()->GetExtensionService()->EnableExtension(extension->id());
EXPECT_EQ("installed", InstallState());
EXPECT_EQ("ready_to_run", RunningState());
EXPECT_FALSE(IsAppInstalled());
// The non-app URL should still not be installed or running.
ui_test_utils::NavigateToURL(browser(), non_app_url);
EXPECT_EQ("not_installed", InstallState());
EXPECT_EQ("cannot_run", RunningState());
EXPECT_FALSE(IsAppInstalled());
EXPECT_EQ("installed", InstallState(L"//html/iframe[1]"));
EXPECT_EQ("cannot_run", RunningState(L"//html/iframe[1]"));
EXPECT_FALSE(IsAppInstalled(L"//html/iframe[1]"));
}
IN_PROC_BROWSER_TEST_F(ChromeAppAPITest, InstallAndRunningStateFrame) {
std::string app_host("app.com");
std::string non_app_host("nonapp.com");
host_resolver()->AddRule(app_host, "127.0.0.1");
host_resolver()->AddRule(non_app_host, "127.0.0.1");
ASSERT_TRUE(test_server()->Start());
GURL test_file_url(test_server()->GetURL(
"files/extensions/get_app_details_for_frame_reversed.html"));
GURL::Replacements replace_host;
replace_host.SetHostStr(app_host);
GURL app_url(test_file_url.ReplaceComponents(replace_host));
replace_host.SetHostStr(non_app_host);
GURL non_app_url(test_file_url.ReplaceComponents(replace_host));
// Check the install and running state of a non-app iframe running
// within an app.
ui_test_utils::NavigateToURL(browser(), app_url);
EXPECT_EQ("not_installed", InstallState(L"//html/iframe[1]"));
EXPECT_EQ("cannot_run", RunningState(L"//html/iframe[1]"));
EXPECT_FALSE(IsAppInstalled(L"//html/iframe[1]"));
}
......@@ -710,7 +710,6 @@ class ExtensionService
scoped_ptr<extensions::SettingsFrontend> settings_frontend_;
// The current list of installed extensions.
// TODO(aa): This should use chrome/common/extensions/extension_set.h.
ExtensionSet extensions_;
// The list of installed extensions that have been disabled.
......
......@@ -137,6 +137,8 @@ bool ExtensionTabHelper::OnMessageReceived(const IPC::Message& message) {
OnInlineWebstoreInstall)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppNotifyChannel,
OnGetAppNotifyChannel)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState,
OnGetAppInstallState);
IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
......@@ -223,6 +225,28 @@ void ExtensionTabHelper::OnGetAppNotifyChannel(
// We'll get called back in AppNotifyChannelSetupComplete.
}
void ExtensionTabHelper::OnGetAppInstallState(const GURL& requestor_url,
int return_route_id,
int callback_id) {
Profile* profile =
Profile::FromBrowserContext(web_contents()->GetBrowserContext());
ExtensionService* extension_service = profile->GetExtensionService();
const ExtensionSet* extensions = extension_service->extensions();
const ExtensionSet* disabled = extension_service->disabled_extensions();
ExtensionURLInfo url(requestor_url);
std::string state;
if (extensions->GetHostedAppByURL(url))
state = extension_misc::kAppStateInstalled;
else if (disabled->GetHostedAppByURL(url))
state = extension_misc::kAppStateDisabled;
else
state = extension_misc::kAppStateNotInstalled;
Send(new ExtensionMsg_GetAppInstallStateResponse(
return_route_id, state, callback_id));
}
void ExtensionTabHelper::AppNotifyChannelSetupComplete(
const std::string& channel_id,
const std::string& error,
......
......@@ -116,6 +116,9 @@ class ExtensionTabHelper
const std::string& client_id,
int return_route_id,
int callback_id);
void OnGetAppInstallState(const GURL& requestor_url,
int return_route_id,
int callback_id);
void OnRequest(const ExtensionHostMsg_Request_Params& params);
// App extensions related methods:
......
......@@ -35,6 +35,33 @@
"type": "boolean"
}
},
{
"name": "installState",
"description": "TODO",
"type": "function",
"parameters": [
{
"type": "function",
"name": "callback",
"parameters": [
{
"type": "string",
"enum": [ "not_installed", "installed", "disabled" ]
}
]
}
]
},
{
"name": "runningState",
"description": "TODO",
"type": "function",
"parameters": [],
"returns": {
"type": "string",
"enum": [ "running", "cannot_run", "ready_to_run" ]
}
},
{
"name": "install",
"description": "TODO",
......
......@@ -112,6 +112,14 @@ const char kAccessExtensionPath[] =
"/usr/share/chromeos-assets/accessibility/extensions";
const char kChromeVoxDirectoryName[] = "access_chromevox";
#endif
const char kAppStateNotInstalled[] = "not_installed";
const char kAppStateInstalled[] = "installed";
const char kAppStateDisabled[] = "disabled";
const char kAppStateRunning[] = "running";
const char kAppStateCannotRun[] = "cannot_run";
const char kAppStateReadyToRun[] = "ready_to_run";
const char kAppNotificationsIncognitoError[] =
"This API is not accessible by 'split' mode "
"extensions in incognito windows.";
......
......@@ -239,6 +239,15 @@ namespace extension_misc {
UNLOAD_REASON_TERMINATE, // Extension has terminated.
};
// The states that an app can be in, as reported by chrome.app.installState
// and chrome.app.runningState.
extern const char kAppStateNotInstalled[];
extern const char kAppStateInstalled[];
extern const char kAppStateDisabled[];
extern const char kAppStateRunning[];
extern const char kAppStateCannotRun[];
extern const char kAppStateReadyToRun[];
// Error indicating that the app notifications API is not accessible by split
// mode extensions in incognito windows.
extern const char kAppNotificationsIncognitoError[];
......
......@@ -279,6 +279,11 @@ IPC_MESSAGE_ROUTED3(ExtensionMsg_GetAppNotifyChannelResponse,
std::string /* error */,
int32 /* callback_id */)
// Response to the renderer for ExtensionHostMsg_GetAppInstallState.
IPC_MESSAGE_ROUTED2(ExtensionMsg_GetAppInstallStateResponse,
std::string /* state */,
int32 /* callback_id */)
// Dispatch the Port.onConnect event for message channels.
IPC_MESSAGE_ROUTED5(ExtensionMsg_DispatchOnConnect,
int /* target_port_id */,
......@@ -401,6 +406,12 @@ IPC_MESSAGE_ROUTED4(ExtensionHostMsg_GetAppNotifyChannel,
int32 /* return_route_id */,
int32 /* callback_id */)
// Sent by the renderer when a web page is checking if its app is installed.
IPC_MESSAGE_ROUTED3(ExtensionHostMsg_GetAppInstallState,
GURL /* requestor_url */,
int32 /* return_route_id */,
int32 /* callback_id */)
// Optional Ack message sent to the browser to notify that the response to a
// function has been processed.
IPC_MESSAGE_ROUTED1(ExtensionHostMsg_ResponseAck,
......
......@@ -69,6 +69,10 @@ AppBindings::AppBindings(ExtensionDispatcher* dispatcher,
base::Bind(&AppBindings::GetDetailsForFrame, base::Unretained(this)));
RouteFunction("GetAppNotifyChannel",
base::Bind(&AppBindings::GetAppNotifyChannel, base::Unretained(this)));
RouteFunction("GetInstallState",
base::Bind(&AppBindings::GetInstallState, base::Unretained(this)));
RouteFunction("GetRunningState",
base::Bind(&AppBindings::GetRunningState, base::Unretained(this)));
}
v8::Handle<v8::Value> AppBindings::GetIsInstalled(
......@@ -184,10 +188,67 @@ v8::Handle<v8::Value> AppBindings::GetAppNotifyChannel(
return v8::Undefined();
}
v8::Handle<v8::Value> AppBindings::GetInstallState(const v8::Arguments& args) {
// Get the callbackId.
int callback_id = 0;
if (args.Length() == 1) {
if (!args[0]->IsInt32()) {
v8::ThrowException(v8::String::New(kInvalidCallbackIdError));
return v8::Undefined();
}
callback_id = args[0]->Int32Value();
}
content::RenderView* render_view = context_->GetRenderView();
CHECK(render_view);
Send(new ExtensionHostMsg_GetAppInstallState(
render_view->GetRoutingID(), context_->web_frame()->document().url(),
GetRoutingID(), callback_id));
return v8::Undefined();
}
v8::Handle<v8::Value> AppBindings::GetRunningState(const v8::Arguments& args) {
// To distinguish between ready_to_run and cannot_run states, we need the top
// level frame.
const WebFrame* parent_frame = context_->web_frame();
while (parent_frame->parent())
parent_frame = parent_frame->parent();
const ExtensionSet* extensions = extension_dispatcher_->extensions();
// The app associated with the top level frame.
const Extension* parent_app = extensions->GetHostedAppByURL(
ExtensionURLInfo(parent_frame->document().url()));
// The app associated with this frame.
const Extension* this_app = extensions->GetHostedAppByURL(
ExtensionURLInfo(context_->web_frame()->document().url()));
if (!this_app || !parent_app)
return v8::String::New(extension_misc::kAppStateCannotRun);
const char* state = NULL;
if (extension_dispatcher_->IsExtensionActive(parent_app->id())) {
if (parent_app == this_app)
state = extension_misc::kAppStateRunning;
else
state = extension_misc::kAppStateCannotRun;
} else if (parent_app == this_app) {
state = extension_misc::kAppStateReadyToRun;
} else {
state = extension_misc::kAppStateCannotRun;
}
return v8::String::New(state);
}
bool AppBindings::OnMessageReceived(const IPC::Message& message) {
IPC_BEGIN_MESSAGE_MAP(AppBindings, message)
IPC_MESSAGE_HANDLER(ExtensionMsg_GetAppNotifyChannelResponse,
OnGetAppNotifyChannelResponse)
IPC_MESSAGE_HANDLER(ExtensionMsg_GetAppInstallStateResponse,
OnAppInstallStateResponse)
IPC_MESSAGE_UNHANDLED(CHECK(false) << "Unhandled IPC message")
IPC_END_MESSAGE_MAP()
return true;
......@@ -204,3 +265,14 @@ void AppBindings::OnGetAppNotifyChannelResponse(
CHECK(context_->CallChromeHiddenMethod("app.onGetAppNotifyChannelResponse",
arraysize(argv), argv, NULL));
}
void AppBindings::OnAppInstallStateResponse(
const std::string& state, int callback_id) {
v8::HandleScope handle_scope;
v8::Context::Scope context_scope(context_->v8_context());
v8::Handle<v8::Value> argv[2];
argv[0] = v8::String::New(state.c_str());
argv[1] = v8::Integer::New(callback_id);
CHECK(context_->CallChromeHiddenMethod("app.onInstallStateResponse",
arraysize(argv), argv, NULL));
}
......@@ -35,12 +35,15 @@ class AppBindings : public ChromeV8Extension, public ChromeV8ExtensionHandler {
v8::Handle<v8::Value> GetDetails(const v8::Arguments& args);
v8::Handle<v8::Value> GetDetailsForFrame(const v8::Arguments& args);
v8::Handle<v8::Value> GetAppNotifyChannel(const v8::Arguments& args);
v8::Handle<v8::Value> GetInstallState(const v8::Arguments& args);
v8::Handle<v8::Value> GetRunningState(const v8::Arguments& args);
v8::Handle<v8::Value> GetDetailsForFrameImpl(WebKit::WebFrame* frame);
void OnGetAppNotifyChannelResponse(const std::string& channel_id,
const std::string& error,
int callback_id);
void OnAppInstallStateResponse(const std::string& state, int callback_id);
DISALLOW_COPY_AND_ASSIGN(AppBindings);
};
......
......@@ -11,7 +11,8 @@ var app = {
getIsInstalled: appNatives.GetIsInstalled,
install: appNatives.Install,
getDetails: appNatives.GetDetails,
getDetailsForFrame: appNatives.GetDetailsForFrame
getDetailsForFrame: appNatives.GetDetailsForFrame,
runningState: appNatives.GetRunningState
};
// Tricky; "getIsInstalled" is actually exposed as the getter "isInstalled",
......@@ -30,6 +31,13 @@ var chromeHiddenApp = {
callbacks[callbackId](channelId, error);
delete callbacks[callbackId];
}
},
onInstallStateResponse: function(state, callbackId) {
if (callbackId) {
callbacks[callbackId](state);
delete callbacks[callbackId];
}
}
};
......@@ -53,6 +61,12 @@ var appNotifications = {
}
};
app.installState = function getInstallState(callback) {
var callbackId = nextCallbackId++;
callbacks[callbackId] = callback;
appNatives.GetInstallState(callbackId);
};
// These must match the names in InstallAppBindings() in
// extension_dispatcher.cc.
exports.chromeApp = app;
......
<script>
function testUnsuccessfulAccess() {
try {
chrome.app.getDetailsForFrame(frames[0]);
} catch (e) {
if (e.indexOf("Access denied") == 0)
return true;
else
throw e;
}
return false;
}
function getFrameURL(host) {
var result = "http://" + host;
if (location.port) {
result += ":";
result += location.port;
}
result += location.pathname;
return result;
}
var iframe = document.createElement("iframe");
iframe.src = getFrameURL("nonapp.com");
document.documentElement.appendChild(iframe);
</script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment