blob: a756dd24591ec55d04175267ebf5844538fe7089 [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 <iomanip>
#include <map>
#include <string>
#include <vector>
#include <chromium/web/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <fuchsia/ui/app/cpp/fidl.h>
#include <fuchsia/ui/policy/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <gtest/gtest.h>
#include <lib/component/cpp/startup_context.h>
#include <lib/fdio/util.h>
#include <lib/fit/defer.h>
#include <lib/fit/function.h>
#include <lib/fsl/vmo/vector.h>
#include <lib/fxl/files/file.h>
#include <lib/fxl/logging.h>
#include <lib/fxl/strings/string_printf.h>
#include <lib/fxl/threading/thread.h>
#include <lib/gtest/real_loop_fixture.h>
#include <lib/zx/time.h>
#include "topaz/tests/web_runner_tests/test_server.h"
namespace {
// Max time to wait in failure cases before bailing.
constexpr zx::duration kTimeout = zx::sec(15);
std::map<uint32_t, size_t> Histogram(
const fuchsia::ui::scenic::ScreenshotData& screenshot) {
EXPECT_GT(screenshot.info.width, 0u);
EXPECT_GT(screenshot.info.height, 0u);
std::vector<uint8_t> data;
EXPECT_TRUE(fsl::VectorFromVmo(screenshot.data, &data))
<< "Failed to read screenshot";
std::map<uint32_t, size_t> histogram;
const uint32_t* bitmap = reinterpret_cast<const uint32_t*>(data.data());
const size_t size = screenshot.info.width * screenshot.info.height;
EXPECT_EQ(size * sizeof(uint32_t), data.size());
for (size_t i = 0; i < size; ++i) {
++histogram[bitmap[i]];
}
return histogram;
}
// Runs the test server on its own thread, with proper cleanup to prevent
// deadlock. |serve| must terminate after |server->Accept()| returns false.
auto ServeAsync(web_runner_tests::TestServer* server, fit::closure serve) {
auto server_thread = std::make_unique<fxl::Thread>(std::move(serve));
server_thread->Run();
// The socket must be closed before the thread goes out of scope so that any
// blocking |Accept| calls terminate so that |serve| can terminate.
return fit::defer(
[server, server_thread = std::move(server_thread)] { server->Close(); });
}
// Responds to a GET request, testing that the request looks as expected.
void MockHttpGetResponse(web_runner_tests::TestServer* server,
const char* resource) {
std::string expected_prefix = fxl::StringPrintf("GET /%s HTTP", resource);
std::vector<char> buf;
// |Read| requires preallocate (see sys/socket.h: read)
buf.resize(4096);
EXPECT_TRUE(server->Read(&buf));
EXPECT_GE(buf.size(), expected_prefix.size());
EXPECT_EQ(expected_prefix, std::string(buf.data(), expected_prefix.size()));
std::string content;
FXL_CHECK(files::ReadFileToString(fxl::StringPrintf("/pkg/data/%s", resource),
&content));
FXL_CHECK(server->WriteContent(content));
}
// Base fixture for web runner pixel tests, containing Scenic and presentation
// setup, and screenshot utilities.
class WebRunnerPixelTest : public gtest::RealLoopFixture {
protected:
WebRunnerPixelTest()
: context_(component::StartupContext::CreateFromStartupInfo()) {
scenic_ =
context_->ConnectToEnvironmentService<fuchsia::ui::scenic::Scenic>();
scenic_.set_error_handler([](zx_status_t status) {
FAIL() << "Lost connection to Scenic: " << status;
});
}
component::StartupContext* context() { return context_.get(); }
// Gets a view token for presentation by |RootPresenter|. See also
// garnet/examples/ui/hello_base_view
zx::eventpair CreatePresentationViewToken() {
zx::eventpair view_holder_token, view_token;
FXL_CHECK(zx::eventpair::create(0u, &view_holder_token, &view_token) ==
ZX_OK);
auto presenter =
context_->ConnectToEnvironmentService<fuchsia::ui::policy::Presenter>();
presenter.set_error_handler(
[](zx_status_t status) { FAIL() << "presenter: " << status; });
presenter->Present2(std::move(view_holder_token), nullptr);
return view_token;
}
bool ScreenshotUntil(
fit::function<bool(fuchsia::ui::scenic::ScreenshotData)> condition,
zx::duration timeout = kTimeout) {
zx::time start = zx::clock::get_monotonic();
while (zx::clock::get_monotonic() - start <= timeout) {
fuchsia::ui::scenic::ScreenshotData screenshot;
bool ok;
scenic_->TakeScreenshot(
[this, &screenshot, &ok](
fuchsia::ui::scenic::ScreenshotData screenshot_in, bool status) {
ok = status;
screenshot = std::move(screenshot_in);
QuitLoop();
});
if (!RunLoopWithTimeout(timeout) && ok &&
condition(std::move(screenshot))) {
return true;
}
}
return false;
}
void ExpectSolidColor(uint32_t argb) {
std::map<uint32_t, size_t> histogram;
FXL_LOG(INFO) << "Looking for color " << std::hex << argb;
EXPECT_TRUE(ScreenshotUntil(
[argb, &histogram](fuchsia::ui::scenic::ScreenshotData screenshot) {
histogram = Histogram(screenshot);
FXL_LOG(INFO) << histogram[argb] << " px";
return histogram[argb] > 0u;
}));
histogram.erase(argb);
EXPECT_EQ((std::map<uint32_t, size_t>){}, histogram) << "Unexpected colors";
}
private:
std::unique_ptr<component::StartupContext> context_;
fuchsia::sys::ComponentControllerPtr runner_ctrl_;
fuchsia::ui::scenic::ScenicPtr scenic_;
};
// Loads a static page with a solid color via the component framework and
// verifies that the color is the only color onscreen.
TEST_F(WebRunnerPixelTest, Static) {
static constexpr uint32_t kTargetColor = 0xffff00ff;
web_runner_tests::TestServer server;
FXL_CHECK(server.FindAndBindPort());
// Chromium and the Fuchsia network package loader both send us requests. This
// may go away after MI4-1807; although the race seems to be in Modular, the
// fix may remove the unnecessary net request in component framework.
auto serve = ServeAsync(&server, [&server] {
while (server.Accept()) {
MockHttpGetResponse(&server, "static.html");
}
});
component::Services services;
fuchsia::sys::ComponentControllerPtr controller;
context()->launcher()->CreateComponent(
{.url =
fxl::StringPrintf("http://localhost:%d/static.html", server.port()),
.directory_request = services.NewRequest()},
controller.NewRequest());
// Present the view.
services.ConnectToService<fuchsia::ui::app::ViewProvider>()->CreateView(
CreatePresentationViewToken(), nullptr, nullptr);
ExpectSolidColor(kTargetColor);
}
// This fixture uses chromium.web FIDL services to interact with Chromium.
class ChromiumFidlTest : public WebRunnerPixelTest,
chromium::web::NavigationEventObserver {
protected:
ChromiumFidlTest() : navigation_event_observer_binding_(this) {
auto context_provider =
context()
->ConnectToEnvironmentService<chromium::web::ContextProvider>();
context_provider.set_error_handler(
[](zx_status_t status) { FAIL() << "context_provider: " << status; });
zx_handle_t incoming_service_clone =
fdio_service_clone(context()->incoming_services()->directory().get());
FXL_CHECK(incoming_service_clone != ZX_HANDLE_INVALID);
chromium::web::CreateContextParams params;
params.service_directory = zx::channel(incoming_service_clone);
context_provider->Create(std::move(params), chromium_context_.NewRequest());
chromium_context_.set_error_handler(
[](zx_status_t status) { FAIL() << "chromium_context_: " << status; });
chromium_context_->CreateFrame(chromium_frame_.NewRequest());
chromium_frame_.set_error_handler(
[](zx_status_t status) { FAIL() << "chromium_frame_: " << status; });
// Bind ourselves as a NavigationEventObserver on this frame.
chromium_frame_->SetNavigationEventObserver(
navigation_event_observer_binding_.NewBinding());
navigation_event_observer_binding_.set_error_handler(
[](zx_status_t status) {
FAIL() << "navigation_event_observer_binding_: " << status;
});
// And create a view for the frame.
chromium_frame_->CreateView2(CreatePresentationViewToken(), nullptr,
nullptr);
chromium_frame_->GetNavigationController(navigation_.NewRequest());
navigation_.set_error_handler(
[](zx_status_t status) { FAIL() << "navigation_: " << status; });
}
void LaunchPage(const std::string& url) { navigation_->LoadUrl(url, {}); }
fit::function<void(chromium::web::NavigationEvent)>
on_navigation_state_changed_;
private:
// |chromium::web::NavigationEventObserver|
void OnNavigationStateChanged(
chromium::web::NavigationEvent change,
OnNavigationStateChangedCallback callback) override {
if (on_navigation_state_changed_) {
on_navigation_state_changed_(std::move(change));
}
callback();
}
chromium::web::NavigationControllerPtr navigation_;
chromium::web::ContextPtr chromium_context_;
chromium::web::FramePtr chromium_frame_;
fidl::Binding<chromium::web::NavigationEventObserver>
navigation_event_observer_binding_;
};
// Loads a static page with a solid color via chromium.web interfaces and
// verifies that the color is the only color onscreen.
TEST_F(ChromiumFidlTest, StaticChromiumFidl) {
static constexpr uint32_t kTargetColor = 0xffff00ff;
web_runner_tests::TestServer server;
FXL_CHECK(server.FindAndBindPort());
auto serve = ServeAsync(&server, [&server] {
FXL_LOG(INFO) << "Waiting for HTTP request from Chromium";
ASSERT_TRUE(server.Accept())
<< "Did not receive HTTP request from Chromium";
MockHttpGetResponse(&server, "static.html");
});
std::string url =
fxl::StringPrintf("http://localhost:%d/static.html", server.port());
on_navigation_state_changed_ = [this,
url](chromium::web::NavigationEvent change) {
if (change.url && *change.url == url) {
EXPECT_FALSE(change.is_error);
on_navigation_state_changed_ = nullptr;
QuitLoop();
}
};
LaunchPage(url);
EXPECT_FALSE(RunLoopWithTimeout(kTimeout))
<< "Timed out waiting for OnNavigationStateChanged";
ExpectSolidColor(kTargetColor);
}
} // namespace