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