[Extensions] Add ExtensionNavigationThrottle unit tests

Right now, most of our web accessible resource tests are in
extension_protocols_unittest.cc. Unfortunately, this is a little too
high-level for the innards of the navigation throttle. Add a separate,
more fine-grained unittest for that.

BUG=None

Review-Url: https://codereview.chromium.org/2335553004
Cr-Commit-Position: refs/heads/master@{#421032}
diff --git a/chrome/browser/extensions/extension_navigation_throttle_unittest.cc b/chrome/browser/extensions/extension_navigation_throttle_unittest.cc
new file mode 100644
index 0000000..cd7a202
--- /dev/null
+++ b/chrome/browser/extensions/extension_navigation_throttle_unittest.cc
@@ -0,0 +1,194 @@
+// Copyright 2016 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 "base/strings/stringprintf.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "components/crx_file/id_util.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/web_contents_tester.h"
+#include "extensions/browser/extension_navigation_throttle.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using content::NavigationThrottle;
+
+namespace extensions {
+
+namespace {
+
+const char kAccessible[] = "accessible.html";
+const char kPrivate[] = "private.html";
+const char kAccessibleDir[] = "accessible_dir/*";
+const char kAccessibleDirResource[] = "accessible_dir/foo.html";
+
+class MockBrowserClient : public content::ContentBrowserClient {
+ public:
+  MockBrowserClient() {}
+  ~MockBrowserClient() override {}
+
+  // Only construct an ExtensionNavigationThrottle so that we can test it in
+  // isolation.
+  ScopedVector<NavigationThrottle> CreateThrottlesForNavigation(
+      content::NavigationHandle* handle) override {
+    ScopedVector<NavigationThrottle> throttles;
+    if (!handle->IsInMainFrame())  // Mirrors ChromeContentBrowserClient.
+      throttles.push_back(new ExtensionNavigationThrottle(handle));
+    return throttles;
+  }
+};
+
+}  // namespace
+
+class ExtensionNavigationThrottleUnitTest
+    : public ChromeRenderViewHostTestHarness {
+ public:
+  ExtensionNavigationThrottleUnitTest() {}
+  void SetUp() override {
+    ChromeRenderViewHostTestHarness::SetUp();
+    original_client_ = content::SetBrowserClientForTesting(&client_);
+    AddExtension();
+  }
+
+  void TearDown() override {
+    content::SetBrowserClientForTesting(original_client_);
+    ChromeRenderViewHostTestHarness::TearDown();
+  }
+
+  // Checks that trying to navigate the given |host| to |url| results in the
+  // |expected_result|.
+  void CheckTestCase(content::RenderFrameHost* host,
+                     const GURL& url,
+                     NavigationThrottle::ThrottleCheckResult expected_result) {
+    std::unique_ptr<content::NavigationHandle> handle =
+        content::NavigationHandle::CreateNavigationHandleForTesting(url, host);
+    EXPECT_EQ(expected_result, handle->CallWillStartRequestForTesting(
+                                   false, //not post
+                                   content::Referrer(), false,
+                                   ui::PAGE_TRANSITION_LINK, false))
+        << url;
+  }
+
+  const Extension* extension() { return extension_.get(); }
+  content::WebContentsTester* web_contents_tester() {
+    return content::WebContentsTester::For(web_contents());
+  }
+  content::RenderFrameHostTester* render_frame_host_tester(
+      content::RenderFrameHost* host) {
+    return content::RenderFrameHostTester::For(host);
+  }
+
+ private:
+  // Constructs an extension with accessible.html and accessible_dir/* as
+  // accessible resources.
+  void AddExtension() {
+    DictionaryBuilder manifest;
+    manifest.Set("name", "ext")
+        .Set("description", "something")
+        .Set("version", "0.1")
+        .Set("manifest_version", 2)
+        .Set("web_accessible_resources",
+             ListBuilder().Append(kAccessible).Append(kAccessibleDir).Build());
+    extension_ = ExtensionBuilder()
+                     .SetManifest(manifest.Build())
+                     .SetID(crx_file::id_util::GenerateId("foo"))
+                     .Build();
+    ASSERT_TRUE(extension_);
+    ExtensionRegistry::Get(browser_context())->AddEnabled(extension_.get());
+  }
+
+  scoped_refptr<const Extension> extension_;
+  MockBrowserClient client_;
+  content::ContentBrowserClient* original_client_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExtensionNavigationThrottleUnitTest);
+};
+
+// Tests the basic case of an external web page embedding an extension resource.
+TEST_F(ExtensionNavigationThrottleUnitTest, ExternalWebPage) {
+  web_contents_tester()->NavigateAndCommit(GURL("http://example.com"));
+  content::RenderFrameHost* child =
+      render_frame_host_tester(main_rfh())->AppendChild("child");
+
+  // Only resources specified in web_accessible_resources should be allowed.
+  CheckTestCase(child, extension()->GetResourceURL(kPrivate),
+                NavigationThrottle::BLOCK_REQUEST);
+  CheckTestCase(child, extension()->GetResourceURL(kAccessible),
+                NavigationThrottle::PROCEED);
+  CheckTestCase(child, extension()->GetResourceURL(kAccessibleDirResource),
+                NavigationThrottle::PROCEED);
+}
+
+// Tests that the owning extension can access any of its resources.
+TEST_F(ExtensionNavigationThrottleUnitTest, SameExtension) {
+  web_contents_tester()->NavigateAndCommit(
+      extension()->GetResourceURL("trusted.html"));
+  content::RenderFrameHost* child =
+      render_frame_host_tester(main_rfh())->AppendChild("child");
+
+  // All resources should be allowed.
+  CheckTestCase(child, extension()->GetResourceURL(kPrivate),
+                NavigationThrottle::PROCEED);
+  CheckTestCase(child, extension()->GetResourceURL(kAccessible),
+                NavigationThrottle::PROCEED);
+  CheckTestCase(child, extension()->GetResourceURL(kAccessibleDirResource),
+                NavigationThrottle::PROCEED);
+}
+
+// Tests that if any of the ancestors are an external web page, we restrict
+// the resources.
+TEST_F(ExtensionNavigationThrottleUnitTest, WebPageAncestor) {
+  web_contents_tester()->NavigateAndCommit(GURL("http://example.com"));
+  content::RenderFrameHost* child =
+      render_frame_host_tester(main_rfh())->AppendChild("subframe1");
+  GURL url = extension()->GetResourceURL(kAccessible);
+  render_frame_host_tester(child)->SimulateNavigationStart(url);
+  render_frame_host_tester(child)->SimulateNavigationCommit(url);
+  content::RenderFrameHost* grand_child =
+      render_frame_host_tester(child)->AppendChild("grandchild");
+
+  // Even though the immediate parent is a trusted frame, we should restrict
+  // to web_accessible_resources since the grand parent is external.
+  CheckTestCase(grand_child, extension()->GetResourceURL(kPrivate),
+                NavigationThrottle::BLOCK_REQUEST);
+  CheckTestCase(grand_child, extension()->GetResourceURL(kAccessible),
+                NavigationThrottle::PROCEED);
+  CheckTestCase(grand_child,
+                extension()->GetResourceURL(kAccessibleDirResource),
+                NavigationThrottle::PROCEED);
+}
+
+// Tests that requests to disabled or non-existent extensions are blocked.
+TEST_F(ExtensionNavigationThrottleUnitTest, InvalidExtension) {
+  web_contents_tester()->NavigateAndCommit(GURL("http://example.com"));
+  content::RenderFrameHost* child =
+      render_frame_host_tester(main_rfh())->AppendChild("child");
+
+  ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
+  registry->RemoveEnabled(extension()->id());
+  registry->AddDisabled(extension());
+
+  // Since the extension is disabled, all requests should be blocked.
+  CheckTestCase(child, extension()->GetResourceURL(kPrivate),
+                NavigationThrottle::BLOCK_REQUEST);
+  CheckTestCase(child, extension()->GetResourceURL(kAccessible),
+                NavigationThrottle::BLOCK_REQUEST);
+  CheckTestCase(child, extension()->GetResourceURL(kAccessibleDirResource),
+                NavigationThrottle::BLOCK_REQUEST);
+
+  std::string second_id = crx_file::id_util::GenerateId("bar");
+  ASSERT_NE(second_id, extension()->id());
+  GURL invalid_url(base::StringPrintf("chrome-extension://%s/accessible.html",
+                                      second_id.c_str()));
+  // Requests to non-existent extensions should be blocked.
+  CheckTestCase(child, invalid_url, NavigationThrottle::BLOCK_REQUEST);
+}
+
+}  // namespace extensions