blob: ef8fec97242a4f512e6eba9f08d40d9726bdaf1d [file] [log] [blame]
/*
* Copyright 2016 The Fuchsia Authors. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "WebView.h"
#include <WebCore/BackForwardController.h>
#include <WebCore/CustomEvent.h>
#include <WebCore/DocumentLoader.h>
#include <WebCore/FocusController.h>
#include <WebCore/FrameLoadRequest.h>
#include <WebCore/ScriptController.h>
#include <WebCore/GraphicsContext.h>
#include <WebCore/IntSize.h>
#include <WebCore/LogInitialization.h>
#include <WebCore/MainFrame.h>
#include <WebCore/NetworkStorageSession.h>
#include <WebCore/Page.h>
#include <WebCore/PageConfiguration.h>
#include <WebCore/PlatformCookieJar.h>
#include <WebCore/PlatformKeyboardEvent.h>
#include <WebCore/ResourceRequest.h>
#include <WebCore/Settings.h>
#include <WebCore/URLParser.h>
#include <runtime/JSCJSValue.h>
#include <WebKit/fuchsia/WebCoreSupport/WebChromeClient.h>
#include <WebKit/fuchsia/WebCoreSupport/WebEditorClient.h>
#include <WebKit/fuchsia/WebCoreSupport/WebFrameLoaderClient.h>
#include <WebKit/fuchsia/WebCoreSupport/WebPlatformStrategies.h>
#include <WebStorageNamespaceProvider.h>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <hid/hid.h>
#include <hid/usages.h>
#include <zircon/pixelformat.h>
using namespace std;
using namespace WebCore;
namespace {
class CustomEventListener : public WebCore::EventListener {
public:
CustomEventListener(CustomEventHandler handler, WebView *webView)
: EventListener(CPPEventListenerType), handler_(handler), webView_(webView) {
}
~CustomEventListener() { }
virtual bool operator==(const EventListener& that) const {
return (void*)this == (void*)&that;
}
virtual void handleEvent(WebCore::ScriptExecutionContext* s,
WebCore::Event* e) {
if (e->eventInterface() == WebCore::CustomEventInterfaceType) {
handler_();
}
}
static Ref<CustomEventListener> create(CustomEventHandler handler, WebView *webView) {
return adoptRef(*new CustomEventListener(handler, webView));
}
private:
CustomEventHandler handler_;
WebView* webView_;
};
} // namespace
WebView::~WebView()
{
if (m_cairoSurface) {
cairo_surface_destroy(m_cairoSurface);
}
if (m_cairoContext) {
cairo_destroy(m_cairoContext);
}
delete m_frameLoaderClient;
delete m_chromeClient;
delete m_page;
}
void WebView::setup_once() {
static std::once_flag initializeOnceFlag;
std::call_once(initializeOnceFlag, [] {
setenv("WEBKIT_DEBUG", "", 1);
setenv("CURL_COOKIE_JAR_PATH", "/data/web_view/cookies.dat", 1);
#if !LOG_DISABLED || !RELEASE_LOG_DISABLED
WebCore::initializeLogChannelsIfNecessary();
#endif
WTF::initializeThreading();
WTF::initializeMainThread();
WTF::RunLoop::initializeMainRunLoop();
WebPlatformStrategies::initializeIfNecessary();
});
}
bool WebView::setup(unsigned char* pixelBuffer, int pixelFormat, size_t targetWidth, size_t targetHeight, size_t rowbytes)
{
cairo_format_t format = CAIRO_FORMAT_INVALID;
switch (pixelFormat) {
case ZX_PIXEL_FORMAT_RGB_565:
format = CAIRO_FORMAT_RGB16_565;
break;
case ZX_PIXEL_FORMAT_RGB_x888:
format = CAIRO_FORMAT_RGB24;
break;
case ZX_PIXEL_FORMAT_ARGB_8888:
format = CAIRO_FORMAT_ARGB32;
break;
case ZX_PIXEL_FORMAT_MONO_8:
format = CAIRO_FORMAT_A8;
break;
default:
cout << "Unsupported pixel format " << pixelFormat << endl;
return false;
}
if (m_cairoSurface) {
cairo_surface_destroy(m_cairoSurface);
m_cairoSurface = nullptr;
}
if (m_cairoContext) {
cairo_destroy(m_cairoContext);
m_cairoContext = nullptr;
}
m_cairoSurface = cairo_image_surface_create_for_data(pixelBuffer,
format,
targetWidth,
targetHeight,
rowbytes);
cairo_status_t cairoStatus = cairo_surface_status(m_cairoSurface);
if (cairoStatus != CAIRO_STATUS_SUCCESS) {
cout << "cairo_image_surface_create_for_data failed: " << cairo_status_to_string(cairoStatus) << endl;
return false;
}
m_cairoContext = cairo_create(m_cairoSurface);
m_pageWidth = targetWidth;
m_pageHeight = targetHeight;
if (m_page == nullptr) {
IntSize targetSize(targetWidth, targetHeight);
std::function<void(bool)> delegate = [=](bool focused) {
this->dispatchInputFocusChange(focused);
};
PageConfiguration pageConfiguration(makeUniqueRef<WebKit::WebEditorClient>(delegate), SocketProvider::create());
fillWithEmptyClients(pageConfiguration);
m_frameLoaderClient = new WebFrameLoaderClient(targetSize, this);
pageConfiguration.loaderClientForMainFrame = m_frameLoaderClient;
pageConfiguration.storageNamespaceProvider = WebStorageNamespaceProvider::create("/data/web_view");
m_chromeClient = new WebChromeClient(targetSize);
pageConfiguration.chromeClient = m_chromeClient;
m_page = new Page(WTFMove(pageConfiguration));
Settings& settings = m_page->settings();
settings.setScriptEnabled(true);
settings.setLoadsImagesAutomatically(true);
settings.setLocalStorageEnabled(true);
m_page->setIsInWindow(true);
m_page->setGroupName("fuchsia_group");
auto& mainFrame = m_page->mainFrame();
m_frameLoaderClient->setFrame(&mainFrame);
mainFrame.tree().setName("fuchsia_group");
mainFrame.init();
} else {
Frame* frame = m_frameLoaderClient->frame();
FrameView* view = frame->view();
IntRect newRect(0, 0, m_pageWidth, m_pageHeight);
view->setFrameRect(newRect);
}
return true;
}
void WebView::setFileURL(const std::string& urlString)
{
URL fileURL(URL::fileURLWithFileSystemPath(urlString.c_str()));
setURLInternal(fileURL);
}
void WebView::setURL(const std::string& urlString)
{
URLParser parser(urlString.c_str());
URL url(parser.result());
setURLInternal(url);
}
void WebView::setURLInternal(const WebCore::URL& url)
{
ResourceRequest resourceRequest(url);
auto& mainFrame = m_page->mainFrame();
auto& loader = mainFrame.loader();
FrameLoadRequest loadRequest(&mainFrame, resourceRequest, ShouldOpenExternalURLsPolicy::ShouldNotAllow);
m_frameLoaderClient->clearMainDocumentError();
loader.load(loadRequest);
}
void WebView::reload()
{
if (m_page) {
m_page->mainFrame().loader().reload();
}
}
void WebView::goBack()
{
if (m_page) {
m_page->backForward().goBack();
}
}
void WebView::goForward()
{
if (m_page) {
m_page->backForward().goForward();
}
}
void WebView::scrollDownOneLine()
{
scrollInternal(ScrollDirection::ScrollDown, ScrollGranularity::ScrollByLine);
}
void WebView::scrollUpOneLine()
{
scrollInternal(ScrollDirection::ScrollUp, ScrollGranularity::ScrollByLine);
}
void WebView::scrollLeftOneLine()
{
scrollInternal(ScrollDirection::ScrollLeft, ScrollGranularity::ScrollByLine);
}
void WebView::scrollRightOneLine()
{
scrollInternal(ScrollDirection::ScrollRight, ScrollGranularity::ScrollByLine);
}
void WebView::scrollPixels(int deltaX, int deltaY)
{
Frame* frame = m_frameLoaderClient->frame();
if (frame) {
FrameView* view = frame->view();
view->scrollBy(IntSize(deltaX, deltaY));
}
}
void WebView::scrollInternal(uint8_t direction, uint8_t granularity)
{
if (m_page) {
Frame* frame = m_frameLoaderClient->frame();
if (frame) {
FrameView* view = frame->view();
view->scroll((ScrollDirection)direction, (ScrollGranularity)granularity);
}
}
}
void WebView::layoutAndPaint()
{
Frame* frame = m_frameLoaderClient->frame();
if (frame) {
GraphicsContext gc(m_cairoContext);
FrameView* view = frame->view();
view->updateLayoutAndStyleIfNeededRecursive();
if (view->frame().contentRenderer()) {
IntRect fullRect(0, 0, m_pageWidth, m_pageHeight);
view->paint(gc, fullRect);
}
cairo_surface_flush(m_cairoSurface);
}
}
void WebView::setFocused(bool focused)
{
auto& mainFrame = m_page->mainFrame();
m_page->focusController().setFocusedFrame(&mainFrame);
m_page->focusController().setActive(focused);
m_page->focusController().setFocused(focused);
}
void WebView::setVisible(bool visible)
{
m_page->setIsVisible(visible);
}
void WebView::setPageAndTextZoomFactors(float pageZoomFactor, float textZoomFactor)
{
auto& mainFrame = m_page->mainFrame();
mainFrame.setPageAndTextZoomFactors(pageZoomFactor, textZoomFactor);
}
void WebView::handleMouseEvent(int x, int y, MouseEventKind eventType)
{
auto& mainFrame = m_page->mainFrame();
IntPoint mousePosition(x, y);
auto now = std::chrono::steady_clock::now().time_since_epoch();
auto timeSinceEpoch = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
if (eventType == kMouseDown) {
WebCore::PlatformMouseEvent mouseEvent(mousePosition, mousePosition,
WebCore::MouseButton::LeftButton, PlatformEvent::MousePressed,
1, false, false, false, false, timeSinceEpoch, 0, SyntheticClickType::NoTap);
mainFrame.eventHandler().handleMousePressEvent(mouseEvent);
} else if (eventType == kMouseUp) {
WebCore::PlatformMouseEvent mouseEvent(mousePosition, mousePosition,
WebCore::MouseButton::LeftButton, PlatformEvent::MouseReleased,
1, false, false, false, false, timeSinceEpoch, 0, SyntheticClickType::NoTap);
mainFrame.eventHandler().handleMouseReleaseEvent(mouseEvent);
} else {
WebCore::PlatformMouseEvent mouseEvent(mousePosition, mousePosition,
WebCore::MouseButton::NoButton, PlatformEvent::MouseMoved,
0, false, false, false, false, timeSinceEpoch, 0, SyntheticClickType::NoTap);
mainFrame.eventHandler().mouseMoved(mouseEvent);
}
}
bool WebView::handleKeyEvent(uint8_t keycode, uint8_t charValue, bool pressed, bool repeat)
{
string identifier;
if (keycode == HID_USAGE_KEY_LEFT_SHIFT || keycode == HID_USAGE_KEY_RIGHT_SHIFT) {
fShift = pressed;
} else if (keycode == HID_USAGE_KEY_LEFT_CTRL || keycode == HID_USAGE_KEY_RIGHT_CTRL) {
fControl = pressed;
}
int rawModifiers = 0;
if (fControl) {
rawModifiers |= PlatformEvent::Modifiers::CtrlKey;
}
if (fShift) {
rawModifiers |= PlatformEvent::Modifiers::ShiftKey;
}
if (charValue != 0) {
ostringstream oss;
oss << "U+" << hex << setw(4) << setfill('0') << (int)charValue;
identifier = oss.str();
} else if (keycode == HID_USAGE_KEY_BACKSPACE) {
identifier = "U+0008";
charValue = 9;
} else if (keycode == HID_USAGE_KEY_TAB) {
identifier = "U+0009";
charValue = 8;
}
char charStr[2] = { static_cast<char>(charValue), 0 };
auto now = std::chrono::steady_clock::now().time_since_epoch();
auto timeSinceEpoch = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
WebCore::PlatformKeyboardEvent keyboardEvent(pressed ? WebCore::PlatformEvent::KeyDown : WebCore::PlatformEvent::KeyUp, charStr, charStr, identifier.c_str(),
0, keycode, 0, repeat, false, false, (PlatformEvent::Modifiers)rawModifiers, timeSinceEpoch);
auto& mainFrame = m_page->mainFrame();
return mainFrame.eventHandler().keyEvent(keyboardEvent);
}
void WebView::dispatchWillSendRequest(DocumentLoader* loader, unsigned long identifier, ResourceRequest& request, const ResourceResponse& redirectResponse)
{
if (m_webRequestDelegate) {
std::string url = request.url().string().utf8().data();
std::string new_url = m_webRequestDelegate(url);
if (url != new_url) {
URLParser parser(new_url.c_str());
URL url(parser.result());
request.setURL(url);
}
}
}
void WebView::dispatchInputFocusChange(bool focused) {
if (m_inputFocusDelegate) {
m_inputFocusDelegate(focused);
}
}
void WebView::setWebRequestDelegate(WebRequestDelegate delegate) {
m_webRequestDelegate = delegate;
}
void WebView::setInputFocusDelegate(InputFocusDelegate delegate) {
m_inputFocusDelegate = delegate;
}
void WebView::setDidFinishLoadDelegate(DidFinishLoadDelegate delegate) {
m_didFinishLoadDelegate = delegate;
}
void WebView::dispatchDidFinishLoad() {
if (m_didFinishLoadDelegate) {
m_didFinishLoadDelegate();
}
}
void WebView::iterateEventLoop()
{
RunLoop::iterate();
}
std::string WebView::getTitle()
{
return m_frameLoaderClient->getTitle();
}
bool WebView::isMainFrameLoaded()
{
auto& mainFrame = m_page->mainFrame();
return !mainFrame.loader().isLoading();
}
std::string WebView::getMainDocumentError() const
{
return m_frameLoaderClient->getMainDocumentError();
}
std::string WebView::getAllCookies(const std::string& urlString)
{
URLParser parser(urlString.c_str());
URL url(parser.result());
WTF::String cookies = WebCore::cookieRequestHeaderFieldValue(
NetworkStorageSession::defaultStorageSession(), URL(), url);
return std::string(cookies.utf8().data());
}
void WebView::deleteAllCookies()
{
WebCore::deleteAllCookies(NetworkStorageSession::defaultStorageSession());
}
std::string WebView::stringByEvaluatingJavaScriptFromString(
const std::string& script) {
auto& mainFrame = m_page->mainFrame();
WTF::String scriptString(script.data(), script.size());
JSC::JSValue result = mainFrame.script().executeScript(scriptString, true);
if (!result) {
return "";
}
JSC::ExecState* exec =
mainFrame.script().globalObject(mainThreadNormalWorld())->globalExec();
JSC::JSLockHolder lock(exec);
WTF::String resultString = result.getString(exec);
WTF::CString resultCString =
resultString.utf8(WTF::ConversionMode::LenientConversion);
return std::string(resultCString.data(), resultCString.length());
}
void WebView::addCustomEventHandler(const std::string& type,
CustomEventHandler handler) {
Document* document = m_page->mainFrame().document();
if (document) {
Ref<CustomEventListener> listener = CustomEventListener::create(handler, this);
// The document takes ownership of the event listener.
document->addEventListener( type.c_str(), WTFMove(listener), false);
}
}