1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <memory>
6 #include <string>
7
8 #include "base/bind.h"
9 #include "base/command_line.h"
10 #include "base/json/json_reader.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/values.h"
14 #include "chrome/browser/extensions/extension_browsertest.h"
15 #include "chrome/browser/extensions/extension_service.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/tabs/tab_strip_model.h"
19 #include "chrome/test/base/ui_test_utils.h"
20 #include "content/public/browser/web_contents.h"
21 #include "content/public/test/browser_test.h"
22 #include "content/public/test/browser_test_utils.h"
23 #include "extensions/common/extension.h"
24 #include "extensions/common/manifest.h"
25 #include "net/dns/mock_host_resolver.h"
26 #include "net/test/embedded_test_server/embedded_test_server.h"
27 #include "url/gurl.h"
28
29 using extensions::Extension;
30
31 class ChromeAppAPITest : public extensions::ExtensionBrowserTest {
SetUpOnMainThread()32 void SetUpOnMainThread() override {
33 extensions::ExtensionBrowserTest::SetUpOnMainThread();
34 host_resolver()->AddRule("*", "127.0.0.1");
35 ASSERT_TRUE(embedded_test_server()->Start());
36 }
37
38 protected:
IsAppInstalledInMainFrame()39 bool IsAppInstalledInMainFrame() {
40 return IsAppInstalledInFrame(
41 browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame());
42 }
IsAppInstalledInIFrame()43 bool IsAppInstalledInIFrame() {
44 return IsAppInstalledInFrame(GetIFrame());
45 }
IsAppInstalledInFrame(content::RenderFrameHost * frame)46 bool IsAppInstalledInFrame(content::RenderFrameHost* frame) {
47 const char kGetAppIsInstalled[] =
48 "window.domAutomationController.send(window.chrome.app.isInstalled);";
49 bool result;
50 CHECK(content::ExecuteScriptAndExtractBool(frame,
51 kGetAppIsInstalled,
52 &result));
53 return result;
54 }
55
InstallStateInMainFrame()56 std::string InstallStateInMainFrame() {
57 return InstallStateInFrame(
58 browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame());
59 }
InstallStateInIFrame()60 std::string InstallStateInIFrame() {
61 return InstallStateInFrame(GetIFrame());
62 }
InstallStateInFrame(content::RenderFrameHost * frame)63 std::string InstallStateInFrame(content::RenderFrameHost* frame) {
64 const char kGetAppInstallState[] =
65 "window.chrome.app.installState("
66 " function(s) { window.domAutomationController.send(s); });";
67 std::string result;
68 CHECK(content::ExecuteScriptAndExtractString(frame,
69 kGetAppInstallState,
70 &result));
71 return result;
72 }
73
RunningStateInMainFrame()74 std::string RunningStateInMainFrame() {
75 return RunningStateInFrame(
76 browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame());
77 }
RunningStateInIFrame()78 std::string RunningStateInIFrame() {
79 return RunningStateInFrame(GetIFrame());
80 }
RunningStateInFrame(content::RenderFrameHost * frame)81 std::string RunningStateInFrame(content::RenderFrameHost* frame) {
82 const char kGetAppRunningState[] =
83 "window.domAutomationController.send("
84 " window.chrome.app.runningState());";
85 std::string result;
86 CHECK(content::ExecuteScriptAndExtractString(frame,
87 kGetAppRunningState,
88 &result));
89 return result;
90 }
91
92 private:
GetIFrame()93 content::RenderFrameHost* GetIFrame() {
94 return content::FrameMatchingPredicate(
95 browser()->tab_strip_model()->GetActiveWebContents(),
96 base::BindRepeating(&content::FrameIsChildOfMainFrame));
97 }
98 };
99
IN_PROC_BROWSER_TEST_F(ChromeAppAPITest,IsInstalled)100 IN_PROC_BROWSER_TEST_F(ChromeAppAPITest, IsInstalled) {
101 GURL app_url =
102 embedded_test_server()->GetURL("app.com", "/extensions/test_file.html");
103 GURL non_app_url = embedded_test_server()->GetURL(
104 "nonapp.com", "/extensions/test_file.html");
105
106 // Before the app is installed, app.com does not think that it is installed
107 ui_test_utils::NavigateToURL(browser(), app_url);
108 EXPECT_FALSE(IsAppInstalledInMainFrame());
109
110 // Load an app which includes app.com in its extent.
111 const Extension* extension = LoadExtension(
112 test_data_dir_.AppendASCII("app_dot_com_app"));
113 ASSERT_TRUE(extension);
114
115 // Even after the app is installed, the existing app.com tab is not in an
116 // app process, so chrome.app.isInstalled should return false.
117 EXPECT_FALSE(IsAppInstalledInMainFrame());
118
119 // Test that a non-app page has chrome.app.isInstalled = false.
120 ui_test_utils::NavigateToURL(browser(), non_app_url);
121 EXPECT_FALSE(IsAppInstalledInMainFrame());
122
123 // Test that a non-app page returns null for chrome.app.getDetails().
124 const char kGetAppDetails[] =
125 "window.domAutomationController.send("
126 " JSON.stringify(window.chrome.app.getDetails()));";
127 std::string result;
128 ASSERT_TRUE(
129 content::ExecuteScriptAndExtractString(
130 browser()->tab_strip_model()->GetActiveWebContents(),
131 kGetAppDetails,
132 &result));
133 EXPECT_EQ("null", result);
134
135 // Check that an app page has chrome.app.isInstalled = true.
136 ui_test_utils::NavigateToURL(browser(), app_url);
137 EXPECT_TRUE(IsAppInstalledInMainFrame());
138
139 // Check that an app page returns the correct result for
140 // chrome.app.getDetails().
141 ui_test_utils::NavigateToURL(browser(), app_url);
142 ASSERT_TRUE(
143 content::ExecuteScriptAndExtractString(
144 browser()->tab_strip_model()->GetActiveWebContents(),
145 kGetAppDetails,
146 &result));
147 std::unique_ptr<base::DictionaryValue> app_details(
148 static_cast<base::DictionaryValue*>(
149 base::JSONReader::ReadDeprecated(result).release()));
150 // extension->manifest() does not contain the id.
151 app_details->Remove("id", NULL);
152 EXPECT_TRUE(app_details.get());
153 EXPECT_TRUE(app_details->Equals(extension->manifest()->value()));
154
155 // Try to change app.isInstalled. Should silently fail, so
156 // that isInstalled should have the initial value.
157 ASSERT_TRUE(
158 content::ExecuteScriptAndExtractString(
159 browser()->tab_strip_model()->GetActiveWebContents(),
160 "window.domAutomationController.send("
161 " function() {"
162 " var value = window.chrome.app.isInstalled;"
163 " window.chrome.app.isInstalled = !value;"
164 " if (window.chrome.app.isInstalled == value) {"
165 " return 'true';"
166 " } else {"
167 " return 'false';"
168 " }"
169 " }()"
170 ");",
171 &result));
172
173 // Should not be able to alter window.chrome.app.isInstalled from javascript";
174 EXPECT_EQ("true", result);
175 }
176
177 // Test accessing app.isInstalled when the context has been invalidated (e.g.
178 // by removing the frame). Regression test for https://crbug.com/855853.
IN_PROC_BROWSER_TEST_F(ChromeAppAPITest,IsInstalledFromRemovedFrame)179 IN_PROC_BROWSER_TEST_F(ChromeAppAPITest, IsInstalledFromRemovedFrame) {
180 GURL app_url =
181 embedded_test_server()->GetURL("app.com", "/extensions/test_file.html");
182 const Extension* extension =
183 LoadExtension(test_data_dir_.AppendASCII("app_dot_com_app"));
184 ASSERT_TRUE(extension);
185 ui_test_utils::NavigateToURL(browser(), app_url);
186
187 constexpr char kScript[] =
188 R"(var i = document.createElement('iframe');
189 i.onload = function() {
190 var frameApp = i.contentWindow.chrome.app;
191 document.body.removeChild(i);
192 var isInstalled = frameApp.isInstalled;
193 window.domAutomationController.send(
194 isInstalled === undefined);
195 };
196 i.src = '%s';
197 document.body.appendChild(i);)";
198 bool result = false;
199 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
200 browser()->tab_strip_model()->GetActiveWebContents(),
201 base::StringPrintf(kScript, app_url.spec().c_str()), &result));
202 EXPECT_TRUE(result);
203 }
204
IN_PROC_BROWSER_TEST_F(ChromeAppAPITest,InstallAndRunningState)205 IN_PROC_BROWSER_TEST_F(ChromeAppAPITest, InstallAndRunningState) {
206 GURL app_url = embedded_test_server()->GetURL(
207 "app.com", "/extensions/get_app_details_for_frame.html");
208 GURL non_app_url = embedded_test_server()->GetURL(
209 "nonapp.com", "/extensions/get_app_details_for_frame.html");
210
211 // Before the app is installed, app.com does not think that it is installed
212 ui_test_utils::NavigateToURL(browser(), app_url);
213
214 EXPECT_EQ("not_installed", InstallStateInMainFrame());
215 EXPECT_EQ("cannot_run", RunningStateInMainFrame());
216 EXPECT_FALSE(IsAppInstalledInMainFrame());
217
218 const Extension* extension = LoadExtension(
219 test_data_dir_.AppendASCII("app_dot_com_app"));
220 ASSERT_TRUE(extension);
221
222 EXPECT_EQ("installed", InstallStateInMainFrame());
223 EXPECT_EQ("ready_to_run", RunningStateInMainFrame());
224 EXPECT_FALSE(IsAppInstalledInMainFrame());
225
226 // Reloading the page should put the tab in an app process.
227 ui_test_utils::NavigateToURL(browser(), app_url);
228 EXPECT_EQ("installed", InstallStateInMainFrame());
229 EXPECT_EQ("running", RunningStateInMainFrame());
230 EXPECT_TRUE(IsAppInstalledInMainFrame());
231
232 // Disable the extension and verify the state.
233 extensions::ExtensionService* service =
234 extensions::ExtensionSystem::Get(browser()->profile())
235 ->extension_service();
236 service->DisableExtension(
237 extension->id(),
238 extensions::disable_reason::DISABLE_PERMISSIONS_INCREASE);
239 ui_test_utils::NavigateToURL(browser(), app_url);
240
241 EXPECT_EQ("disabled", InstallStateInMainFrame());
242 EXPECT_EQ("cannot_run", RunningStateInMainFrame());
243 EXPECT_FALSE(IsAppInstalledInMainFrame());
244
245 service->EnableExtension(extension->id());
246 EXPECT_EQ("installed", InstallStateInMainFrame());
247 EXPECT_EQ("ready_to_run", RunningStateInMainFrame());
248 EXPECT_FALSE(IsAppInstalledInMainFrame());
249
250 // The non-app URL should still not be installed or running.
251 ui_test_utils::NavigateToURL(browser(), non_app_url);
252
253 EXPECT_EQ("not_installed", InstallStateInMainFrame());
254 EXPECT_EQ("cannot_run", RunningStateInMainFrame());
255 EXPECT_FALSE(IsAppInstalledInMainFrame());
256
257 EXPECT_EQ("installed", InstallStateInIFrame());
258 EXPECT_EQ("cannot_run", RunningStateInIFrame());
259
260 // With --site-per-process, the iframe on nonapp.com will currently swap
261 // processes and go into the hosted app process.
262 if (content::AreAllSitesIsolatedForTesting())
263 EXPECT_TRUE(IsAppInstalledInIFrame());
264 else
265 EXPECT_FALSE(IsAppInstalledInIFrame());
266 }
267
IN_PROC_BROWSER_TEST_F(ChromeAppAPITest,InstallAndRunningStateFrame)268 IN_PROC_BROWSER_TEST_F(ChromeAppAPITest, InstallAndRunningStateFrame) {
269 GURL app_url = embedded_test_server()->GetURL(
270 "app.com", "/extensions/get_app_details_for_frame_reversed.html");
271
272 // Check the install and running state of a non-app iframe running
273 // within an app.
274 ui_test_utils::NavigateToURL(browser(), app_url);
275
276 EXPECT_EQ("not_installed", InstallStateInIFrame());
277 EXPECT_EQ("cannot_run", RunningStateInIFrame());
278 EXPECT_FALSE(IsAppInstalledInIFrame());
279 }
280