chromium/chrome/browser/extensions/extension_nacl_browsertest.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/chrome_browser_main_extra_parts_nacl_deprecation.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/nacl/common/nacl_constants.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/plugin_service.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/webplugininfo.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/test/test_extension_dir.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"

using content::PluginService;
using content::WebContents;
using extensions::Extension;
using extensions::Manifest;
using extensions::mojom::ManifestLocation;

namespace {

const char kExtensionId[] = "bjjcibdiodkkeanflmiijlcfieiemced";

// This class tests that the Native Client plugin is blocked unless the
// .nexe is part of an extension from the Chrome Webstore.
class NaClExtensionTest : public extensions::ExtensionBrowserTest {
 public:
  NaClExtensionTest() { feature_list_.InitAndEnableFeature(kNaclAllow); }

  void SetUpOnMainThread() override {
    extensions::ExtensionBrowserTest::SetUpOnMainThread();
    host_resolver()->AddRule("*", "127.0.0.1");
    ASSERT_TRUE(embedded_test_server()->Start());
  }

 protected:
  enum InstallType {
    INSTALL_TYPE_COMPONENT,
    INSTALL_TYPE_UNPACKED,
    INSTALL_TYPE_FROM_WEBSTORE,
    INSTALL_TYPE_NON_WEBSTORE,
  };

  enum PluginType {
    PLUGIN_TYPE_NONE = 0,
    PLUGIN_TYPE_EMBED = 1,
    PLUGIN_TYPE_CONTENT_HANDLER = 2,
    PLUGIN_TYPE_ALL = PLUGIN_TYPE_EMBED |
                      PLUGIN_TYPE_CONTENT_HANDLER,
  };

  const Extension* InstallExtension(const base::FilePath& file_path,
                                    InstallType install_type) {
    extensions::ExtensionRegistry* registry = extension_registry();
    const Extension* extension = nullptr;
    switch (install_type) {
      case INSTALL_TYPE_COMPONENT:
        if (LoadExtensionAsComponent(file_path)) {
          extension = registry->enabled_extensions().GetByID(kExtensionId);
        }
        break;

      case INSTALL_TYPE_UNPACKED:
        // Install the extension from a folder so it's unpacked.
        if (LoadExtension(file_path)) {
          extension = registry->enabled_extensions().GetByID(kExtensionId);
        }
        break;

      case INSTALL_TYPE_FROM_WEBSTORE:
        // Install native_client.crx from the webstore.
        if (InstallExtensionFromWebstore(file_path, 1)) {
          extension = registry->enabled_extensions().GetByID(
              last_loaded_extension_id());
        }
        break;

      case INSTALL_TYPE_NON_WEBSTORE:
        // Install native_client.crx but not from the webstore.
        if (extensions::ExtensionBrowserTest::InstallExtension(file_path, 1)) {
          extension = registry->enabled_extensions().GetByID(
              last_loaded_extension_id());
        }
        break;
    }
    return extension;
  }

  const Extension* InstallExtension(InstallType install_type) {
    base::FilePath file_path = test_data_dir_.AppendASCII("native_client");
    return InstallExtension(file_path, install_type);
  }

  const Extension* InstallHostedApp() {
    base::FilePath file_path = test_data_dir_.AppendASCII(
        "native_client_hosted_app");
    return InstallExtension(file_path, INSTALL_TYPE_FROM_WEBSTORE);
  }

  bool IsNaClPluginLoaded() {
    // Make sure plugins are loaded off disk first.
    {
      base::RunLoop run_loop;
      PluginService::GetInstance()->GetPlugins(base::BindLambdaForTesting(
          [&](const std::vector<content::WebPluginInfo>&) {
            run_loop.Quit();
          }));
      run_loop.Run();
    }

    static const base::FilePath path(nacl::kInternalNaClPluginFileName);
    content::WebPluginInfo info;
    return PluginService::GetInstance()->GetPluginInfoByPath(path, &info);
  }

  void CheckPluginsCreated(const GURL& url, PluginType expected_to_succeed) {
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
    // Don't run tests if the NaCl plugin isn't loaded.
    if (!IsNaClPluginLoaded())
      return;

    WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    bool embedded_plugin_created =
        content::EvalJs(web_contents, "EmbeddedPluginCreated();").ExtractBool();
    bool content_handler_plugin_created =
        content::EvalJs(web_contents, "ContentHandlerPluginCreated();")
            .ExtractBool();

    EXPECT_EQ(embedded_plugin_created,
              (expected_to_succeed & PLUGIN_TYPE_EMBED) != 0);
    EXPECT_EQ(content_handler_plugin_created,
              (expected_to_succeed & PLUGIN_TYPE_CONTENT_HANDLER) != 0);
  }

  void CheckPluginsCreated(const Extension* extension,
                           PluginType expected_to_succeed) {
    CheckPluginsCreated(extension->GetResourceURL("test.html"),
                        expected_to_succeed);
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Test that the NaCl plugin isn't blocked for Webstore extensions.
// Disabled: http://crbug.com/319892
IN_PROC_BROWSER_TEST_F(NaClExtensionTest, DISABLED_WebStoreExtension) {
  const Extension* extension = InstallExtension(INSTALL_TYPE_FROM_WEBSTORE);
  ASSERT_TRUE(extension);
  CheckPluginsCreated(extension, PLUGIN_TYPE_ALL);
}

// Test that the NaCl plugin is blocked for non-Webstore extensions.
// Disabled: http://crbug.com/319892
IN_PROC_BROWSER_TEST_F(NaClExtensionTest, DISABLED_NonWebStoreExtension) {
  const Extension* extension = InstallExtension(INSTALL_TYPE_NON_WEBSTORE);
  ASSERT_TRUE(extension);
  CheckPluginsCreated(extension, PLUGIN_TYPE_NONE);
}

// Test that the NaCl plugin isn't blocked for component extensions.
// Disabled: http://crbug.com/319892
IN_PROC_BROWSER_TEST_F(NaClExtensionTest, DISABLED_ComponentExtension) {
  const Extension* extension = InstallExtension(INSTALL_TYPE_COMPONENT);
  ASSERT_TRUE(extension);
  ASSERT_EQ(extension->location(), ManifestLocation::kComponent);
  CheckPluginsCreated(extension, PLUGIN_TYPE_ALL);
}

// Test that the NaCl plugin isn't blocked for unpacked extensions.
// Disabled: http://crbug.com/319892
IN_PROC_BROWSER_TEST_F(NaClExtensionTest, DISABLED_UnpackedExtension) {
  const Extension* extension = InstallExtension(INSTALL_TYPE_UNPACKED);
  ASSERT_TRUE(extension);
  ASSERT_EQ(extension->location(), ManifestLocation::kUnpacked);
  CheckPluginsCreated(extension, PLUGIN_TYPE_ALL);
}

// Test that the NaCl plugin is blocked for non chrome-extension urls, except
// if it's a content (MIME type) handler.
// Disabled: http://crbug.com/319892
IN_PROC_BROWSER_TEST_F(NaClExtensionTest, DISABLED_NonExtensionScheme) {
  const Extension* extension = InstallExtension(INSTALL_TYPE_FROM_WEBSTORE);
  ASSERT_TRUE(extension);
  CheckPluginsCreated(
      embedded_test_server()->GetURL("/extensions/native_client/test.html"),
      PLUGIN_TYPE_CONTENT_HANDLER);
}

// Test that NaCl plugin isn't blocked for hosted app URLs.
IN_PROC_BROWSER_TEST_F(NaClExtensionTest, DISABLED_HostedApp) {
  GURL url =
      embedded_test_server()->GetURL("/extensions/native_client/test.html");
  GURL::Replacements replace_host;
  replace_host.SetHostStr("localhost");
  replace_host.ClearPort();
  url = url.ReplaceComponents(replace_host);

  const Extension* extension = InstallHostedApp();
  ASSERT_TRUE(extension);
  CheckPluginsCreated(url, PLUGIN_TYPE_ALL);
}

// Verify that there is no renderer crash when PNaCl plugin is loaded in a
// subframe with a remote parent / main frame.  This is a regression test
// for https://crbug.com/728295.
IN_PROC_BROWSER_TEST_F(NaClExtensionTest, MainFrameIsRemote) {
  // The test tries to load a PNaCl plugin into an *extension* frame to avoid
  // running into the following error: "Only unpacked extensions and apps
  // installed from the Chrome Web Store can load NaCl modules without enabling
  // Native Client in about:flags."
  extensions::TestExtensionDir ext_dir;
  ext_dir.WriteFile(FILE_PATH_LITERAL("subframe.html"),
                    "<html><body>Extension frame</body></html>");
  ext_dir.WriteManifest(
      R"(
      {
        "name": "ChromeSitePerProcessTest.MainFrameIsRemote",
        "version": "0.1",
        "manifest_version": 2,
        "web_accessible_resources": [ "subframe.html" ]
      }
      )");
  const extensions::Extension* extension =
      LoadExtension(ext_dir.UnpackedPath());

  // Navigate to a page with an iframe.
  GURL main_url(embedded_test_server()->GetURL("a.com", "/iframe.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));

  // Navigate the subframe to the extension's html file.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(content::NavigateIframeToURL(
      web_contents, "test", extension->GetResourceURL("subframe.html")));

  // Sanity check - the test setup should cause main frame and subframe to be in
  // a different process.
  content::RenderFrameHost* subframe = ChildFrameAt(web_contents, 0);
  EXPECT_NE(web_contents->GetPrimaryMainFrame()->GetProcess(),
            subframe->GetProcess());

  // Insert a plugin element into the subframe.  Before the fix from
  // https://crrev.com/2932703005 this would have trigerred a crash reported in
  // https://crbug.com/728295.
  std::string script = R"(
      var embed = document.createElement("embed");
      embed.id = "test_nexe";
      embed.name = "nacl_module";
      embed.type = "application/x-pnacl";
      embed.src = "doesnt-exist.nmf";
      new Promise(resolve => {
        embed.addEventListener('error', function() {
            resolve(true);
        });
        document.body.appendChild(embed);
      });
       )";
  EXPECT_EQ(true, EvalJs(subframe, script));

  // If we get here, then it means that the renderer didn't crash (the crash
  // would have prevented the "error" event from firing and so
  // EvalJs above wouldn't return).
}

}  // namespace