// Copyright 2015 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/bind.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "content/browser/message_port_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/message_port_delegate.h"
#include "content/public/browser/message_port_provider.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"

namespace content {

// This test verifies the functionality of the Message Port Provider API.

// A mock class for testing message port provider.
class MockMessagePortDelegate : public MessagePortDelegate {
 public:
  // A container to hold received messages
  struct Message {
    int route_id;  // the routing id of the target port
    MessagePortMessage data;  // the message data
    std::vector<TransferredMessagePort> sent_ports;  // any transferred ports
  };

  typedef std::vector<Message> Messages;

  MockMessagePortDelegate() { }
  ~MockMessagePortDelegate() override { }

  // MessagePortDelegate implementation
  void SendMessage(
      int route_id,
      const MessagePortMessage& message,
      const std::vector<TransferredMessagePort>& sent_message_ports) override {
    Message m;
    m.route_id = route_id;
    m.data = message;
    m.sent_ports = sent_message_ports;
    messages_.push_back(m);
  }

  void SendMessagesAreQueued(int route_id) override { }

  const Messages& getReceivedMessages() {
    return messages_;
  }
 private:
  Messages messages_;

  DISALLOW_COPY_AND_ASSIGN(MockMessagePortDelegate);
};


class MessagePortProviderBrowserTest : public ContentBrowserTest {
};

// Verify that messages can be posted to main frame.
IN_PROC_BROWSER_TEST_F(MessagePortProviderBrowserTest, PostMessage) {
  const std::string data =
      "<!DOCTYPE html><html><body>"
      "    <script type=\"text/javascript\">"
      "        onmessage = function (e) { document.title = e.data; }"
      "   </script>"
      "</body></html>";
  const base::string16 target_origin(base::UTF8ToUTF16("http://baseurl"));
  const GURL base_url(target_origin);
  const GURL history_url;
  // Load data. Blocks until it is done.
  content::LoadDataWithBaseURL(shell(), history_url, data, base_url);
  const base::string16 source_origin(base::UTF8ToUTF16("source"));
  const base::string16 message(base::UTF8ToUTF16("success"));
  const std::vector<TransferredMessagePort> ports;
  content::TitleWatcher title_watcher(shell()->web_contents(), message);
  MessagePortProvider::PostMessageToFrame(shell()->web_contents(),
                                          source_origin,
                                          target_origin,
                                          message,
                                          ports);
  EXPECT_EQ(message, title_watcher.WaitAndGetTitle());
}

namespace {

void VerifyCreateChannelOnIOThread(base::WaitableEvent* event) {

  const base::char16 MESSAGE1[] = { 0x1000, 0 };
  const base::char16 MESSAGE2[] = { 0x1001, 0 };

  MockMessagePortDelegate delegate;
  int port1;
  int port2;

  MessagePortProvider::CreateMessageChannel(&delegate, &port1, &port2);
  MessagePortService* service = MessagePortService::GetInstance();
  // Send a message to port1 transferring no ports.
  std::vector<TransferredMessagePort> sent_ports;
  service->PostMessage(port1, MessagePortMessage(base::string16(MESSAGE1)),
                       sent_ports);
  // Verify that message is received
  const MockMessagePortDelegate::Messages& received =
      delegate.getReceivedMessages();
  EXPECT_EQ(received.size(), 1u);
  // Verify that message sent to port1 is received by entangled port, which is
  // port2.
  EXPECT_EQ(received[0].route_id, port2);
  EXPECT_EQ(received[0].data.message_as_string, MESSAGE1);
  EXPECT_EQ(received[0].sent_ports.size(), 0u);

  // Create a new channel, and transfer one of its ports to port2, making sure
  // the transferred port is received.
  TransferredMessagePort port3;
  TransferredMessagePort port4;
  MessagePortProvider::CreateMessageChannel(&delegate, &port3.id, &port4.id);
  sent_ports.push_back(port3);
  service->PostMessage(port1, MessagePortMessage(base::string16(MESSAGE2)),
                       sent_ports);
  EXPECT_EQ(received.size(), 2u);
  EXPECT_EQ(received[1].route_id, port2);
  EXPECT_EQ(received[1].data.message_as_string, MESSAGE2);
  EXPECT_EQ(received[1].sent_ports.size(), 1u);
  EXPECT_EQ(received[1].sent_ports[0].id, port3.id);

  event->Signal();
}

}  // namespace

// Verify that a message channel can be created and used for exchanging
// messages.
IN_PROC_BROWSER_TEST_F(MessagePortProviderBrowserTest, CreateChannel) {
  base::WaitableEvent event(true, false);
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      base::Bind(&VerifyCreateChannelOnIOThread, &event));
  event.Wait();
}

}  // namespace content
