diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-09-12 14:56:22 +0200 |
---|---|---|
committer | Michael BrĂ¼ning <michael.bruning@qt.io> | 2017-12-06 15:02:13 +0000 |
commit | 481155893fd85b4b0770397375ceaf520fcf9db6 (patch) | |
tree | 84727f5b93c31debe246995b0072ef1b818e3ef1 | |
parent | 1c6cacf3020c0c201cd484ba165126123046e53b (diff) |
Introduce devtools frontend
Makes it possible to use devtools without using the remote-debugger
Task-number: QTBUG-47899
Task-number: QTBUG-50725
Task-number: QTBUG-50766
Change-Id: Id32e13f773372d9917599ebbb64ab4af61bbf1d8
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
26 files changed, 994 insertions, 38 deletions
diff --git a/examples/webengine/quicknanobrowser/BrowserWindow.qml b/examples/webengine/quicknanobrowser/BrowserWindow.qml index 193f10ab3..f3bd8e457 100644 --- a/examples/webengine/quicknanobrowser/BrowserWindow.qml +++ b/examples/webengine/quicknanobrowser/BrowserWindow.qml @@ -88,6 +88,7 @@ ApplicationWindow { property alias autoLoadIconsForPage: autoLoadIconsForPage.checked property alias touchIconsEnabled: touchIconsEnabled.checked property alias webRTCPublicInterfacesOnly : webRTCPublicInterfacesOnly.checked + property alias devToolsEnabled: devToolsEnabled.checked } Action { @@ -318,6 +319,12 @@ ApplicationWindow { checkable: true checked: WebEngine.settings.webRTCPublicInterfacesOnly } + MenuItem { + id: devToolsEnabled + text: "Open DevTools" + checkable: true + checked: false + } } } } @@ -476,6 +483,15 @@ ApplicationWindow { } } } + WebEngineView { + id: devToolsView + visible: devToolsEnabled.checked + height: 400 + inspectedView: tabs.currentIndex < tabs.count ? tabs.getTab(tabs.currentIndex).item : null + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + } MessageDialog { id: sslDialog diff --git a/examples/webenginewidgets/simplebrowser/browser.cpp b/examples/webenginewidgets/simplebrowser/browser.cpp index c50974531..5c6dbd35e 100644 --- a/examples/webenginewidgets/simplebrowser/browser.cpp +++ b/examples/webenginewidgets/simplebrowser/browser.cpp @@ -69,7 +69,19 @@ Browser::Browser() BrowserWindow *Browser::createWindow(bool offTheRecord) { auto profile = offTheRecord ? &m_otrProfile : QWebEngineProfile::defaultProfile(); - auto mainWindow = new BrowserWindow(this, profile); + auto mainWindow = new BrowserWindow(this, profile, false); + m_windows.append(mainWindow); + QObject::connect(mainWindow, &QObject::destroyed, [this, mainWindow]() { + m_windows.removeOne(mainWindow); + }); + mainWindow->show(); + return mainWindow; +} + +BrowserWindow *Browser::createDevToolsWindow() +{ + auto profile = QWebEngineProfile::defaultProfile(); + auto mainWindow = new BrowserWindow(this, profile, true); m_windows.append(mainWindow); QObject::connect(mainWindow, &QObject::destroyed, [this, mainWindow]() { m_windows.removeOne(mainWindow); diff --git a/examples/webenginewidgets/simplebrowser/browser.h b/examples/webenginewidgets/simplebrowser/browser.h index 9240cc987..fbc8465d2 100644 --- a/examples/webenginewidgets/simplebrowser/browser.h +++ b/examples/webenginewidgets/simplebrowser/browser.h @@ -66,6 +66,7 @@ public: QVector<BrowserWindow*> windows() { return m_windows; } BrowserWindow *createWindow(bool offTheRecord = false); + BrowserWindow *createDevToolsWindow(); DownloadManagerWidget &downloadManagerWidget() { return m_downloadManagerWidget; } diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.cpp b/examples/webenginewidgets/simplebrowser/browserwindow.cpp index 016d58afe..c369a90fa 100644 --- a/examples/webenginewidgets/simplebrowser/browserwindow.cpp +++ b/examples/webenginewidgets/simplebrowser/browserwindow.cpp @@ -67,11 +67,11 @@ #include <QVBoxLayout> #include <QWebEngineProfile> -BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile) +BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool forDevTools) : m_browser(browser) , m_profile(profile) , m_tabWidget(new TabWidget(profile, this)) - , m_progressBar(new QProgressBar(this)) + , m_progressBar(nullptr) , m_historyBackAction(nullptr) , m_historyForwardAction(nullptr) , m_stopAction(nullptr) @@ -83,49 +83,59 @@ BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile) setAttribute(Qt::WA_DeleteOnClose, true); setFocusPolicy(Qt::ClickFocus); - QToolBar *toolbar = createToolBar(); - addToolBar(toolbar); - menuBar()->addMenu(createFileMenu(m_tabWidget)); - menuBar()->addMenu(createEditMenu()); - menuBar()->addMenu(createViewMenu(toolbar)); - menuBar()->addMenu(createWindowMenu(m_tabWidget)); - menuBar()->addMenu(createHelpMenu()); + if (!forDevTools) { + m_progressBar = new QProgressBar(this); + + QToolBar *toolbar = createToolBar(); + addToolBar(toolbar); + menuBar()->addMenu(createFileMenu(m_tabWidget)); + menuBar()->addMenu(createEditMenu()); + menuBar()->addMenu(createViewMenu(toolbar)); + menuBar()->addMenu(createWindowMenu(m_tabWidget)); + menuBar()->addMenu(createHelpMenu()); + } QWidget *centralWidget = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout; layout->setSpacing(0); layout->setMargin(0); - addToolBarBreak(); + if (!forDevTools) { + addToolBarBreak(); - m_progressBar->setMaximumHeight(1); - m_progressBar->setTextVisible(false); - m_progressBar->setStyleSheet(QStringLiteral("QProgressBar {border: 0px} QProgressBar::chunk {background-color: #da4453}")); + m_progressBar->setMaximumHeight(1); + m_progressBar->setTextVisible(false); + m_progressBar->setStyleSheet(QStringLiteral("QProgressBar {border: 0px} QProgressBar::chunk {background-color: #da4453}")); + + layout->addWidget(m_progressBar); + } - layout->addWidget(m_progressBar); layout->addWidget(m_tabWidget); centralWidget->setLayout(layout); setCentralWidget(centralWidget); connect(m_tabWidget, &TabWidget::titleChanged, this, &BrowserWindow::handleWebViewTitleChanged); - connect(m_tabWidget, &TabWidget::linkHovered, [this](const QString& url) { - statusBar()->showMessage(url); - }); - connect(m_tabWidget, &TabWidget::loadProgress, this, &BrowserWindow::handleWebViewLoadProgress); - connect(m_tabWidget, &TabWidget::webActionEnabledChanged, this, &BrowserWindow::handleWebActionEnabledChanged); - connect(m_tabWidget, &TabWidget::urlChanged, [this](const QUrl &url) { - m_urlLineEdit->setText(url.toDisplayString()); - }); - connect(m_tabWidget, &TabWidget::favIconChanged, m_favAction, &QAction::setIcon); - connect(m_urlLineEdit, &QLineEdit::returnPressed, [this]() { - m_tabWidget->setUrl(QUrl::fromUserInput(m_urlLineEdit->text())); - }); + if (!forDevTools) { + connect(m_tabWidget, &TabWidget::linkHovered, [this](const QString& url) { + statusBar()->showMessage(url); + }); + connect(m_tabWidget, &TabWidget::loadProgress, this, &BrowserWindow::handleWebViewLoadProgress); + connect(m_tabWidget, &TabWidget::webActionEnabledChanged, this, &BrowserWindow::handleWebActionEnabledChanged); + connect(m_tabWidget, &TabWidget::urlChanged, [this](const QUrl &url) { + m_urlLineEdit->setText(url.toDisplayString()); + }); + connect(m_tabWidget, &TabWidget::favIconChanged, m_favAction, &QAction::setIcon); + connect(m_tabWidget, &TabWidget::devToolsRequested, this, &BrowserWindow::handleDevToolsRequested); + connect(m_urlLineEdit, &QLineEdit::returnPressed, [this]() { + m_tabWidget->setUrl(QUrl::fromUserInput(m_urlLineEdit->text())); + }); - QAction *focusUrlLineEditAction = new QAction(this); - addAction(focusUrlLineEditAction); - focusUrlLineEditAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L)); - connect(focusUrlLineEditAction, &QAction::triggered, this, [this] () { - m_urlLineEdit->setFocus(Qt::ShortcutFocusReason); - }); + QAction *focusUrlLineEditAction = new QAction(this); + addAction(focusUrlLineEditAction); + focusUrlLineEditAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L)); + connect(focusUrlLineEditAction, &QAction::triggered, this, [this] () { + m_urlLineEdit->setFocus(Qt::ShortcutFocusReason); + }); + } handleWebViewTitleChanged(QString()); m_tabWidget->createTab(); @@ -504,3 +514,9 @@ void BrowserWindow::handleShowWindowTriggered() windows.at(offset)->currentTab()->setFocus(); } } + +void BrowserWindow::handleDevToolsRequested(QWebEnginePage *source) +{ + source->setDevToolsPage(m_browser->createDevToolsWindow()->currentTab()->page()); + source->triggerAction(QWebEnginePage::InspectElement); +} diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.h b/examples/webenginewidgets/simplebrowser/browserwindow.h index b522a6b9d..8f328b751 100644 --- a/examples/webenginewidgets/simplebrowser/browserwindow.h +++ b/examples/webenginewidgets/simplebrowser/browserwindow.h @@ -69,7 +69,7 @@ class BrowserWindow : public QMainWindow Q_OBJECT public: - BrowserWindow(Browser *browser, QWebEngineProfile *profile); + BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool forDevTools = false); QSize sizeHint() const override; TabWidget *tabWidget() const; WebView *currentTab() const; @@ -87,6 +87,7 @@ private slots: void handleWebViewLoadProgress(int); void handleWebViewTitleChanged(const QString &title); void handleWebActionEnabledChanged(QWebEnginePage::WebAction action, bool enabled); + void handleDevToolsRequested(QWebEnginePage *source); private: QMenu *createFileMenu(TabWidget *tabWidget); diff --git a/examples/webenginewidgets/simplebrowser/tabwidget.cpp b/examples/webenginewidgets/simplebrowser/tabwidget.cpp index e7376c7a5..8b458a9af 100644 --- a/examples/webenginewidgets/simplebrowser/tabwidget.cpp +++ b/examples/webenginewidgets/simplebrowser/tabwidget.cpp @@ -190,6 +190,7 @@ void TabWidget::setupView(WebView *webView) if (index >= 0) closeTab(index); }); + connect(webView, &WebView::devToolsRequested, this, &TabWidget::devToolsRequested); } WebView *TabWidget::createTab() diff --git a/examples/webenginewidgets/simplebrowser/tabwidget.h b/examples/webenginewidgets/simplebrowser/tabwidget.h index 5b09f2708..bf83781df 100644 --- a/examples/webenginewidgets/simplebrowser/tabwidget.h +++ b/examples/webenginewidgets/simplebrowser/tabwidget.h @@ -77,6 +77,7 @@ signals: void urlChanged(const QUrl &url); void favIconChanged(const QIcon &icon); void webActionEnabledChanged(QWebEnginePage::WebAction action, bool enabled); + void devToolsRequested(QWebEnginePage *source); public slots: // current tab/page slots diff --git a/examples/webenginewidgets/simplebrowser/webview.cpp b/examples/webenginewidgets/simplebrowser/webview.cpp index 868cca037..ab42c4a0a 100644 --- a/examples/webenginewidgets/simplebrowser/webview.cpp +++ b/examples/webenginewidgets/simplebrowser/webview.cpp @@ -167,6 +167,7 @@ QWebEngineView *WebView::createWindow(QWebEnginePage::WebWindowType type) } case QWebEnginePage::WebDialog: { WebPopupWindow *popup = new WebPopupWindow(page()->profile()); + connect(popup->view(), &WebView::devToolsRequested, this, &WebView::devToolsRequested); return popup->view(); } } @@ -185,6 +186,13 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) menu->insertAction(before, page()->action(QWebEnginePage::OpenLinkInNewWindow)); menu->insertAction(before, page()->action(QWebEnginePage::OpenLinkInNewTab)); } + it = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::InspectElement)); + if (it == actions.cend()) { + QAction *action = new QAction(menu); + action->setText("Inspect Element"); + connect(action, &QAction::triggered, [this]() { emit devToolsRequested(page()); }); + menu->addAction(action); + } menu->popup(event->globalPos()); } diff --git a/examples/webenginewidgets/simplebrowser/webview.h b/examples/webenginewidgets/simplebrowser/webview.h index 7276ab1c4..8559a68b8 100644 --- a/examples/webenginewidgets/simplebrowser/webview.h +++ b/examples/webenginewidgets/simplebrowser/webview.h @@ -75,6 +75,7 @@ protected: signals: void webActionEnabledChanged(QWebEnginePage::WebAction webAction, bool enabled); void favIconChanged(const QIcon &icon); + void devToolsRequested(QWebEnginePage *source); private: void createWebActionTrigger(QWebEnginePage *page, QWebEnginePage::WebAction); diff --git a/src/3rdparty b/src/3rdparty -Subproject 4368c7380883fd6b32d913d27a5b8f51f615068 +Subproject 3af56a2143e4fa3a10f93917ecde4ef46c2c807 diff --git a/src/core/core_chromium.pri b/src/core/core_chromium.pri index 513cd4c1f..b4dbaeb4f 100644 --- a/src/core/core_chromium.pri +++ b/src/core/core_chromium.pri @@ -59,6 +59,7 @@ SOURCES = \ custom_protocol_handler.cpp \ delegated_frame_node.cpp \ desktop_screen_qt.cpp \ + devtools_frontend_qt.cpp \ devtools_manager_delegate_qt.cpp \ download_manager_delegate_qt.cpp \ favicon_manager.cpp \ @@ -135,6 +136,7 @@ HEADERS = \ custom_protocol_handler.h \ delegated_frame_node.h \ desktop_screen_qt.h \ + devtools_frontend_qt.h \ devtools_manager_delegate_qt.h \ download_manager_delegate_qt.h \ chromium_gpu_helper.h \ diff --git a/src/core/devtools_frontend_qt.cpp b/src/core/devtools_frontend_qt.cpp new file mode 100644 index 000000000..7330090e7 --- /dev/null +++ b/src/core/devtools_frontend_qt.cpp @@ -0,0 +1,519 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// based on content/shell/browser/shell_devtools_frontend.cc: +// Copyright 2013 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 "devtools_frontend_qt.h" + +#include "browser_context_qt.h" +#include "web_contents_adapter.h" + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/json/string_escape.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/common/url_constants.h" +#include "components/prefs/in_memory_pref_store.h" +#include "components/prefs/json_pref_store.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/navigation_controller.h" +#include "content/public/browser/navigation_handle.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/storage_partition.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/content_client.h" +#include "content/public/common/url_constants.h" +#include "ipc/ipc_channel.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/http/http_response_headers.h" +#include "net/traffic_annotation/network_traffic_annotation.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_fetcher_response_writer.h" + +#include <QDebug> +using namespace QtWebEngineCore; + +namespace { + +// ResponseWriter ------------------------------------------------------------- + +class ResponseWriter : public net::URLFetcherResponseWriter { +public: + ResponseWriter(base::WeakPtr<DevToolsFrontendQt> shell_devtools_, int stream_id); + ~ResponseWriter() override; + + // URLFetcherResponseWriter overrides: + int Initialize(const net::CompletionCallback &callback) override; + int Write(net::IOBuffer *buffer, int num_bytes, const net::CompletionCallback &callback) override; + int Finish(int net_error, const net::CompletionCallback &callback) override; + +private: + base::WeakPtr<DevToolsFrontendQt> shell_devtools_; + int stream_id_; + + DISALLOW_COPY_AND_ASSIGN(ResponseWriter); +}; + +ResponseWriter::ResponseWriter(base::WeakPtr<DevToolsFrontendQt> shell_devtools, int stream_id) + : shell_devtools_(shell_devtools), stream_id_(stream_id) +{} + +ResponseWriter::~ResponseWriter() {} + +int ResponseWriter::Initialize(const net::CompletionCallback& callback) +{ + return net::OK; +} + +int ResponseWriter::Write(net::IOBuffer *buffer, int num_bytes, const net::CompletionCallback &callback) +{ + std::string chunk = std::string(buffer->data(), num_bytes); + if (!base::IsStringUTF8(chunk)) + return num_bytes; + + base::Value *id = new base::Value(stream_id_); + base::Value *chunkValue = new base::Value(chunk); + + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&DevToolsFrontendQt::CallClientFunction, shell_devtools_, + "DevToolsAPI.streamWrite", base::Owned(id), + base::Owned(chunkValue), nullptr)); + return num_bytes; +} + +int ResponseWriter::Finish(int net_error, const net::CompletionCallback &callback) +{ + return net::OK; +} + +static std::string GetFrontendURL() +{ + return chrome::kChromeUIDevToolsURL; +} + +} // namespace + +namespace QtWebEngineCore { + +// This constant should be in sync with +// the constant at devtools_ui_bindings.cc. +const size_t kMaxMessageChunkSize = IPC::Channel::kMaximumMessageSize / 4; + +// static +DevToolsFrontendQt *DevToolsFrontendQt::Show(QSharedPointer<WebContentsAdapter> frontendAdapter, content::WebContents *inspectedContents) +{ + DCHECK(frontendAdapter); + DCHECK(inspectedContents); + + content::WebContents *contents = frontendAdapter->webContents(); + if (contents == inspectedContents) { + qWarning() << "You can not inspect youself"; + return nullptr; + } + + DevToolsFrontendQt *devtoolsFrontend = new DevToolsFrontendQt(frontendAdapter, inspectedContents); + + if (contents->GetURL() == GURL(GetFrontendURL())) { + contents->GetController().Reload(content::ReloadType::ORIGINAL_REQUEST_URL, false); + } else { + content::NavigationController::LoadURLParams loadParams = content::NavigationController::LoadURLParams(GURL(GetFrontendURL())); + loadParams.transition_type = ui::PageTransitionFromInt(ui::PAGE_TRANSITION_AUTO_TOPLEVEL | ui::PAGE_TRANSITION_FROM_API); + contents->GetController().LoadURLWithParams(loadParams); + } + + return devtoolsFrontend; +} + +DevToolsFrontendQt::DevToolsFrontendQt(QSharedPointer<WebContentsAdapter> webContentsAdapter, + content::WebContents *inspectedContents) + : content::WebContentsObserver(webContentsAdapter->webContents()) + , m_webContentsAdapter(webContentsAdapter) + , m_inspectedContents(inspectedContents) + , m_inspect_element_at_x(-1) + , m_inspect_element_at_y(-1) + , m_prefStore(nullptr) + , m_weakFactory(this) +{ + // We use a separate prefstore than BrowserContextQt, because that one is in-memory only, and this + // needs to be stored or it will show introduction text on every load. + if (web_contents()->GetBrowserContext()->IsOffTheRecord()) + m_prefStore = std::move(scoped_refptr<PersistentPrefStore>(new InMemoryPrefStore())); + else + CreateJsonPreferences(false); + + m_frontendDelegate = static_cast<WebContentsDelegateQt *>(webContentsAdapter->webContents()->GetDelegate()); +} + + +DevToolsFrontendQt::~DevToolsFrontendQt() +{ + for (const auto &pair : m_pendingRequests) + delete pair.first; +} + +void DevToolsFrontendQt::Activate() +{ + m_frontendDelegate->ActivateContents(web_contents()); +} + +void DevToolsFrontendQt::Focus() +{ + web_contents()->Focus(); +} + +void DevToolsFrontendQt::InspectElementAt(int x, int y) +{ + if (m_agentHost) + m_agentHost->InspectElement(this, x, y); + else { + m_inspect_element_at_x = x; + m_inspect_element_at_y = y; + } +} + +void DevToolsFrontendQt::Close() +{ + // Don't close the webContents, it might be reused, but pretend it was + WebContentsDestroyed(); +} + +void DevToolsFrontendQt::DisconnectFromTarget() +{ + if (!m_agentHost) + return; + m_agentHost->DetachClient(this); + m_agentHost = nullptr; +} + +void DevToolsFrontendQt::ReadyToCommitNavigation(content::NavigationHandle *navigationHandle) +{ + // ShellDevToolsFrontend does this in RenderViewCreated, + // but that doesn't work for us for some reason. + content::RenderFrameHost *frame = navigationHandle->GetRenderFrameHost(); + if (navigationHandle->IsInMainFrame()) { + // If the frontend for some reason goes to some place other than devtools, stop the bindings + if (navigationHandle->GetURL() != GetFrontendURL()) + m_frontendHost.reset(nullptr); + else + m_frontendHost.reset(content::DevToolsFrontendHost::Create(frame, + base::Bind(&DevToolsFrontendQt::HandleMessageFromDevToolsFrontend, + base::Unretained(this)))); + } +} + +void DevToolsFrontendQt::DocumentAvailableInMainFrame() +{ + if (!m_inspectedContents) + return; + // Don't call AttachClient multiple times for the same DevToolsAgentHost. + // Otherwise it will call AgentHostClosed which closes the DevTools window. + // This may happen in cases where the DevTools content fails to load. + scoped_refptr<content::DevToolsAgentHost> agent_host = + content::DevToolsAgentHost::GetOrCreateFor(m_inspectedContents); + if (agent_host != m_agentHost) { + m_agentHost = agent_host; + m_agentHost->AttachClient(this); + if (m_inspect_element_at_x != -1) { + m_agentHost->InspectElement(this, m_inspect_element_at_x, m_inspect_element_at_y); + m_inspect_element_at_x = -1; + m_inspect_element_at_y = -1; + } + } +} + +void DevToolsFrontendQt::WebContentsDestroyed() +{ + if (m_inspectedContents) + static_cast<WebContentsDelegateQt *>(m_inspectedContents->GetDelegate())->webContentsAdapter()->devToolsFrontendDestroyed(this); + + if (m_agentHost) + m_agentHost->DetachClient(this); + delete this; +} + +void DevToolsFrontendQt::SetPreference(const std::string &name, const std::string &value) +{ + DCHECK(m_prefStore); + m_prefStore->SetValue(name, base::WrapUnique(new base::Value(value)), 0); +} + +void DevToolsFrontendQt::RemovePreference(const std::string &name) +{ + DCHECK(m_prefStore); + m_prefStore->RemoveValue(name, 0); +} + +void DevToolsFrontendQt::ClearPreferences() +{ + if (web_contents()->GetBrowserContext()->IsOffTheRecord()) + m_prefStore = scoped_refptr<PersistentPrefStore>(new InMemoryPrefStore()); + else + CreateJsonPreferences(true); +} + +void DevToolsFrontendQt::CreateJsonPreferences(bool clear) +{ + content::BrowserContext *browserContext = web_contents()->GetBrowserContext(); + DCHECK(!browserContext->IsOffTheRecord()); + JsonPrefStore *jsonPrefStore = new JsonPrefStore( + browserContext->GetPath().Append(FILE_PATH_LITERAL("devtoolsprefs.json"))); + // We effectively clear the preferences by not calling ReadPrefs + if (!clear) + jsonPrefStore->ReadPrefs(); + + m_prefStore = scoped_refptr<PersistentPrefStore>(jsonPrefStore); +} + +void DevToolsFrontendQt::HandleMessageFromDevToolsFrontend(const std::string &message) +{ + if (!m_agentHost) + return; + std::string method; + base::ListValue *params = nullptr; + base::DictionaryValue *dict = nullptr; + std::unique_ptr<base::Value> parsed_message = base::JSONReader::Read(message); + if (!parsed_message || !parsed_message->GetAsDictionary(&dict) || !dict->GetString("method", &method)) + return; + int request_id = 0; + dict->GetInteger("id", &request_id); + dict->GetList("params", ¶ms); + + if (method == "dispatchProtocolMessage" && params && params->GetSize() == 1) { + if (!m_agentHost || !m_agentHost->IsAttached()) + return; + std::string protocol_message; + if (!params->GetString(0, &protocol_message)) + return; + m_agentHost->DispatchProtocolMessage(this, protocol_message); + } else if (method == "loadCompleted") { + web_contents()->GetMainFrame()->ExecuteJavaScript(base::ASCIIToUTF16("DevToolsAPI.setUseSoftMenu(true);")); + } else if (method == "loadNetworkResource" && params->GetSize() == 3) { + // TODO(pfeldman): handle some of the embedder messages in content. + std::string url; + std::string headers; + int stream_id; + if (!params->GetString(0, &url) || !params->GetString(1, &headers) || !params->GetInteger(2, &stream_id)) + return; + + GURL gurl(url); + if (!gurl.is_valid()) { + base::DictionaryValue response; + response.SetInteger("statusCode", 404); + SendMessageAck(request_id, &response); + return; + } + + net::NetworkTrafficAnnotationTag traffic_annotation = + net::DefineNetworkTrafficAnnotation( + "devtools_handle_front_end_messages", R"( + semantics { + sender: "Developer Tools" + description: + "When user opens Developer Tools, the browser may fetch " + "additional resources from the network to enrich the debugging " + "experience (e.g. source map resources)." + trigger: "User opens Developer Tools to debug a web page." + data: "Any resources requested by Developer Tools." + destination: OTHER + } + policy { + cookies_allowed: YES + cookies_store: "user" + setting: + "It's not possible to disable this feature from settings." + chrome_policy { + DeveloperToolsDisabled { + policy_options {mode: MANDATORY} + DeveloperToolsDisabled: true + } + } + })"); + net::URLFetcher *fetcher = net::URLFetcher::Create(gurl, net::URLFetcher::GET, this, traffic_annotation).release(); + m_pendingRequests[fetcher] = request_id; + fetcher->SetRequestContext(content::BrowserContext::GetDefaultStoragePartition( + web_contents()->GetBrowserContext())->GetURLRequestContext()); + fetcher->SetExtraRequestHeaders(headers); + fetcher->SaveResponseWithWriter(std::unique_ptr<net::URLFetcherResponseWriter>( + new ResponseWriter(m_weakFactory.GetWeakPtr(), stream_id))); + fetcher->Start(); + return; + } else if (method == "getPreferences") { + m_preferences = *m_prefStore->GetValues(); + SendMessageAck(request_id, &m_preferences); + return; + } else if (method == "setPreference") { + std::string name; + std::string value; + if (!params->GetString(0, &name) || !params->GetString(1, &value)) + return; + SetPreference(name, value); + } else if (method == "removePreference") { + std::string name; + if (!params->GetString(0, &name)) + return; + RemovePreference(name); + } else if (method == "clearPreferences") { + ClearPreferences(); + } else if (method == "requestFileSystems") { + web_contents()->GetMainFrame()->ExecuteJavaScript(base::ASCIIToUTF16("DevToolsAPI.fileSystemsLoaded([]);")); + } else if (method == "reattach") { + m_agentHost->DetachClient(this); + m_agentHost->AttachClient(this); + } else if (method == "openInNewTab") { + std::string urlString; + if (!params->GetString(0, &urlString)) + return; + GURL url(urlString); + if (!url.is_valid()) + return; + content::OpenURLParams openParams(GURL(url), + content::Referrer(), + WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui::PAGE_TRANSITION_LINK, + false); + m_frontendDelegate->OpenURLFromTab(nullptr, openParams); + } else if (method == "bringToFront") { + Activate(); + } else { + VLOG(1) << "Unimplemented devtools method: " << message; + return; + } + + if (request_id) + SendMessageAck(request_id, nullptr); +} + +void DevToolsFrontendQt::DispatchProtocolMessage(content::DevToolsAgentHost *agentHost, const std::string &message) +{ + Q_UNUSED(agentHost); + if (message.length() < kMaxMessageChunkSize) { + std::string param; + base::EscapeJSONString(message, true, ¶m); + std::string code = "DevToolsAPI.dispatchMessage(" + param + ");"; + base::string16 javascript = base::UTF8ToUTF16(code); + web_contents()->GetMainFrame()->ExecuteJavaScript(javascript); + return; + } + + size_t total_size = message.length(); + for (size_t pos = 0; pos < message.length(); pos += kMaxMessageChunkSize) { + std::string param; + base::EscapeJSONString(message.substr(pos, kMaxMessageChunkSize), true, ¶m); + std::string code = "DevToolsAPI.dispatchMessageChunk(" + param + "," + + std::to_string(pos ? 0 : total_size) + ");"; + base::string16 javascript = base::UTF8ToUTF16(code); + web_contents()->GetMainFrame()->ExecuteJavaScript(javascript); + } +} + +void DevToolsFrontendQt::OnURLFetchComplete(const net::URLFetcher *source) +{ + // TODO(pfeldman): this is a copy of chrome's devtools_ui_bindings.cc. + // We should handle some of the commands including this one in content. + DCHECK(source); + PendingRequestsMap::iterator it = m_pendingRequests.find(source); + DCHECK(it != m_pendingRequests.end()); + + base::DictionaryValue response; + auto headers = base::MakeUnique<base::DictionaryValue>(); + net::HttpResponseHeaders* rh = source->GetResponseHeaders(); + response.SetInteger("statusCode", rh ? rh->response_code() : 200); + + size_t iterator = 0; + std::string name; + std::string value; + while (rh && rh->EnumerateHeaderLines(&iterator, &name, &value)) + headers->SetString(name, value); + response.Set("headers", std::move(headers)); + + SendMessageAck(it->second, &response); + m_pendingRequests.erase(it); + delete source; +} + +void DevToolsFrontendQt::CallClientFunction(const std::string &function_name, + const base::Value *arg1, + const base::Value *arg2, + const base::Value *arg3) +{ + std::string javascript = function_name + "("; + if (arg1) { + std::string json; + base::JSONWriter::Write(*arg1, &json); + javascript.append(json); + if (arg2) { + base::JSONWriter::Write(*arg2, &json); + javascript.append(", ").append(json); + if (arg3) { + base::JSONWriter::Write(*arg3, &json); + javascript.append(", ").append(json); + } + } + } + javascript.append(");"); + web_contents()->GetMainFrame()->ExecuteJavaScript(base::UTF8ToUTF16(javascript)); +} + +void DevToolsFrontendQt::SendMessageAck(int request_id, const base::Value *arg) +{ + base::Value id_value(request_id); + CallClientFunction("DevToolsAPI.embedderMessageAck", &id_value, arg, nullptr); +} + +void DevToolsFrontendQt::AgentHostClosed(content::DevToolsAgentHost *agentHost, bool /*replaced*/) +{ + DCHECK(agentHost == m_agentHost.get()); + m_agentHost = nullptr; + m_inspectedContents = nullptr; + Close(); +} + +} // namespace QtWebEngineCore diff --git a/src/core/devtools_frontend_qt.h b/src/core/devtools_frontend_qt.h new file mode 100644 index 000000000..7d00845ba --- /dev/null +++ b/src/core/devtools_frontend_qt.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef DEVTOOLS_FRONTEND_QT_H +#define DEVTOOLS_FRONTEND_QT_H + +#include <memory> + +#include "web_contents_delegate_qt.h" + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/values.h" +#include "content/public/browser/devtools_agent_host.h" +#include "content/public/browser/devtools_frontend_host.h" +#include "content/public/browser/web_contents_observer.h" +#include "net/url_request/url_fetcher_delegate.h" + +namespace base { +class Value; +} + +namespace content { +class NavigationHandle; +class RenderViewHost; +class WebContents; +} // namespace content + +class PersistentPrefStore; + +namespace QtWebEngineCore { + +class DevToolsFrontendQt : public content::WebContentsObserver + , public content::DevToolsAgentHostClient + , public net::URLFetcherDelegate { +public: + static DevToolsFrontendQt *Show(QSharedPointer<WebContentsAdapter> frontendAdapter, content::WebContents *inspectedContents); + + void Activate(); + void Focus(); + void InspectElementAt(int x, int y); + void Close(); + + void DisconnectFromTarget(); + + void CallClientFunction(const std::string& function_name, + const base::Value* arg1, + const base::Value* arg2, + const base::Value* arg3); + + WebContentsDelegateQt *frontendDelegate() const + { + return m_frontendDelegate; + } + +protected: + DevToolsFrontendQt(QSharedPointer<WebContentsAdapter> webContentsAdapter, content::WebContents *inspectedContents); + ~DevToolsFrontendQt() override; + + // content::DevToolsAgentHostClient implementation. + void AgentHostClosed(content::DevToolsAgentHost* agent_host, bool replaced) override; + void DispatchProtocolMessage(content::DevToolsAgentHost* agent_host, const std::string& message) override; + + void SetPreferences(const std::string& json); + virtual void HandleMessageFromDevToolsFrontend(const std::string& message); + +private: + // WebContentsObserver overrides + void ReadyToCommitNavigation(content::NavigationHandle* navigation_handle) override; + void DocumentAvailableInMainFrame() override; + void WebContentsDestroyed() override; + + // net::URLFetcherDelegate overrides. + void OnURLFetchComplete(const net::URLFetcher* source) override; + + void SendMessageAck(int request_id, const base::Value* arg1); + void SetPreference(const std::string &name, const std::string &value); + void RemovePreference(const std::string &name); + void ClearPreferences(); + void CreateJsonPreferences(bool clear); + + // We shouldn't be keeping it alive + QWeakPointer<WebContentsAdapter> m_webContentsAdapter; + WebContentsDelegateQt *m_frontendDelegate; + content::WebContents *m_inspectedContents; + scoped_refptr<content::DevToolsAgentHost> m_agentHost; + int m_inspect_element_at_x; + int m_inspect_element_at_y; + std::unique_ptr<content::DevToolsFrontendHost> m_frontendHost; + using PendingRequestsMap = std::map<const net::URLFetcher*, int>; + PendingRequestsMap m_pendingRequests; + base::DictionaryValue m_preferences; + scoped_refptr<PersistentPrefStore> m_prefStore; + base::WeakPtrFactory<DevToolsFrontendQt> m_weakFactory; + + DISALLOW_COPY_AND_ASSIGN(DevToolsFrontendQt); +}; + +} // namespace QtWebEngineCore + +#endif // DEVTOOLS_FRONTEND_QT_H diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp index e084de463..762f91b3c 100644 --- a/src/core/render_widget_host_view_qt.cpp +++ b/src/core/render_widget_host_view_qt.cpp @@ -487,7 +487,7 @@ bool RenderWidgetHostViewQt::HasFocus() const bool RenderWidgetHostViewQt::IsSurfaceAvailableForCopy() const { - return true; + return false; } void RenderWidgetHostViewQt::Show() diff --git a/src/core/web_contents_adapter.cpp b/src/core/web_contents_adapter.cpp index 68481dccc..515981b1b 100644 --- a/src/core/web_contents_adapter.cpp +++ b/src/core/web_contents_adapter.cpp @@ -48,6 +48,7 @@ #include "browser_context_adapter.h" #include "browser_context_adapter_client.h" #include "browser_context_qt.h" +#include "devtools_frontend_qt.h" #include "download_manager_delegate_qt.h" #include "media_capture_devices_dispatcher.h" #include "print_view_manager_qt.h" @@ -359,6 +360,7 @@ WebContentsAdapterPrivate::WebContentsAdapterPrivate() , nextRequestId(CallbackDirectory::ReservedCallbackIdsEnd) , lastFindRequestId(0) , currentDropAction(blink::kWebDragOperationNone) + , devToolsFrontend(nullptr) { } @@ -1035,19 +1037,56 @@ void WebContentsAdapter::executeMediaPlayerActionAt(const QPoint &location, Medi void WebContentsAdapter::inspectElementAt(const QPoint &location) { Q_D(WebContentsAdapter); - if (content::DevToolsAgentHost::HasFor(d->webContents.get())) { - content::DevToolsAgentHost::GetOrCreateFor(d->webContents.get())->InspectElement(nullptr, location.x(), location.y()); + if (d->devToolsFrontend) { + d->devToolsFrontend->InspectElementAt(location.x(), location.y()); + return; } + if (content::DevToolsAgentHost::HasFor(d->webContents.get())) + content::DevToolsAgentHost::GetOrCreateFor(d->webContents.get())->InspectElement(nullptr, location.x(), location.y()); } bool WebContentsAdapter::hasInspector() const { - const Q_D(WebContentsAdapter); + Q_D(const WebContentsAdapter); + if (d->devToolsFrontend) + return true; if (content::DevToolsAgentHost::HasFor(d->webContents.get())) return content::DevToolsAgentHost::GetOrCreateFor(d->webContents.get())->IsAttached(); return false; } +void WebContentsAdapter::openDevToolsFrontend(QSharedPointer<WebContentsAdapter> frontendAdapter) +{ + Q_D(WebContentsAdapter); + if (d->devToolsFrontend && + d->devToolsFrontend->frontendDelegate() == frontendAdapter->webContents()->GetDelegate()) + return; + + if (d->devToolsFrontend) { + d->devToolsFrontend->DisconnectFromTarget(); + d->devToolsFrontend->Close(); + } + + d->devToolsFrontend = DevToolsFrontendQt::Show(frontendAdapter, d->webContents.get()); +} + +void WebContentsAdapter::closeDevToolsFrontend() +{ + Q_D(WebContentsAdapter); + if (d->devToolsFrontend) { + d->devToolsFrontend->DisconnectFromTarget(); + d->devToolsFrontend->Close(); + } +} + +void WebContentsAdapter::devToolsFrontendDestroyed(DevToolsFrontendQt *frontend) +{ + Q_D(WebContentsAdapter); + Q_ASSERT(frontend == d->devToolsFrontend); + Q_UNUSED(frontend); + d->devToolsFrontend = nullptr; +} + void WebContentsAdapter::exitFullScreen() { Q_D(WebContentsAdapter); diff --git a/src/core/web_contents_adapter.h b/src/core/web_contents_adapter.h index 51fd2891d..9ce438119 100644 --- a/src/core/web_contents_adapter.h +++ b/src/core/web_contents_adapter.h @@ -67,6 +67,7 @@ QT_END_NAMESPACE namespace QtWebEngineCore { class BrowserContextQt; +class DevToolsFrontendQt; class MessagePassingInterface; class WebContentsAdapterPrivate; class FaviconManager; @@ -148,6 +149,9 @@ public: void exitFullScreen(); void requestClose(); void changedFullScreen(); + void openDevToolsFrontend(QSharedPointer<WebContentsAdapter> devtoolsFrontend); + void closeDevToolsFrontend(); + void devToolsFrontendDestroyed(DevToolsFrontendQt *frontend); void wasShown(); void wasHidden(); diff --git a/src/core/web_contents_adapter_p.h b/src/core/web_contents_adapter_p.h index 94f3ca08b..50096c92e 100644 --- a/src/core/web_contents_adapter_p.h +++ b/src/core/web_contents_adapter_p.h @@ -73,6 +73,7 @@ struct DropData; namespace QtWebEngineCore { class BrowserContextAdapter; +class DevToolsFrontendQt; class RenderViewObserverHostQt; class WebChannelIPCTransportHost; class WebContentsAdapterClient; @@ -100,6 +101,7 @@ public: gfx::Point lastDragClientPos; gfx::Point lastDragScreenPos; std::unique_ptr<QTemporaryDir> dndTmpDir; + DevToolsFrontendQt *devToolsFrontend; }; } // namespace QtWebEngineCore diff --git a/src/core/web_contents_delegate_qt.cpp b/src/core/web_contents_delegate_qt.cpp index 25aa9ae04..2086bea4b 100644 --- a/src/core/web_contents_delegate_qt.cpp +++ b/src/core/web_contents_delegate_qt.cpp @@ -604,4 +604,9 @@ WebEngineSettings *WebContentsDelegateQt::webEngineSettings() const { return m_viewClient->webEngineSettings(); } +WebContentsAdapter *WebContentsDelegateQt::webContentsAdapter() const +{ + return m_viewClient->webContentsAdapter(); +} + } // namespace QtWebEngineCore diff --git a/src/core/web_contents_delegate_qt.h b/src/core/web_contents_delegate_qt.h index 87704d3c6..c056d36ab 100644 --- a/src/core/web_contents_delegate_qt.h +++ b/src/core/web_contents_delegate_qt.h @@ -67,6 +67,7 @@ namespace content { namespace QtWebEngineCore { +class WebContentsAdapter; class WebContentsAdapterClient; class WebEngineSettings; @@ -152,6 +153,7 @@ public: const SavePageInfo &savePageInfo() { return m_savePageInfo; } WebEngineSettings *webEngineSettings() const; + WebContentsAdapter *webContentsAdapter() const; private: QWeakPointer<WebContentsAdapter> createWindow(content::WebContents *new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, bool user_gesture); diff --git a/src/webengine/api/qquickwebengineview.cpp b/src/webengine/api/qquickwebengineview.cpp index 0636d8471..2721039ae 100644 --- a/src/webengine/api/qquickwebengineview.cpp +++ b/src/webengine/api/qquickwebengineview.cpp @@ -823,6 +823,11 @@ void QQuickWebEngineViewPrivate::adoptWebContents(WebContentsAdapter *webContent if (m_webChannel) adapter->setWebChannel(m_webChannel, m_webChannelWorld); + if (devToolsView && devToolsView->d_ptr->adapter) + adapter->openDevToolsFrontend(devToolsView->d_ptr->adapter); + else if (inspectedView && inspectedView->d_ptr->adapter) + inspectedView->d_ptr->adapter->openDevToolsFrontend(adapter); + // set initial background color if non-default if (m_backgroundColor != Qt::white) adapter->backgroundColorChanged(); @@ -882,6 +887,8 @@ void QQuickWebEngineViewPrivate::ensureContentsAdapter() adapter->setWebChannel(m_webChannel, m_webChannelWorld); if (explicitUrl.isValid()) adapter->load(explicitUrl); + if (inspectedView) + inspectedView->d_ptr->adapter->openDevToolsFrontend(adapter); // push down the page's user scripts Q_FOREACH (QQuickWebEngineScript *script, m_userScripts) script->d_func()->bind(browserContextAdapter()->userResourceController(), adapter.data()); @@ -1419,6 +1426,54 @@ void QQuickWebEngineView::setWebChannelWorld(uint webChannelWorld) Q_EMIT webChannelWorldChanged(webChannelWorld); } +QQuickWebEngineView *QQuickWebEngineView::inspectedView() const +{ + Q_D(const QQuickWebEngineView); + return d->inspectedView; +} + +void QQuickWebEngineView::setInspectedView(QQuickWebEngineView *view) +{ + Q_D(QQuickWebEngineView); + if (d->inspectedView == view) + return; + QQuickWebEngineView *oldView = d->inspectedView; + d->inspectedView = nullptr; + if (oldView) + oldView->setDevToolsView(nullptr); + d->inspectedView = view; + if (view) + view->setDevToolsView(this); + Q_EMIT inspectedViewChanged(); +} + +QQuickWebEngineView *QQuickWebEngineView::devToolsView() const +{ + Q_D(const QQuickWebEngineView); + return d->devToolsView; +} + +void QQuickWebEngineView::setDevToolsView(QQuickWebEngineView *devToolsView) +{ + Q_D(QQuickWebEngineView); + if (d->devToolsView == devToolsView) + return; + QQuickWebEngineView *oldView = d->devToolsView; + d->devToolsView = nullptr; + if (oldView) + oldView->setInspectedView(nullptr); + d->devToolsView = devToolsView; + if (devToolsView) + devToolsView->setInspectedView(this); + if (d->adapter) { + if (devToolsView) + d->adapter->openDevToolsFrontend(devToolsView->d_ptr->adapter); + else + d->adapter->closeDevToolsFrontend(); + } + Q_EMIT devToolsViewChanged(); +} + void QQuickWebEngineView::grantFeaturePermission(const QUrl &securityOrigin, QQuickWebEngineView::Feature feature, bool granted) { if (!d_ptr->adapter) diff --git a/src/webengine/api/qquickwebengineview_p.h b/src/webengine/api/qquickwebengineview_p.h index ee7e01f26..275503d14 100644 --- a/src/webengine/api/qquickwebengineview_p.h +++ b/src/webengine/api/qquickwebengineview_p.h @@ -148,6 +148,8 @@ class Q_WEBENGINE_PRIVATE_EXPORT QQuickWebEngineView : public QQuickItem { Q_PROPERTY(bool recentlyAudible READ recentlyAudible NOTIFY recentlyAudibleChanged FINAL REVISION 3) Q_PROPERTY(uint webChannelWorld READ webChannelWorld WRITE setWebChannelWorld NOTIFY webChannelWorldChanged REVISION 3 FINAL) + Q_PROPERTY(QQuickWebEngineView *inspectedView READ inspectedView WRITE setInspectedView NOTIFY inspectedViewChanged REVISION 7 FINAL) + Q_PROPERTY(QQuickWebEngineView *devToolsView READ devToolsView WRITE setDevToolsView NOTIFY devToolsViewChanged REVISION 7 FINAL) #ifdef ENABLE_QML_TESTSUPPORT_API Q_PROPERTY(QQuickWebEngineTestSupport *testSupport READ testSupport WRITE setTestSupport NOTIFY testSupportChanged FINAL) #endif @@ -500,6 +502,11 @@ public: bool activeFocusOnPress() const; + void setInspectedView(QQuickWebEngineView *); + QQuickWebEngineView *inspectedView() const; + void setDevToolsView(QQuickWebEngineView *); + QQuickWebEngineView *devToolsView() const; + public Q_SLOTS: void runJavaScript(const QString&, const QJSValue & = QJSValue()); Q_REVISION(3) void runJavaScript(const QString&, quint32 worldId, const QJSValue & = QJSValue()); @@ -557,6 +564,8 @@ Q_SIGNALS: Q_REVISION(5) void pdfPrintingFinished(const QString &filePath, bool success); Q_REVISION(7) void quotaPermissionRequested(const QQuickWebEngineQuotaPermissionRequest &request); Q_REVISION(7) void geometryChangeRequested(const QRect &geometry, const QRect &frameGeometry); + Q_REVISION(7) void inspectedViewChanged(); + Q_REVISION(7) void devToolsViewChanged(); #ifdef ENABLE_QML_TESTSUPPORT_API void testSupportChanged(); diff --git a/src/webengine/api/qquickwebengineview_p_p.h b/src/webengine/api/qquickwebengineview_p_p.h index 708725787..a30614b2f 100644 --- a/src/webengine/api/qquickwebengineview_p_p.h +++ b/src/webengine/api/qquickwebengineview_p_p.h @@ -54,6 +54,7 @@ #include "qquickwebengineview_p.h" #include "web_contents_adapter_client.h" +#include <QPointer> #include <QScopedPointer> #include <QSharedData> #include <QString> @@ -185,6 +186,8 @@ public: QMap<quint64, QJSValue> m_callbacks; QList<QSharedPointer<CertificateErrorController> > m_certificateErrorControllers; QQmlWebChannel *m_webChannel; + QPointer<QQuickWebEngineView> inspectedView; + QPointer<QQuickWebEngineView> devToolsView; uint m_webChannelWorld; private: diff --git a/src/webengine/doc/src/webengineview_lgpl.qdoc b/src/webengine/doc/src/webengineview_lgpl.qdoc index 20db51793..fadce39ad 100644 --- a/src/webengine/doc/src/webengineview_lgpl.qdoc +++ b/src/webengine/doc/src/webengineview_lgpl.qdoc @@ -1377,3 +1377,25 @@ } \endqml */ + +/*! + \qmlproperty WebEngineView WebEngineView::inspectedView + \since QtWebEngine 1.7 + + The view this view is currently inspecting, if any. Setting it + will navigate to an internal URL with the developer tools of + the view set. + + \sa devToolView +*/ + +/*! + \qmlproperty WebEngineView WebEngineView::devToolsView + \since QtWebEngine 1.7 + + The view currently hosting the developer tools for this view. + Setting it to a new view will navigate that view to an internal + URL with the developer tools, and bind it to this view. + + \sa inspectedView +*/ diff --git a/src/webenginewidgets/api/qwebenginepage.cpp b/src/webenginewidgets/api/qwebenginepage.cpp index 4a892103d..3ca73f12d 100644 --- a/src/webenginewidgets/api/qwebenginepage.cpp +++ b/src/webenginewidgets/api/qwebenginepage.cpp @@ -832,6 +832,7 @@ QWebEnginePage::QWebEnginePage(QWebEngineProfile *profile, QObject* parent) QWebEnginePage::~QWebEnginePage() { Q_D(QWebEnginePage); + setDevToolsPage(nullptr); if (d->adapter) d->adapter->stopFinding(); QWebEngineViewPrivate::bind(d->view, 0); @@ -1996,6 +1997,94 @@ QWebEnginePage *QWebEnginePage::createWindow(WebWindowType type) return 0; } +/*! + \since 5.11 + Returns the page this page is inspecting, if any. + + Returns \c nullptr if this page is not a developer tools page. + + \sa setInspectedPage(), devToolsPage() +*/ + +QWebEnginePage *QWebEnginePage::inspectedPage() const +{ + Q_D(const QWebEnginePage); + return d->inspectedPage; +} + +/*! + \since 5.11 + Navigates this page to an internal URL that is the developer + tools of \a page. + + This is the same as calling setDevToolsPage() on \a page + with \c this as argument. + + \sa inspectedPage(), setDevToolsPage() +*/ + +void QWebEnginePage::setInspectedPage(QWebEnginePage *page) +{ + Q_D(QWebEnginePage); + if (d->inspectedPage == page) + return; + QWebEnginePage *oldPage = d->inspectedPage; + d->inspectedPage = nullptr; + if (oldPage) + oldPage->setDevToolsPage(nullptr); + d->inspectedPage = page; + if (page) + page->setDevToolsPage(this); +} + +/*! + \since 5.11 + Returns the pagethat is hosting the developer tools + of this page, if any. + + Returns \c nullptr if no developer tools page is set. + + \sa setDevToolsPage(), inspectedPage() +*/ + +QWebEnginePage *QWebEnginePage::devToolsPage() const +{ + Q_D(const QWebEnginePage); + return d->devToolsPage; +} + +/*! + \since 5.11 + Binds \a devToolsPage to be the developer tools of this page. + Triggers \a devToolsPage to navigate to an internal URL + with the developer tools. + + This is the same as calling setInspectedPage() on \a devToolsPage + with \c this as argument. + + \sa devToolsPage(), setInspectedPage() +*/ + +void QWebEnginePage::setDevToolsPage(QWebEnginePage *devToolsPage) +{ + Q_D(QWebEnginePage); + if (d->devToolsPage == devToolsPage) + return; + QWebEnginePage *oldDevTools = d->devToolsPage; + d->devToolsPage = nullptr; + if (oldDevTools) + oldDevTools->setInspectedPage(nullptr); + d->devToolsPage = devToolsPage; + if (devToolsPage) + devToolsPage->setInspectedPage(this); + if (d->adapter) { + if (devToolsPage) + d->adapter->openDevToolsFrontend(devToolsPage->d_ptr->adapter); + else + d->adapter->closeDevToolsFrontend(); + } +} + ASSERT_ENUMS_MATCH(FilePickerController::Open, QWebEnginePage::FileSelectOpen) ASSERT_ENUMS_MATCH(FilePickerController::OpenMultiple, QWebEnginePage::FileSelectOpenMultiple) diff --git a/src/webenginewidgets/api/qwebenginepage.h b/src/webenginewidgets/api/qwebenginepage.h index 3fad6ca09..531eef6e9 100644 --- a/src/webenginewidgets/api/qwebenginepage.h +++ b/src/webenginewidgets/api/qwebenginepage.h @@ -316,6 +316,11 @@ public: void print(QPrinter *printer, const QWebEngineCallback<bool> &resultCallback); #endif // QDOC + void setInspectedPage(QWebEnginePage *page); + QWebEnginePage *inspectedPage() const; + void setDevToolsPage(QWebEnginePage *page); + QWebEnginePage *devToolsPage() const; + const QWebEngineContextMenuData &contextMenuData() const; Q_SIGNALS: diff --git a/src/webenginewidgets/api/qwebenginepage_p.h b/src/webenginewidgets/api/qwebenginepage_p.h index 2d44d1849..66f479717 100644 --- a/src/webenginewidgets/api/qwebenginepage_p.h +++ b/src/webenginewidgets/api/qwebenginepage_p.h @@ -57,7 +57,9 @@ #include "qwebenginecontextmenudata.h" #include "qwebenginescriptcollection.h" #include "web_contents_adapter_client.h" + #include <QtCore/qcompilerdetection.h> +#include <QtCore/QPointer> namespace QtWebEngineCore { class RenderWidgetHostViewQtDelegate; @@ -175,6 +177,8 @@ public: unsigned int webChannelWorldId; QUrl iconUrl; bool m_navigationActionTriggered; + QPointer<QWebEnginePage> inspectedPage; + QPointer<QWebEnginePage> devToolsPage; mutable QtWebEngineCore::CallbackDirectory m_callbacks; mutable QAction *actions[QWebEnginePage::WebActionCount]; |