blob: 5c1bd301faad93a6c01ac8559a00300c70e0e4fc [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 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$
**
****************************************************************************/
#include "user_resource_controller.h"
#include "base/memory/weak_ptr.h"
#include "base/pending_task.h"
#include "base/strings/pattern.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_view.h"
#include "content/public/renderer/render_frame_observer.h"
#include "content/public/renderer/render_view_observer.h"
#include "extensions/common/url_pattern.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_script_source.h"
#include "third_party/blink/public/web/web_view.h"
#include "v8/include/v8.h"
#include "common/qt_messages.h"
#include "common/user_script_data.h"
#include "type_conversion.h"
#include "user_script.h"
#include <QRegularExpression>
#include <bitset>
Q_GLOBAL_STATIC(UserResourceController, qt_webengine_userResourceController)
static content::RenderView *const globalScriptsIndex = nullptr;
// Scripts meant to run after the load event will be run 500ms after DOMContentLoaded if the load event doesn't come within that delay.
static const int afterLoadTimeout = 500;
static int validUserScriptSchemes()
{
return URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS | URLPattern::SCHEME_FILE | URLPattern::SCHEME_QRC;
}
static bool regexMatchesURL(const std::string &pat, const GURL &url)
{
QRegularExpression qre(QtWebEngineCore::toQt(pat));
qre.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
if (!qre.isValid())
return false;
return qre.match(QtWebEngineCore::toQt(url.spec())).hasMatch();
}
static bool includeRuleMatchesURL(const std::string &pat, const GURL &url)
{
// Match patterns for greasemonkey's @include and @exclude rules which can
// be either strings with wildcards or regular expressions.
if (pat.front() == '/' && pat.back() == '/') {
std::string re(++pat.cbegin(), --pat.cend());
if (regexMatchesURL(re, url))
return true;
} else if (base::MatchPattern(url.spec(), pat)) {
return true;
}
return false;
}
static bool scriptMatchesURL(const UserScriptData &scriptData, const GURL &url)
{
// Logic taken from Chromium (extensions/common/user_script.cc)
bool matchFound;
if (!scriptData.urlPatterns.empty()) {
matchFound = false;
for (auto it = scriptData.urlPatterns.begin(), end = scriptData.urlPatterns.end(); it != end; ++it) {
URLPattern urlPattern(validUserScriptSchemes());
if (urlPattern.Parse(*it) == URLPattern::ParseResult::kSuccess && urlPattern.MatchesURL(url))
matchFound = true;
}
if (!matchFound)
return false;
}
if (!scriptData.globs.empty()) {
matchFound = false;
for (auto it = scriptData.globs.begin(), end = scriptData.globs.end(); it != end; ++it) {
if (includeRuleMatchesURL(*it, url))
matchFound = true;
}
if (!matchFound)
return false;
}
if (!scriptData.excludeGlobs.empty()) {
for (auto it = scriptData.excludeGlobs.begin(), end = scriptData.excludeGlobs.end(); it != end; ++it) {
if (includeRuleMatchesURL(*it, url))
return false;
}
}
return true;
}
class UserResourceController::RenderFrameObserverHelper : public content::RenderFrameObserver
{
public:
RenderFrameObserverHelper(content::RenderFrame *render_frame);
private:
// RenderFrameObserver implementation.
void DidCommitProvisionalLoad(bool is_same_document_navigation, ui::PageTransition transition) override;
void DidFinishDocumentLoad() override;
void DidFinishLoad() override;
void FrameDetached() override;
void OnDestruct() override;
bool OnMessageReceived(const IPC::Message &message) override;
void onUserScriptAdded(const UserScriptData &);
void onUserScriptRemoved(const UserScriptData &);
void onScriptsCleared();
class Runner;
QScopedPointer<Runner> m_runner;
};
// Helper class to create WeakPtrs so the AfterLoad tasks can be canceled and to
// avoid running scripts more than once per injection point.
class UserResourceController::RenderFrameObserverHelper::Runner : public base::SupportsWeakPtr<Runner>
{
public:
explicit Runner(blink::WebLocalFrame *frame) : m_frame(frame) {}
void run(UserScriptData::InjectionPoint p)
{
DCHECK_LT(p, m_ran.size());
if (!m_ran[p]) {
UserResourceController::instance()->runScripts(p, m_frame);
m_ran[p] = true;
}
}
private:
blink::WebLocalFrame *m_frame;
std::bitset<3> m_ran;
};
// Used only for script cleanup on RenderView destruction.
class UserResourceController::RenderViewObserverHelper : public content::RenderViewObserver
{
public:
RenderViewObserverHelper(content::RenderView *render_view);
private:
// RenderViewObserver implementation.
void OnDestruct() override;
};
void UserResourceController::runScripts(UserScriptData::InjectionPoint p, blink::WebLocalFrame *frame)
{
content::RenderFrame *renderFrame = content::RenderFrame::FromWebFrame(frame);
if (!renderFrame)
return;
const bool isMainFrame = renderFrame->IsMainFrame();
content::RenderView *renderView = renderFrame->GetRenderView();
if (!renderView)
return;
QList<uint64_t> scriptsToRun = m_viewUserScriptMap.value(0).toList();
scriptsToRun.append(m_viewUserScriptMap.value(renderView).toList());
for (uint64_t id : qAsConst(scriptsToRun)) {
const UserScriptData &script = m_scripts.value(id);
if (script.injectionPoint != p || (!script.injectForSubframes && !isMainFrame))
continue;
if (!scriptMatchesURL(script, frame->GetDocument().Url()))
continue;
blink::WebScriptSource source(blink::WebString::FromUTF8(script.source), script.url);
if (script.worldId)
frame->ExecuteScriptInIsolatedWorld(script.worldId, source);
else
frame->ExecuteScript(source);
}
}
void UserResourceController::RunScriptsAtDocumentEnd(content::RenderFrame *render_frame)
{
runScripts(UserScriptData::DocumentLoadFinished, render_frame->GetWebFrame());
}
UserResourceController::RenderFrameObserverHelper::RenderFrameObserverHelper(content::RenderFrame *render_frame)
: content::RenderFrameObserver(render_frame)
{}
UserResourceController::RenderViewObserverHelper::RenderViewObserverHelper(content::RenderView *render_view)
: content::RenderViewObserver(render_view)
{}
void UserResourceController::RenderFrameObserverHelper::DidCommitProvisionalLoad(bool is_same_document_navigation,
ui::PageTransition /*transitionbool*/)
{
if (is_same_document_navigation)
return;
// We are almost ready to run scripts. We still have to wait until the host
// process has been notified of the DidCommitProvisionalLoad event to ensure
// that the WebChannelTransportHost is ready to receive messages.
m_runner.reset(new Runner(render_frame()->GetWebFrame()));
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&Runner::run, m_runner->AsWeakPtr(), UserScriptData::DocumentElementCreation));
}
void UserResourceController::RenderFrameObserverHelper::DidFinishDocumentLoad()
{
// Don't run scripts if provisional load failed (DidFailProvisionalLoad
// called instead of DidCommitProvisionalLoad).
if (m_runner)
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::BindOnce(&Runner::run, m_runner->AsWeakPtr(), UserScriptData::AfterLoad),
base::TimeDelta::FromMilliseconds(afterLoadTimeout));
}
void UserResourceController::RenderFrameObserverHelper::DidFinishLoad()
{
if (m_runner)
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&Runner::run, m_runner->AsWeakPtr(), UserScriptData::AfterLoad));
}
void UserResourceController::RenderFrameObserverHelper::FrameDetached()
{
m_runner.reset();
}
void UserResourceController::RenderFrameObserverHelper::OnDestruct()
{
delete this;
}
void UserResourceController::RenderViewObserverHelper::OnDestruct()
{
// Remove all scripts associated with the render view.
if (content::RenderView *view = render_view())
UserResourceController::instance()->renderViewDestroyed(view);
delete this;
}
bool UserResourceController::RenderFrameObserverHelper::OnMessageReceived(const IPC::Message &message)
{
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(UserResourceController::RenderFrameObserverHelper, message)
IPC_MESSAGE_HANDLER(RenderFrameObserverHelper_AddScript, onUserScriptAdded)
IPC_MESSAGE_HANDLER(RenderFrameObserverHelper_RemoveScript, onUserScriptRemoved)
IPC_MESSAGE_HANDLER(RenderFrameObserverHelper_ClearScripts, onScriptsCleared)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void UserResourceController::RenderFrameObserverHelper::onUserScriptAdded(const UserScriptData &script)
{
if (content::RenderFrame *frame = render_frame())
if (content::RenderView *view = frame->GetRenderView())
UserResourceController::instance()->addScriptForView(script, view);
}
void UserResourceController::RenderFrameObserverHelper::onUserScriptRemoved(const UserScriptData &script)
{
if (content::RenderFrame *frame = render_frame())
if (content::RenderView *view = frame->GetRenderView())
UserResourceController::instance()->removeScriptForView(script, view);
}
void UserResourceController::RenderFrameObserverHelper::onScriptsCleared()
{
if (content::RenderFrame *frame = render_frame())
if (content::RenderView *view = frame->GetRenderView())
UserResourceController::instance()->clearScriptsForView(view);
}
UserResourceController *UserResourceController::instance()
{
return qt_webengine_userResourceController();
}
bool UserResourceController::OnControlMessageReceived(const IPC::Message &message)
{
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(UserResourceController, message)
IPC_MESSAGE_HANDLER(UserResourceController_AddScript, onAddScript)
IPC_MESSAGE_HANDLER(UserResourceController_RemoveScript, onRemoveScript)
IPC_MESSAGE_HANDLER(UserResourceController_ClearScripts, onClearScripts)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
UserResourceController::UserResourceController()
{
#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS)
static bool onlyCalledOnce = true;
DCHECK(onlyCalledOnce);
onlyCalledOnce = false;
#endif // !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS)
}
void UserResourceController::renderFrameCreated(content::RenderFrame *renderFrame)
{
// Will destroy itself when the RenderFrame is destroyed.
new RenderFrameObserverHelper(renderFrame);
}
void UserResourceController::renderViewCreated(content::RenderView *renderView)
{
// Will destroy itself when the RenderView is destroyed.
new RenderViewObserverHelper(renderView);
}
void UserResourceController::renderViewDestroyed(content::RenderView *renderView)
{
ViewUserScriptMap::iterator it = m_viewUserScriptMap.find(renderView);
if (it == m_viewUserScriptMap.end()) // ASSERT maybe?
return;
for (uint64_t id : qAsConst(it.value())) {
m_scripts.remove(id);
}
m_viewUserScriptMap.remove(renderView);
}
void UserResourceController::addScriptForView(const UserScriptData &script, content::RenderView *view)
{
ViewUserScriptMap::iterator it = m_viewUserScriptMap.find(view);
if (it == m_viewUserScriptMap.end())
it = m_viewUserScriptMap.insert(view, UserScriptSet());
(*it).insert(script.scriptId);
m_scripts.insert(script.scriptId, script);
}
void UserResourceController::removeScriptForView(const UserScriptData &script, content::RenderView *view)
{
ViewUserScriptMap::iterator it = m_viewUserScriptMap.find(view);
if (it == m_viewUserScriptMap.end())
return;
(*it).remove(script.scriptId);
m_scripts.remove(script.scriptId);
}
void UserResourceController::clearScriptsForView(content::RenderView *view)
{
ViewUserScriptMap::iterator it = m_viewUserScriptMap.find(view);
if (it == m_viewUserScriptMap.end())
return;
for (uint64_t id : qAsConst(it.value()))
m_scripts.remove(id);
m_viewUserScriptMap.remove(view);
}
void UserResourceController::onAddScript(const UserScriptData &script)
{
addScriptForView(script, globalScriptsIndex);
}
void UserResourceController::onRemoveScript(const UserScriptData &script)
{
removeScriptForView(script, globalScriptsIndex);
}
void UserResourceController::onClearScripts()
{
clearScriptsForView(globalScriptsIndex);
}