910e62b5创建于 1月15日历史提交
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/renderer/extensions/api/tabs_hooks_delegate.h"

#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "extensions/renderer/api/messaging/message_target.h"
#include "extensions/renderer/api/messaging/messaging_util.h"
#include "extensions/renderer/api/messaging/native_renderer_messaging_service.h"
#include "extensions/renderer/api/messaging/send_message_tester.h"
#include "extensions/renderer/bindings/api_binding_test_util.h"
#include "extensions/renderer/native_extension_bindings_system.h"
#include "extensions/renderer/native_extension_bindings_system_test_base.h"
#include "extensions/renderer/script_context.h"

namespace extensions {

namespace {

void CallAPIAndExpectError(v8::Local<v8::Context> context,
                           const char* method_name,
                           const char* args) {
  SCOPED_TRACE(base::StringPrintf("Args: `%s`", args));
  constexpr char kTemplate[] = "(function() { chrome.tabs.%s(%s); })";

  v8::Isolate* isolate = v8::Isolate::GetCurrent();

  // Just verify some error was thrown. Expecting the exact error message
  // tends to rely too much on our argument spec code, which is tested
  // separately.
  v8::Local<v8::Function> function = FunctionFromString(
      context, base::StringPrintf(kTemplate, method_name, args));
  v8::TryCatch try_catch(isolate);
  v8::MaybeLocal<v8::Value> result =
      function->Call(context, v8::Undefined(isolate), 0, nullptr);
  EXPECT_TRUE(result.IsEmpty());
  EXPECT_TRUE(try_catch.HasCaught());
}

}  // namespace

class TabsHooksDelegateTest : public NativeExtensionBindingsSystemUnittest {
 public:
  TabsHooksDelegateTest() = default;

  TabsHooksDelegateTest(const TabsHooksDelegateTest&) = delete;
  TabsHooksDelegateTest& operator=(const TabsHooksDelegateTest&) = delete;

  ~TabsHooksDelegateTest() override = default;

  // NativeExtensionBindingsSystemUnittest:
  void SetUp() override {
    NativeExtensionBindingsSystemUnittest::SetUp();
    messaging_service_ =
        std::make_unique<NativeRendererMessagingService>(bindings_system());

    bindings_system()->api_system()->RegisterHooksDelegate(
        "tabs", std::make_unique<TabsHooksDelegate>(messaging_service_.get()));

    scoped_refptr<const Extension> mutable_extension = BuildExtension();
    RegisterExtension(mutable_extension);
    extension_ = mutable_extension;

    v8::HandleScope handle_scope(isolate());
    v8::Local<v8::Context> context = MainContext();

    script_context_ =
        CreateScriptContext(context, mutable_extension.get(),
                            mojom::ContextType::kPrivilegedExtension);
    script_context_->set_url(extension_->url());
    bindings_system()->UpdateBindingsForContext(script_context_);
  }
  void TearDown() override {
    script_context_ = nullptr;
    extension_ = nullptr;
    messaging_service_.reset();
    NativeExtensionBindingsSystemUnittest::TearDown();
  }
  bool UseStrictIPCMessageSender() override { return true; }

  virtual scoped_refptr<const Extension> BuildExtension() {
    // TODO(https://crbug.com/40804030): Update this to use MV3.
    // SendMessageTester needs to be updated since runtime.sendMessage() now
    // returns a promise.
    return ExtensionBuilder("foo").SetManifestVersion(2).Build();
  }

  NativeRendererMessagingService* messaging_service() {
    return messaging_service_.get();
  }
  ScriptContext* script_context() { return script_context_; }
  const Extension* extension() { return extension_.get(); }

 private:
  std::unique_ptr<NativeRendererMessagingService> messaging_service_;

  raw_ptr<ScriptContext> script_context_ = nullptr;
  scoped_refptr<const Extension> extension_;
};

TEST_F(TabsHooksDelegateTest, Connect) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  SendMessageTester tester(ipc_message_sender(), script_context(), 0, "tabs");

  tester.TestConnect("1", "",
                     MessageTarget::ForTab(1, messaging_util::kNoFrameId));
  tester.TestConnect("-0", "",
                     MessageTarget::ForTab(0, messaging_util::kNoFrameId));
  tester.TestConnect("4, {name: 'channel'}", "channel",
                     MessageTarget::ForTab(4, messaging_util::kNoFrameId));
  tester.TestConnect("9, {frameId: null}", "",
                     MessageTarget::ForTab(9, messaging_util::kNoFrameId));
  tester.TestConnect("9, {frameId: 16}", "", MessageTarget::ForTab(9, 16));
  tester.TestConnect("25, {}", "",
                     MessageTarget::ForTab(25, messaging_util::kNoFrameId));

  CallAPIAndExpectError(context, "connect", "36, {includeTlsChannelId: true}");
}

TEST_F(TabsHooksDelegateTest, SendMessage) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  SendMessageTester tester(ipc_message_sender(), script_context(), 0, "tabs");

  tester.TestSendMessage("1, ''", R"("")",
                         MessageTarget::ForTab(1, messaging_util::kNoFrameId),
                         SendMessageTester::CLOSED);

  constexpr char kStandardMessage[] = R"({"data":"hello"})";
  tester.TestSendMessage("1, {data: 'hello'}", kStandardMessage,
                         MessageTarget::ForTab(1, messaging_util::kNoFrameId),
                         SendMessageTester::CLOSED);
  tester.TestSendMessage("-0, {data: 'hello'}", kStandardMessage,
                         MessageTarget::ForTab(0, messaging_util::kNoFrameId),
                         SendMessageTester::CLOSED);
  tester.TestSendMessage("1, {data: 'hello'}, function() {}", kStandardMessage,
                         MessageTarget::ForTab(1, messaging_util::kNoFrameId),
                         SendMessageTester::OPEN);
  tester.TestSendMessage("1, {data: 'hello'}, {frameId: null}",
                         kStandardMessage,
                         MessageTarget::ForTab(1, messaging_util::kNoFrameId),
                         SendMessageTester::CLOSED);
  tester.TestSendMessage("1, {data: 'hello'}, {frameId: 10}", kStandardMessage,
                         MessageTarget::ForTab(1, 10),
                         SendMessageTester::CLOSED);
  tester.TestSendMessage("1, {data: 'hello'}, {frameId: 10}, function() {}",
                         kStandardMessage, MessageTarget::ForTab(1, 10),
                         SendMessageTester::OPEN);

  CallAPIAndExpectError(context, "sendMessage",
                        "1, 'hello', {includeTlsChannelId: true}");
}

TEST_F(TabsHooksDelegateTest, SendRequest) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  SendMessageTester tester(ipc_message_sender(), script_context(), 0, "tabs");

  tester.TestSendRequest("1, ''", R"("")",
                         MessageTarget::ForTab(1, messaging_util::kNoFrameId),
                         SendMessageTester::CLOSED);

  constexpr char kStandardMessage[] = R"({"data":"hello"})";
  tester.TestSendRequest("1, {data: 'hello'}", kStandardMessage,
                         MessageTarget::ForTab(1, messaging_util::kNoFrameId),
                         SendMessageTester::CLOSED);
  tester.TestSendRequest("1, {data: 'hello'}, function() {}", kStandardMessage,
                         MessageTarget::ForTab(1, messaging_util::kNoFrameId),
                         SendMessageTester::OPEN);

  CallAPIAndExpectError(context, "sendRequest", "1, 'hello', {frameId: 10}");
}

class TabsHooksDelegateMV3Test : public TabsHooksDelegateTest {
 public:
  TabsHooksDelegateMV3Test() = default;
  ~TabsHooksDelegateMV3Test() override = default;

  scoped_refptr<const Extension> BuildExtension() override {
    return ExtensionBuilder("foo")
        .SetManifestKey("manifest_version", 3)
        .Build();
  }
};

TEST_F(TabsHooksDelegateMV3Test, SendMessageUsingPromise) {
  v8::HandleScope handle_scope(isolate());

  SendMessageTester tester(ipc_message_sender(), script_context(), 0, "tabs");

  // The port remains open here after the call because in MV3 we return a
  // promise if the callback parameter is omitted, so we can't use the presence/
  // lack of the callback to determine if the caller is/isn't going to handle
  // the response.
  MessageTarget self_target = MessageTarget::ForExtension(extension()->id());
  tester.TestSendMessage("1, ''", R"("")",
                         MessageTarget::ForTab(1, messaging_util::kNoFrameId),
                         SendMessageTester::OPEN);

  constexpr char kStandardMessage[] = R"({"data":"hello"})";
  {
    // Calling sendMessage with a callback should result in no value returned.
    v8::Local<v8::Value> result = tester.TestSendMessage(
        "1, {data: 'hello'}, function() {}", kStandardMessage,
        MessageTarget::ForTab(1, messaging_util::kNoFrameId),
        SendMessageTester::OPEN);
    EXPECT_TRUE(result->IsUndefined());
  }

  {
    // Calling sendMessage without a callback should result in a promise
    // returned.
    v8::Local<v8::Value> result = tester.TestSendMessage(
        "1, {data: 'hello'}", kStandardMessage,
        MessageTarget::ForTab(1, messaging_util::kNoFrameId),
        SendMessageTester::OPEN);
    v8::Local<v8::Promise> promise;
    ASSERT_TRUE(GetValueAs(result, &promise));
    EXPECT_EQ(v8::Promise::kPending, promise->State());
  }
}

}  // namespace extensions