From 3062467e7ae4ca68377216083e015846b307e5a0 Mon Sep 17 00:00:00 2001
From: Pierre Rossi <pierre.rossi@digia.com>
Date: Wed, 28 Aug 2013 14:19:14 +0200
Subject: [PATCH] Add preliminary context menu support

This is essentially the widgets part, with some tricks to get it to
honor the widget's context menu policy.
It enables c++11 for the widgets library for the convenience of using lambdas,
which admitedly we could do without, but seems reasonable considering our timeline
and the fact that we build chromium that way.

Change-Id: I6a632a78d2aa48fb0dfecfe491e92651d12407db
Reviewed-by: Zeno Albisser <zeno.albisser@digia.com>
---
 lib/quick/qquickwebengineview_p_p.h |  1 +
 lib/web_contents_adapter_client.h   | 19 +++++++-
 lib/web_contents_view_qt.cpp        | 17 +++++++
 lib/web_contents_view_qt.h          |  2 +
 lib/widgets/Api/qwebenginepage.cpp  | 76 +++++++++++++++++++++++++++++
 lib/widgets/Api/qwebenginepage_p.h  |  2 +
 lib/widgets/Api/qwebengineview.cpp  | 21 ++++++++
 lib/widgets/Api/qwebengineview.h    |  4 ++
 lib/widgets/Api/qwebengineview_p.h  |  1 +
 lib/widgets/widgets.pro             |  2 +
 10 files changed, 144 insertions(+), 1 deletion(-)

diff --git a/lib/quick/qquickwebengineview_p_p.h b/lib/quick/qquickwebengineview_p_p.h
index 947886c44..54bc21628 100644
--- a/lib/quick/qquickwebengineview_p_p.h
+++ b/lib/quick/qquickwebengineview_p_p.h
@@ -68,6 +68,7 @@ public:
     virtual void loadFinished(bool success) Q_DECL_OVERRIDE;
     virtual void focusContainer() Q_DECL_OVERRIDE;
     virtual void adoptNewWindow(WebContentsAdapter *newWebContents, WindowOpenDisposition disposition) Q_DECL_OVERRIDE;
+    virtual bool contextMenuRequested(const WebEngineContextMenuData &) Q_DECL_OVERRIDE { return false;}
 
     QExplicitlySharedDataPointer<WebContentsAdapter> adapter;
     QUrl icon;
diff --git a/lib/web_contents_adapter_client.h b/lib/web_contents_adapter_client.h
index 39968a8a5..8e92ead7c 100644
--- a/lib/web_contents_adapter_client.h
+++ b/lib/web_contents_adapter_client.h
@@ -47,12 +47,28 @@
 #include <QString>
 #include <QUrl>
 
-
 class RenderWidgetHostViewQt;
 class RenderWidgetHostViewQtDelegate;
 class WebContentsAdapter;
 class WebContentsDelegateQt;
 
+// FIXME: make this ref-counted and implicitely shared and expose as public API maybe ?
+class WebEngineContextMenuData {
+
+public:
+    QPoint pos;
+    QUrl linkUrl;
+    QString linkText;
+    QString selectedText;
+// Some likely candidates for future additions as we add support for the related actions:
+//    bool isImageBlocked;
+//    bool isEditable;
+//    bool isSpellCheckingEnabled;
+//    QStringList spellCheckingSuggestions;
+//    <enum tbd> mediaType;
+//    ...
+};
+
 class QWEBENGINE_EXPORT WebContentsAdapterClient {
 public:
     enum CompositingMode {
@@ -87,6 +103,7 @@ public:
     virtual void loadFinished(bool success) = 0;
     virtual void focusContainer() = 0;
     virtual void adoptNewWindow(WebContentsAdapter *newWebContents, WindowOpenDisposition disposition) = 0;
+    virtual bool contextMenuRequested(const WebEngineContextMenuData&) = 0;
 };
 
 #endif // WEB_CONTENTS_ADAPTER_CLIENT_H
diff --git a/lib/web_contents_view_qt.cpp b/lib/web_contents_view_qt.cpp
index 340075c9a..8fc7f5b12 100644
--- a/lib/web_contents_view_qt.cpp
+++ b/lib/web_contents_view_qt.cpp
@@ -48,6 +48,7 @@
 #include "base/command_line.h"
 #include "content/browser/renderer_host/render_view_host_impl.h"
 #include "content/public/common/content_switches.h"
+#include "content/public/common/context_menu_params.h"
 
 void WebContentsViewQt::initialize(WebContentsAdapterClient* client)
 {
@@ -111,3 +112,19 @@ void WebContentsViewQt::SetInitialFocus()
 {
     Focus();
 }
+
+static WebEngineContextMenuData fromParams(const content::ContextMenuParams &params)
+{
+    WebEngineContextMenuData ret;
+    ret.pos = QPoint(params.x, params.y);
+    ret.linkUrl = toQt(params.link_url);
+    ret.linkText = toQt(params.link_text.data());
+    ret.selectedText = toQt(params.selection_text.data());
+    return ret;
+}
+
+void WebContentsViewQt::ShowContextMenu(const content::ContextMenuParams &params)
+{
+    WebEngineContextMenuData contextMenuData(fromParams(params));
+    m_client->contextMenuRequested(contextMenuData);
+}
diff --git a/lib/web_contents_view_qt.h b/lib/web_contents_view_qt.h
index b76c72545..d71369762 100644
--- a/lib/web_contents_view_qt.h
+++ b/lib/web_contents_view_qt.h
@@ -107,6 +107,8 @@ public:
     virtual void ShowPopupMenu(const gfx::Rect& bounds, int item_height, double item_font_size, int selected_item,
                                 const std::vector<content::MenuItem>& items, bool right_aligned, bool allow_multiple_selection) { QT_NOT_YET_IMPLEMENTED }
 
+    virtual void ShowContextMenu(const content::ContextMenuParams &params);
+
 #if defined(OS_MACOSX)
     virtual void SetAllowOverlappingViews(bool overlapping) { QT_NOT_YET_IMPLEMENTED }
     virtual void CloseTabAfterEventTracking() { QT_NOT_YET_IMPLEMENTED }
diff --git a/lib/widgets/Api/qwebenginepage.cpp b/lib/widgets/Api/qwebenginepage.cpp
index 8fafb3c47..b956a8785 100644
--- a/lib/widgets/Api/qwebenginepage.cpp
+++ b/lib/widgets/Api/qwebenginepage.cpp
@@ -31,7 +31,11 @@
 #include "web_contents_adapter.h"
 
 #include <QAction>
+#include <QApplication>
+#include <QClipboard>
+#include <QIcon>
 #include <QLayout>
+#include <QMenu>
 #include <QUrl>
 
 QT_BEGIN_NAMESPACE
@@ -269,6 +273,78 @@ void QWebEnginePage::triggerAction(WebAction action, bool)
     }
 }
 
+bool QWebEnginePagePrivate::contextMenuRequested(const WebEngineContextMenuData &data)
+{
+    if (!view)
+        return false;
+
+    QContextMenuEvent event(QContextMenuEvent::Mouse, data.pos, view->mapToGlobal(data.pos));
+    switch (view->contextMenuPolicy()) {
+    case Qt::PreventContextMenu:
+        return false;
+    case Qt::DefaultContextMenu:
+        m_menuData = data;
+        view->contextMenuEvent(&event);
+        break;
+    case Qt::CustomContextMenu:
+        Q_EMIT view->customContextMenuRequested(data.pos);
+        break;
+    case Qt::ActionsContextMenu:
+        if (view->actions().count()) {
+            QMenu::exec(view->actions(), event.globalPos(), 0, view);
+            break;
+        }
+        // fall through
+    default:
+        event.ignore();
+        return false;
+        break;
+    }
+    Q_ASSERT(view->d_func()->m_pendingContextMenuEvent);
+    view->d_func()->m_pendingContextMenuEvent = false;
+    m_menuData = WebEngineContextMenuData();
+    return true;
+}
+
+QMenu *QWebEnginePage::createStandardContextMenu()
+{
+    Q_D(QWebEnginePage);
+    QMenu *menu = new QMenu(d->view);
+    QAction *action = 0;
+    WebEngineContextMenuData contextMenuData(d->m_menuData);
+    if (contextMenuData.selectedText.isEmpty()) {
+        action = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), tr("&Back"), menu);
+        connect(action, &QAction::triggered, d->view, &QWebEngineView::back);
+        action->setEnabled(d->adapter->canGoBack());
+        menu->addAction(action);
+
+        action = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), tr("&Forward"), menu);
+        connect(action, &QAction::triggered, d->view, &QWebEngineView::forward);
+        action->setEnabled(d->adapter->canGoForward());
+        menu->addAction(action);
+
+        action = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), tr("&Reload"), menu);
+        connect(action, &QAction::triggered, d->view, &QWebEngineView::reload);
+        menu->addAction(action);
+    } else {
+        action = new QAction(tr("Copy..."), menu);
+        // FIXME: We probably can't keep "cheating" with lambdas, but for now it keeps this patch smaller ;)
+        connect(action, &QAction::triggered, [=]() { qApp->clipboard()->setText(contextMenuData.selectedText); });
+        menu->addAction(action);
+    }
+
+    if (!contextMenuData.linkText.isEmpty() && contextMenuData.linkUrl.isValid()) {
+        menu->addSeparator();
+        action = new QAction(tr("Navigate to..."), menu);
+        connect(action, &QAction::triggered, [=]() { load(contextMenuData.linkUrl); });
+        menu->addAction(action);
+        action = new QAction(tr("Copy link address"), menu);
+        connect(action, &QAction::triggered, [=]() { qApp->clipboard()->setText(contextMenuData.linkUrl.toString()); });
+        menu->addAction(action);
+    }
+    return menu;
+}
+
 void QWebEnginePage::load(const QUrl& url)
 {
     Q_D(QWebEnginePage);
diff --git a/lib/widgets/Api/qwebenginepage_p.h b/lib/widgets/Api/qwebenginepage_p.h
index e8f511d2d..56080c977 100644
--- a/lib/widgets/Api/qwebenginepage_p.h
+++ b/lib/widgets/Api/qwebenginepage_p.h
@@ -74,6 +74,7 @@ public:
     virtual void loadFinished(bool success) Q_DECL_OVERRIDE;
     virtual void focusContainer() Q_DECL_OVERRIDE;
     virtual void adoptNewWindow(WebContentsAdapter *newWebContents, WindowOpenDisposition disposition) Q_DECL_OVERRIDE;
+    virtual bool contextMenuRequested(const WebEngineContextMenuData &data) Q_DECL_OVERRIDE;
 
     void updateAction(QWebEnginePage::WebAction) const;
     void updateNavigationActions();
@@ -84,6 +85,7 @@ public:
     QWebEngineView *view;
     mutable QAction *actions[QWebEnginePage::WebActionCount];
     bool m_isLoading;
+    WebEngineContextMenuData m_menuData;
 };
 
 QT_END_NAMESPACE
diff --git a/lib/widgets/Api/qwebengineview.cpp b/lib/widgets/Api/qwebengineview.cpp
index d07e710ab..536f075f3 100644
--- a/lib/widgets/Api/qwebengineview.cpp
+++ b/lib/widgets/Api/qwebengineview.cpp
@@ -44,6 +44,9 @@
 
 #include "qwebenginepage_p.h"
 
+#include <QAction>
+#include <QMenu>
+#include <QContextMenuEvent>
 #include <QStackedLayout>
 
 QT_BEGIN_NAMESPACE
@@ -186,6 +189,24 @@ void QWebEngineView::setZoomFactor(qreal factor)
     page()->setZoomFactor(factor);
 }
 
+bool QWebEngineView::event(QEvent *ev)
+{
+    Q_D(QWebEngineView);
+    // We swallow spontaneous contextMenu events and synthethize those back later on when we get the
+    // HandleContextMenu callback from chromium
+    if (ev->type() == QEvent::ContextMenu) {
+        ev->accept();
+        return true;
+    }
+    return QWidget::event(ev);
+}
+
+void QWebEngineView::contextMenuEvent(QContextMenuEvent *event)
+{
+    QMenu *menu = page()->createStandardContextMenu();
+    menu->popup(event->globalPos());
+}
+
 QT_END_NAMESPACE
 
 #include "moc_qwebengineview.cpp"
diff --git a/lib/widgets/Api/qwebengineview.h b/lib/widgets/Api/qwebengineview.h
index c3b3067bc..2c649d46f 100644
--- a/lib/widgets/Api/qwebengineview.h
+++ b/lib/widgets/Api/qwebengineview.h
@@ -30,6 +30,7 @@
 #include <QtWebEngineWidgets/qwebenginepage.h>
 
 QT_BEGIN_NAMESPACE
+class QContextMenuEvent;
 class QIcon;
 class QNetworkRequest;
 class QPrinter;
@@ -129,11 +130,14 @@ Q_SIGNALS:
 
 protected:
     virtual QWebEngineView *createWindow(QWebEnginePage::WebWindowType type);
+    virtual void contextMenuEvent(QContextMenuEvent*) Q_DECL_OVERRIDE;
+    virtual bool event(QEvent*) Q_DECL_OVERRIDE;
 
 private:
     Q_DECLARE_PRIVATE(QWebEngineView);
 
     friend class QWebEnginePage;
+    friend class QWebEnginePagePrivate;
 };
 
 QT_END_NAMESPACE
diff --git a/lib/widgets/Api/qwebengineview_p.h b/lib/widgets/Api/qwebengineview_p.h
index 73e5d45d8..774386a21 100644
--- a/lib/widgets/Api/qwebengineview_p.h
+++ b/lib/widgets/Api/qwebengineview_p.h
@@ -59,6 +59,7 @@ public:
     QWebEngineViewPrivate();
 
     QWebEnginePage *page;
+    bool m_pendingContextMenuEvent;
 };
 
 QT_END_NAMESPACE
diff --git a/lib/widgets/widgets.pro b/lib/widgets/widgets.pro
index 272e3568e..60730f880 100644
--- a/lib/widgets/widgets.pro
+++ b/lib/widgets/widgets.pro
@@ -9,6 +9,8 @@ MODULE = webenginewidgets
 # For our export macros
 DEFINES += QT_BUILD_WEBENGINEWIDGETS_LIB
 
+CONFIG += c++11
+
 QT += widgets
 QT_PRIVATE += widgets-private gui-private core-private
 
-- 
GitLab