diff --git a/src/core/common/qt_messages.h b/src/core/common/qt_messages.h index 62d88521cf4b10f3e22461d869376189bd3a91e7..411d06bc85d69c23ddca49a515e55270ff9ceec8 100644 --- a/src/core/common/qt_messages.h +++ b/src/core/common/qt_messages.h @@ -40,8 +40,7 @@ IPC_MESSAGE_ROUTED1(RenderViewObserverQt_FetchDocumentInnerText, IPC_MESSAGE_ROUTED1(RenderViewObserverQt_SetBackgroundColor, uint32_t /* color */) -IPC_MESSAGE_ROUTED1(WebChannelIPCTransport_Install, uint /* worldId */) -IPC_MESSAGE_ROUTED1(WebChannelIPCTransport_Uninstall, uint /* worldId */) +IPC_MESSAGE_ROUTED1(WebChannelIPCTransport_SetWorldId, base::Optional<uint> /* worldId */) IPC_MESSAGE_ROUTED2(WebChannelIPCTransport_Message, std::vector<char> /*binaryJSON*/, uint /* worldId */) // User scripts messages diff --git a/src/core/renderer/content_renderer_client_qt.cpp b/src/core/renderer/content_renderer_client_qt.cpp index 56ebfec30f9c62eea4821f22a8680cc265fd1e60..74edc43693dea66ede8444d03b0925c329082534 100644 --- a/src/core/renderer/content_renderer_client_qt.cpp +++ b/src/core/renderer/content_renderer_client_qt.cpp @@ -123,13 +123,14 @@ void ContentRendererClientQt::RenderViewCreated(content::RenderView* render_view { // RenderViewObservers destroy themselves with their RenderView. new RenderViewObserverQt(render_view, m_webCacheImpl.data()); - new WebChannelIPCTransport(render_view); UserResourceController::instance()->renderViewCreated(render_view); } void ContentRendererClientQt::RenderFrameCreated(content::RenderFrame* render_frame) { new QtWebEngineCore::RenderFrameObserverQt(render_frame); + if (render_frame->IsMainFrame()) + new WebChannelIPCTransport(render_frame); UserResourceController::instance()->renderFrameCreated(render_frame); #if BUILDFLAG(ENABLE_SPELLCHECK) @@ -150,8 +151,6 @@ void ContentRendererClientQt::RunScriptsAtDocumentStart(content::RenderFrame* re if (!render_frame_observer || render_frame_observer->isFrameDetached()) return; // The frame is invisible to scripts. - if (WebChannelIPCTransport *transport = WebChannelIPCTransport::Get(render_frame->GetRenderView())) - transport->RunScriptsAtDocumentStart(render_frame); UserResourceController::instance()->RunScriptsAtDocumentStart(render_frame); } diff --git a/src/core/renderer/web_channel_ipc_transport.cpp b/src/core/renderer/web_channel_ipc_transport.cpp index 534ee302dcbff8880064064f36260cbdeb35759e..bb544168f7b77a92dd28fbb8810dd5dea23da9a6 100644 --- a/src/core/renderer/web_channel_ipc_transport.cpp +++ b/src/core/renderer/web_channel_ipc_transport.cpp @@ -45,13 +45,12 @@ #include "common/qt_messages.h" #include "content/public/renderer/render_frame.h" -#include "content/public/renderer/render_view.h" #include "gin/arguments.h" #include "gin/handle.h" #include "gin/object_template_builder.h" #include "gin/wrappable.h" +#include "third_party/WebKit/public/web/WebKit.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" -#include "third_party/WebKit/public/web/WebView.h" #include "v8/include/v8.h" #include <QJsonDocument> @@ -61,193 +60,189 @@ namespace QtWebEngineCore { class WebChannelTransport : public gin::Wrappable<WebChannelTransport> { public: static gin::WrapperInfo kWrapperInfo; - static void Install(blink::WebFrame *frame, uint worldId); - static void Uninstall(blink::WebFrame *frame, uint worldId); + static void Install(blink::WebLocalFrame *frame, uint worldId); + static void Uninstall(blink::WebLocalFrame *frame, uint worldId); private: - content::RenderView *GetRenderView(v8::Isolate *isolate); - WebChannelTransport() { } - gin::ObjectTemplateBuilder GetObjectTemplateBuilder(v8::Isolate *isolate) override; + WebChannelTransport() {} + bool NativeQtSendMessage(gin::Arguments *args); - bool NativeQtSendMessage(gin::Arguments *args) - { - content::RenderView *renderView = GetRenderView(args->isolate()); - if (!renderView || args->Length() != 1) - return false; - v8::Handle<v8::Value> val; - args->GetNext(&val); - if (!val->IsString() && !val->IsStringObject()) - return false; - v8::String::Utf8Value utf8(val->ToString()); - - QByteArray valueData(*utf8, utf8.length()); - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(valueData, &error); - if (error.error != QJsonParseError::NoError) { - qWarning("%s %d: Parsing error: %s",__FILE__, __LINE__, qPrintable(error.errorString())); - return false; - } - int size = 0; - const char *rawData = doc.rawData(&size); - if (size == 0) - return false; - renderView->Send(new WebChannelIPCTransportHost_SendMessage(renderView->GetRoutingID(), std::vector<char>(rawData, rawData + size))); - return true; - } + // gin::WrappableBase + gin::ObjectTemplateBuilder GetObjectTemplateBuilder(v8::Isolate *isolate) override; DISALLOW_COPY_AND_ASSIGN(WebChannelTransport); }; gin::WrapperInfo WebChannelTransport::kWrapperInfo = { gin::kEmbedderNativeGin }; -void WebChannelTransport::Install(blink::WebFrame *frame, uint worldId) +void WebChannelTransport::Install(blink::WebLocalFrame *frame, uint worldId) { - v8::Isolate *isolate = v8::Isolate::GetCurrent(); + v8::Isolate *isolate = blink::MainThreadIsolate(); v8::HandleScope handleScope(isolate); - v8::Handle<v8::Context> context; + v8::Local<v8::Context> context; if (worldId == 0) - context = frame->ToWebLocalFrame()->MainWorldScriptContext(); + context = frame->MainWorldScriptContext(); else - context = frame->ToWebLocalFrame()->IsolatedWorldScriptContext(worldId); + context = frame->IsolatedWorldScriptContext(worldId); v8::Context::Scope contextScope(context); gin::Handle<WebChannelTransport> transport = gin::CreateHandle(isolate, new WebChannelTransport); - v8::Handle<v8::Object> global = context->Global(); - v8::Handle<v8::Object> qt = global->Get(gin::StringToV8(isolate, "qt"))->ToObject(); - if (qt.IsEmpty()) { - qt = v8::Object::New(isolate); - global->Set(gin::StringToV8(isolate, "qt"), qt); + + v8::Local<v8::Object> global = context->Global(); + v8::Local<v8::Value> qtObjectValue = global->Get(gin::StringToV8(isolate, "qt")); + v8::Local<v8::Object> qtObject; + if (qtObjectValue.IsEmpty() || !qtObjectValue->IsObject()) { + qtObject = v8::Object::New(isolate); + global->Set(gin::StringToV8(isolate, "qt"), qtObject); + } else { + qtObject = v8::Local<v8::Object>::Cast(qtObjectValue); } - qt->Set(gin::StringToV8(isolate, "webChannelTransport"), transport.ToV8()); + qtObject->Set(gin::StringToV8(isolate, "webChannelTransport"), transport.ToV8()); } -void WebChannelTransport::Uninstall(blink::WebFrame *frame, uint worldId) +void WebChannelTransport::Uninstall(blink::WebLocalFrame *frame, uint worldId) { - v8::Isolate *isolate = v8::Isolate::GetCurrent(); + v8::Isolate *isolate = blink::MainThreadIsolate(); v8::HandleScope handleScope(isolate); - v8::Handle<v8::Context> context; + v8::Local<v8::Context> context; if (worldId == 0) - context = frame->ToWebLocalFrame()->MainWorldScriptContext(); + context = frame->MainWorldScriptContext(); else - context = frame->ToWebLocalFrame()->IsolatedWorldScriptContext(worldId); + context = frame->IsolatedWorldScriptContext(worldId); v8::Context::Scope contextScope(context); - v8::Handle<v8::Object> global(context->Global()); - v8::Handle<v8::Object> qt = global->Get(gin::StringToV8(isolate, "qt"))->ToObject(); - if (qt.IsEmpty()) + v8::Local<v8::Object> global(context->Global()); + v8::Local<v8::Value> qtObjectValue = global->Get(gin::StringToV8(isolate, "qt")); + if (qtObjectValue.IsEmpty() || !qtObjectValue->IsObject()) return; - qt->Delete(gin::StringToV8(isolate, "webChannelTransport")); -} - -gin::ObjectTemplateBuilder WebChannelTransport::GetObjectTemplateBuilder(v8::Isolate *isolate) -{ - return gin::Wrappable<WebChannelTransport>::GetObjectTemplateBuilder(isolate).SetMethod("send", &WebChannelTransport::NativeQtSendMessage); + v8::Local<v8::Object> qtObject = v8::Local<v8::Object>::Cast(qtObjectValue); + qtObject->Delete(gin::StringToV8(isolate, "webChannelTransport")); } -content::RenderView *WebChannelTransport::GetRenderView(v8::Isolate *isolate) +bool WebChannelTransport::NativeQtSendMessage(gin::Arguments *args) { - blink::WebLocalFrame *webframe = blink::WebLocalFrame::FrameForContext(isolate->GetCurrentContext()); - DCHECK(webframe) << "There should be an active frame since we just got a native function called."; - if (!webframe) - return 0; + blink::WebLocalFrame *frame = blink::WebLocalFrame::FrameForCurrentContext(); + if (!frame || !frame->View()) + return false; + + content::RenderFrame *renderFrame = content::RenderFrame::FromWebFrame(frame); + if (!renderFrame) + return false; + + std::string message; + if (!args->GetNext(&message)) + return false; + + QByteArray valueData(message.data(), message.size()); + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(valueData, &error); + if (error.error != QJsonParseError::NoError) { + LOG(WARNING) << "Parsing error: " << qPrintable(error.errorString()); + return false; + } - blink::WebView *webview = webframe->View(); - if (!webview) - return 0; // can happen during closing + int size = 0; + const char *rawData = doc.rawData(&size); + if (size == 0) + return false; - return content::RenderView::FromWebView(webview); + renderFrame->Send(new WebChannelIPCTransportHost_SendMessage( + renderFrame->GetRoutingID(), + std::vector<char>(rawData, rawData + size))); + return true; } -WebChannelIPCTransport::WebChannelIPCTransport(content::RenderView *renderView) - : content::RenderViewObserver(renderView) - , content::RenderViewObserverTracker<WebChannelIPCTransport>(renderView) - , m_installed(false) - , m_installedWorldId(0) +gin::ObjectTemplateBuilder WebChannelTransport::GetObjectTemplateBuilder(v8::Isolate *isolate) { + return gin::Wrappable<WebChannelTransport>::GetObjectTemplateBuilder(isolate) + .SetMethod("send", &WebChannelTransport::NativeQtSendMessage); } -void WebChannelIPCTransport::RunScriptsAtDocumentStart(content::RenderFrame *render_frame) +WebChannelIPCTransport::WebChannelIPCTransport(content::RenderFrame *renderFrame) + : content::RenderFrameObserver(renderFrame) { - // JavaScript run before this point doesn't stick, and needs to be redone. - // ### FIXME: we should try no even installing before - if (m_installed && render_frame->IsMainFrame()) - WebChannelTransport::Install(render_frame->GetWebFrame(), m_installedWorldId); } - -void WebChannelIPCTransport::installWebChannel(uint worldId) +void WebChannelIPCTransport::setWorldId(base::Optional<uint> worldId) { - blink::WebView *webView = render_view()->GetWebView(); - if (!webView) + if (m_worldId == worldId) return; - WebChannelTransport::Install(webView->MainFrame(), worldId); - m_installed = true; - m_installedWorldId = worldId; -} -void WebChannelIPCTransport::uninstallWebChannel(uint worldId) -{ - Q_ASSERT(worldId == m_installedWorldId); - blink::WebView *webView = render_view()->GetWebView(); - if (!webView) - return; - WebChannelTransport::Uninstall(webView->MainFrame(), worldId); - m_installed = false; + if (m_worldId && m_canUseContext) + WebChannelTransport::Uninstall(render_frame()->GetWebFrame(), *m_worldId); + + m_worldId = worldId; + + if (m_worldId && m_canUseContext) + WebChannelTransport::Install(render_frame()->GetWebFrame(), *m_worldId); } -void WebChannelIPCTransport::dispatchWebChannelMessage(const std::vector<char> &binaryJSON, uint worldId) +void WebChannelIPCTransport::dispatchWebChannelMessage(const std::vector<char> &binaryJson, uint worldId) { - blink::WebView *webView = render_view()->GetWebView(); - if (!webView) - return; + DCHECK(m_canUseContext); + DCHECK(m_worldId == worldId); - QJsonDocument doc = QJsonDocument::fromRawData(binaryJSON.data(), binaryJSON.size(), QJsonDocument::BypassValidation); - Q_ASSERT(doc.isObject()); + QJsonDocument doc = QJsonDocument::fromRawData(binaryJson.data(), binaryJson.size(), QJsonDocument::BypassValidation); + DCHECK(doc.isObject()); QByteArray json = doc.toJson(QJsonDocument::Compact); - v8::Isolate *isolate = v8::Isolate::GetCurrent(); + blink::WebLocalFrame *frame = render_frame()->GetWebFrame(); + v8::Isolate *isolate = blink::MainThreadIsolate(); v8::HandleScope handleScope(isolate); - blink::WebFrame *frame = webView->MainFrame(); - v8::Handle<v8::Context> context; + v8::Local<v8::Context> context; if (worldId == 0) - context = frame->ToWebLocalFrame()->MainWorldScriptContext(); + context = frame->MainWorldScriptContext(); else - context = frame->ToWebLocalFrame()->IsolatedWorldScriptContext(worldId); + context = frame->IsolatedWorldScriptContext(worldId); v8::Context::Scope contextScope(context); - v8::Handle<v8::Object> global(context->Global()); - v8::Handle<v8::Value> qtObjectValue(global->Get(gin::StringToV8(isolate, "qt"))); - if (!qtObjectValue->IsObject()) + v8::Local<v8::Object> global(context->Global()); + v8::Local<v8::Value> qtObjectValue(global->Get(gin::StringToV8(isolate, "qt"))); + if (qtObjectValue.IsEmpty() || !qtObjectValue->IsObject()) return; - v8::Handle<v8::Value> webChannelObjectValue(qtObjectValue->ToObject()->Get(gin::StringToV8(isolate, "webChannelTransport"))); - if (!webChannelObjectValue->IsObject()) + v8::Local<v8::Object> qtObject = v8::Local<v8::Object>::Cast(qtObjectValue); + v8::Local<v8::Value> webChannelObjectValue(qtObject->Get(gin::StringToV8(isolate, "webChannelTransport"))); + if (webChannelObjectValue.IsEmpty() || !webChannelObjectValue->IsObject()) return; - v8::Handle<v8::Value> onmessageCallbackValue(webChannelObjectValue->ToObject()->Get(gin::StringToV8(isolate, "onmessage"))); - if (!onmessageCallbackValue->IsFunction()) { - qWarning("onmessage is not a callable property of qt.webChannelTransport. Some things might not work as expected."); + v8::Local<v8::Object> webChannelObject = v8::Local<v8::Object>::Cast(webChannelObjectValue); + v8::Local<v8::Value> callbackValue(webChannelObject->Get(gin::StringToV8(isolate, "onmessage"))); + if (callbackValue.IsEmpty() || !callbackValue->IsFunction()) { + LOG(WARNING) << "onmessage is not a callable property of qt.webChannelTransport. Some things might not work as expected."; return; } - v8::Handle<v8::Object> messageObject(v8::Object::New(isolate)); + v8::Local<v8::Object> messageObject(v8::Object::New(isolate)); v8::Maybe<bool> wasSet = messageObject->DefineOwnProperty( context, v8::String::NewFromUtf8(isolate, "data"), v8::String::NewFromUtf8(isolate, json.constData(), v8::String::kNormalString, json.size()), v8::PropertyAttribute(v8::ReadOnly | v8::DontDelete)); - Q_ASSERT(!wasSet.IsNothing() && wasSet.FromJust()); + DCHECK(!wasSet.IsNothing() && wasSet.FromJust()); + + v8::Local<v8::Function> callback = v8::Local<v8::Function>::Cast(callbackValue); + v8::Local<v8::Value> argv[] = { messageObject }; + frame->CallFunctionEvenIfScriptDisabled(callback, webChannelObject, 1, argv); +} + +void WebChannelIPCTransport::WillReleaseScriptContext(v8::Local<v8::Context> context, int worldId) +{ + if (static_cast<uint>(worldId) == m_worldId) + m_canUseContext = false; +} - v8::Handle<v8::Function> callback = v8::Handle<v8::Function>::Cast(onmessageCallbackValue); - const int argc = 1; - v8::Handle<v8::Value> argv[argc]; - argv[0] = messageObject; - frame->ToWebLocalFrame()->CallFunctionEvenIfScriptDisabled(callback, webChannelObjectValue->ToObject(), argc, argv); +void WebChannelIPCTransport::DidClearWindowObject() +{ + if (!m_canUseContext) { + m_canUseContext = true; + if (m_worldId) + WebChannelTransport::Install(render_frame()->GetWebFrame(), *m_worldId); + } } bool WebChannelIPCTransport::OnMessageReceived(const IPC::Message &message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(WebChannelIPCTransport, message) - IPC_MESSAGE_HANDLER(WebChannelIPCTransport_Install, installWebChannel) - IPC_MESSAGE_HANDLER(WebChannelIPCTransport_Uninstall, uninstallWebChannel) + IPC_MESSAGE_HANDLER(WebChannelIPCTransport_SetWorldId, setWorldId) IPC_MESSAGE_HANDLER(WebChannelIPCTransport_Message, dispatchWebChannelMessage) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() @@ -259,4 +254,4 @@ void WebChannelIPCTransport::OnDestruct() delete this; } -} // namespace +} // namespace QtWebEngineCore diff --git a/src/core/renderer/web_channel_ipc_transport.h b/src/core/renderer/web_channel_ipc_transport.h index 04041c6c7d0bc438f40bebfb977aa4e853e0f16a..19494360a5a0899908934e8e469ced657b597bef 100644 --- a/src/core/renderer/web_channel_ipc_transport.h +++ b/src/core/renderer/web_channel_ipc_transport.h @@ -40,41 +40,31 @@ #ifndef WEB_CHANNEL_IPC_TRANSPORT_H #define WEB_CHANNEL_IPC_TRANSPORT_H -#include "base/values.h" -#include "content/public/renderer/render_view_observer.h" -#include "content/public/renderer/render_view_observer_tracker.h" +#include "content/public/renderer/render_frame_observer.h" #include <QtCore/qglobal.h> -namespace content { -class RenderFrame; -} - -namespace v8 { -class Extension; -} - namespace QtWebEngineCore { -class WebChannelIPCTransport : public content::RenderViewObserver - , public content::RenderViewObserverTracker<WebChannelIPCTransport> -{ +class WebChannelIPCTransport : private content::RenderFrameObserver { public: - WebChannelIPCTransport(content::RenderView *); - - void RunScriptsAtDocumentStart(content::RenderFrame *render_frame); + WebChannelIPCTransport(content::RenderFrame *); private: - void dispatchWebChannelMessage(const std::vector<char> &binaryJSON, uint worldId); - void installWebChannel(uint worldId); - void uninstallWebChannel(uint worldId); + void setWorldId(base::Optional<uint> worldId); + void dispatchWebChannelMessage(const std::vector<char> &binaryJson, uint worldId); - // content::RenderViewObserver overrides: + // RenderFrameObserver + void WillReleaseScriptContext(v8::Local<v8::Context> context, int worldId) override; + void DidClearWindowObject() override; bool OnMessageReceived(const IPC::Message &message) override; void OnDestruct() override; - bool m_installed; - uint m_installedWorldId; + // The worldId from our WebChannelIPCTransportHost or empty when there is no + // WebChannelIPCTransportHost. + base::Optional<uint> m_worldId; + // True means it's currently OK to manipulate the frame's script context. + bool m_canUseContext = false; }; } // namespace diff --git a/src/core/renderer_host/web_channel_ipc_transport_host.cpp b/src/core/renderer_host/web_channel_ipc_transport_host.cpp index b624d7e454d5322b9083e379bd80cb7a9c30edf5..6b32093a6c32f817d434a8f453b755f38bfd8fbb 100644 --- a/src/core/renderer_host/web_channel_ipc_transport_host.cpp +++ b/src/core/renderer_host/web_channel_ipc_transport_host.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtWebEngine module of the Qt Toolkit. @@ -39,70 +39,71 @@ #include "web_channel_ipc_transport_host.h" -#include "base/strings/string16.h" -#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" #include "common/qt_messages.h" -#include "type_conversion.h" #include <QJsonDocument> #include <QJsonObject> +#include <QLoggingCategory> namespace QtWebEngineCore { +Q_LOGGING_CATEGORY(log, "qt.webengine.webchanneltransport"); + +inline QDebug operator<<(QDebug stream, content::RenderFrameHost *frame) +{ + return stream << "frame " << frame->GetRoutingID() << " in process " << frame->GetProcess()->GetID(); +} + +template <class T> +inline QDebug operator<<(QDebug stream, const base::Optional<T> &opt) +{ + if (opt) + return stream << *opt; + else + return stream << "nullopt"; +} + WebChannelIPCTransportHost::WebChannelIPCTransportHost(content::WebContents *contents, uint worldId, QObject *parent) : QWebChannelAbstractTransport(parent) , content::WebContentsObserver(contents) - , m_worldId(worldId) { - contents->GetRenderViewHost()->Send( - new WebChannelIPCTransport_Install( - contents->GetRenderViewHost()->GetRoutingID(), - m_worldId)); + setWorldId(worldId); } WebChannelIPCTransportHost::~WebChannelIPCTransportHost() { + setWorldId(base::nullopt); } -void WebChannelIPCTransportHost::RenderViewHostChanged(content::RenderViewHost *oldHost, content::RenderViewHost *) -{ - if (oldHost) - oldHost->Send(new WebChannelIPCTransport_Uninstall(oldHost->GetRoutingID(), m_worldId)); -} - -void WebChannelIPCTransportHost::RenderViewCreated(content::RenderViewHost *view_host) +void WebChannelIPCTransportHost::sendMessage(const QJsonObject &message) { - // Make sure the new view knows a webchannel is installed and in which world. - view_host->Send(new WebChannelIPCTransport_Install(view_host->GetRoutingID(), m_worldId)); + QJsonDocument doc(message); + int size = 0; + const char *rawData = doc.rawData(&size); + content::RenderFrameHost *frame = web_contents()->GetMainFrame(); + qCDebug(log).nospace() << "sending webchannel message to " << frame << ": " << doc; + frame->Send(new WebChannelIPCTransport_Message(frame->GetRoutingID(), std::vector<char>(rawData, rawData + size), *m_worldId)); } -void WebChannelIPCTransportHost::setWorldId(uint worldId) +void WebChannelIPCTransportHost::setWorldId(base::Optional<uint> worldId) { - if (worldId == m_worldId) + if (m_worldId == worldId) return; - web_contents()->GetRenderViewHost()->Send( - new WebChannelIPCTransport_Uninstall( - web_contents()->GetRenderViewHost()->GetRoutingID(), - m_worldId)); + for (content::RenderFrameHost *frame : web_contents()->GetAllFrames()) + setWorldId(frame, worldId); m_worldId = worldId; - web_contents()->GetRenderViewHost()->Send( - new WebChannelIPCTransport_Install( - web_contents()->GetRenderViewHost()->GetRoutingID(), - m_worldId)); } -void WebChannelIPCTransportHost::sendMessage(const QJsonObject &message) +void WebChannelIPCTransportHost::setWorldId(content::RenderFrameHost *frame, base::Optional<uint> worldId) { - QJsonDocument doc(message); - int size = 0; - const char *rawData = doc.rawData(&size); - web_contents()->GetRenderViewHost()->Send( - new WebChannelIPCTransport_Message( - web_contents()->GetRenderViewHost()->GetRoutingID(), - std::vector<char>(rawData, rawData + size), - m_worldId)); + if (!frame->IsRenderFrameLive()) + return; + qCDebug(log).nospace() << "sending setWorldId(" << worldId << ") message to " << frame; + frame->Send(new WebChannelIPCTransport_SetWorldId(frame->GetRoutingID(), worldId)); } void WebChannelIPCTransportHost::onWebChannelMessage(const std::vector<char> &message) @@ -110,11 +111,21 @@ void WebChannelIPCTransportHost::onWebChannelMessage(const std::vector<char> &me Q_ASSERT(!message.empty()); QJsonDocument doc = QJsonDocument::fromRawData(message.data(), message.size(), QJsonDocument::BypassValidation); Q_ASSERT(doc.isObject()); + content::RenderFrameHost *frame = web_contents()->GetMainFrame(); + qCDebug(log).nospace() << "received webchannel message from " << frame << ": " << doc; Q_EMIT messageReceived(doc.object(), this); } -bool WebChannelIPCTransportHost::OnMessageReceived(const IPC::Message &message) +void WebChannelIPCTransportHost::RenderFrameCreated(content::RenderFrameHost *frame) { + setWorldId(frame, m_worldId); +} + +bool WebChannelIPCTransportHost::OnMessageReceived(const IPC::Message& message, content::RenderFrameHost *receiver) +{ + if (receiver != web_contents()->GetMainFrame()) + return false; + bool handled = true; IPC_BEGIN_MESSAGE_MAP(WebChannelIPCTransportHost, message) IPC_MESSAGE_HANDLER(WebChannelIPCTransportHost_SendMessage, onWebChannelMessage) @@ -123,4 +134,4 @@ bool WebChannelIPCTransportHost::OnMessageReceived(const IPC::Message &message) return handled; } -} // namespace +} // namespace QtWebEngineCore diff --git a/src/core/renderer_host/web_channel_ipc_transport_host.h b/src/core/renderer_host/web_channel_ipc_transport_host.h index a1e697a910ad84dd5cc8c5beb78ad616f6ccfaa2..3a814a7941997d3fcf37f4fcd4891aa8debfb98e 100644 --- a/src/core/renderer_host/web_channel_ipc_transport_host.h +++ b/src/core/renderer_host/web_channel_ipc_transport_host.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtWebEngine module of the Qt Toolkit. @@ -40,38 +40,40 @@ #ifndef WEB_CHANNEL_IPC_TRANSPORT_H #define WEB_CHANNEL_IPC_TRANSPORT_H +#include "qtwebenginecoreglobal.h" -#include <QtWebChannel/QWebChannelAbstractTransport> #include "content/public/browser/web_contents_observer.h" -#include "qtwebenginecoreglobal.h" -#include <QtCore/QObject> +#include <QWebChannelAbstractTransport> QT_FORWARD_DECLARE_CLASS(QString) namespace QtWebEngineCore { class WebChannelIPCTransportHost : public QWebChannelAbstractTransport - , public content::WebContentsObserver -{ + , private content::WebContentsObserver { public: - WebChannelIPCTransportHost(content::WebContents *, uint worldId = 0, QObject *parent = 0); + WebChannelIPCTransportHost(content::WebContents *webContents, uint worldId = 0, QObject *parent = nullptr); virtual ~WebChannelIPCTransportHost(); - // WebContentsObserver - void RenderViewHostChanged(content::RenderViewHost* old_host, content::RenderViewHost* new_host) override; - void RenderViewCreated(content::RenderViewHost* render_view_host) override; + void setWorldId(uint worldId) { setWorldId(base::Optional<uint>(worldId)); } + uint worldId() const { return *m_worldId; } // QWebChannelAbstractTransport void sendMessage(const QJsonObject &message) override; - void setWorldId(uint worldId); - uint worldId() const { return m_worldId; } - private: - bool OnMessageReceived(const IPC::Message& message) override; + void setWorldId(base::Optional<uint> worldId); + void setWorldId(content::RenderFrameHost *frame, base::Optional<uint> worldId); void onWebChannelMessage(const std::vector<char> &message); - uint m_worldId; + + // WebContentsObserver + void RenderFrameCreated(content::RenderFrameHost *frame) override; + bool OnMessageReceived(const IPC::Message& message, content::RenderFrameHost *receiver) override; + + // Empty only during construction/destruction. Synchronized to all the + // WebChannelIPCTransports/RenderFrames in the observed WebContents. + base::Optional<uint> m_worldId; }; } // namespace diff --git a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp index d852ca90200f14e3a5e1d9718bdc1356202fbc92..e342632e75fb875fb217b9d3a30cd8a6ce9e2616 100644 --- a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp +++ b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp @@ -20,6 +20,7 @@ #include <QtTest/QtTest> #include <qwebenginepage.h> +#include <qwebengineprofile.h> #include <qwebenginescript.h> #include <qwebenginescriptcollection.h> #include <qwebengineview.h> @@ -39,6 +40,9 @@ private Q_SLOTS: void webChannel(); void noTransportWithoutWebChannel(); void scriptsInNestedIframes(); + void webChannelResettingAndUnsetting(); + void webChannelWithExistingQtObject(); + void navigation(); }; void tst_QWebEngineScript::domEditing() @@ -183,6 +187,27 @@ private: QString m_text; }; +static QString readFile(const QString &path) +{ + QFile file(path); + file.open(QFile::ReadOnly); + QByteArray contents = file.readAll(); + file.close(); + return contents; +} + +static QWebEngineScript webChannelScript() +{ + QString sourceCode = readFile(QStringLiteral(":/qwebchannel.js")); + if (sourceCode.isEmpty()) + return {}; + + QWebEngineScript script; + script.setSourceCode(sourceCode); + script.setInjectionPoint(QWebEngineScript::DocumentCreation); + script.setWorldId(QWebEngineScript::MainWorld); + return script; +} void tst_QWebEngineScript::webChannel_data() { @@ -204,15 +229,8 @@ void tst_QWebEngineScript::webChannel() channel->registerObject(QStringLiteral("object"), &testObject); page.setWebChannel(channel.data(), worldId); - QFile qwebchanneljs(":/qwebchannel.js"); - QVERIFY(qwebchanneljs.exists()); - qwebchanneljs.open(QFile::ReadOnly); - QByteArray scriptSrc = qwebchanneljs.readAll(); - qwebchanneljs.close(); - QWebEngineScript script; - script.setInjectionPoint(QWebEngineScript::DocumentCreation); + QWebEngineScript script = webChannelScript(); script.setWorldId(worldId); - script.setSourceCode(QString::fromLatin1(scriptSrc)); page.scripts().insert(script); page.setHtml(QStringLiteral("<html><body></body></html>")); QSignalSpy spyFinished(&page, &QWebEnginePage::loadFinished); @@ -300,6 +318,93 @@ void tst_QWebEngineScript::scriptsInNestedIframes() QVariant::fromValue(QStringLiteral("Modified Inner text"))); } +void tst_QWebEngineScript::webChannelResettingAndUnsetting() +{ + QWebEnginePage page; + + // There should be no webChannelTransport yet. + QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::MainWorld), + QVariant(QVariant::Invalid)); + QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::ApplicationWorld), + QVariant(QVariant::Invalid)); + + QWebChannel channel; + page.setWebChannel(&channel, QWebEngineScript::MainWorld); + + // There should be one in MainWorld now. + QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::MainWorld), + QVariant(QVariantMap())); + QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::ApplicationWorld), + QVariant(QVariant::Invalid)); + + page.setWebChannel(&channel, QWebEngineScript::ApplicationWorld); + + // Now it should have moved to ApplicationWorld. + QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::MainWorld), + QVariant(QVariant::Invalid)); + QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::ApplicationWorld), + QVariant(QVariantMap())); + + page.setWebChannel(nullptr); + + // And now it should be gone again. + QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::MainWorld), + QVariant(QVariant::Invalid)); + QCOMPARE(evaluateJavaScriptSyncInWorld(&page, "qt.webChannelTransport", QWebEngineScript::ApplicationWorld), + QVariant(QVariant::Invalid)); +} + +void tst_QWebEngineScript::webChannelWithExistingQtObject() +{ + QWebEnginePage page; + + evaluateJavaScriptSync(&page, "qt = 42"); + QCOMPARE(evaluateJavaScriptSync(&page, "qt.webChannelTransport"), QVariant(QVariant::Invalid)); + + QWebChannel channel; + page.setWebChannel(&channel); + + // setWebChannel should have overwritten the qt variable + QCOMPARE(evaluateJavaScriptSync(&page, "qt.webChannelTransport"), QVariant(QVariantMap())); +} + +static QWebEngineScript locationMonitorScript() +{ + QWebEngineScript script = webChannelScript(); + script.setSourceCode(script.sourceCode() + QStringLiteral(R"( + new QWebChannel(qt.webChannelTransport, channel => { + channel.objects.object.text = window.location.href; + }) + )")); + return script; +} + +void tst_QWebEngineScript::navigation() +{ + QWebEnginePage page; + TestObject testObject; + QSignalSpy spyTextChanged(&testObject, &TestObject::textChanged); + QWebChannel channel; + channel.registerObject(QStringLiteral("object"), &testObject); + page.setWebChannel(&channel); + page.scripts().insert(locationMonitorScript()); + + QString url1 = QStringLiteral("about:blank"); + page.setUrl(url1); + QTRY_COMPARE(spyTextChanged.count(), 1); + QCOMPARE(testObject.text(), url1); + + QString url2 = QStringLiteral("chrome://gpu/"); + page.setUrl(url2); + QTRY_COMPARE(spyTextChanged.count(), 2); + QCOMPARE(testObject.text(), url2); + + QString url3 = QStringLiteral("qrc:/resources/test_iframe_main.html"); + page.setUrl(url3); + QTRY_COMPARE(spyTextChanged.count(), 3); + QCOMPARE(testObject.text(), url3); +} + QTEST_MAIN(tst_QWebEngineScript) #include "tst_qwebenginescript.moc"