diff options
author | Szabolcs David <davidsz@inf.u-szeged.hu> | 2021-08-19 15:22:21 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2021-10-04 10:20:46 +0200 |
commit | 0ff9e0014bc32c3d13852fc4d92367bac22cb4c1 (patch) | |
tree | 28d4d3da2d65a2dbf7e19a037bcc2cd1dbcbc90a | |
parent | 7bfd8534f5f557517873734779502c315fe5270e (diff) | |
download | qtwebengine-chromium-0ff9e0014bc32c3d13852fc4d92367bac22cb4c1.tar.gz |
Fix navigation when clicking on links in a PDF
Implement a very limited version of chrome.tabs.update() JavaScript API
with only one functionality: navigating the current WebContents when an
URL update was requested.
Task-number: QTBUG-95282
Change-Id: I9628262ed73aefb2ef53934e724444dd3378a9ad
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
7 files changed, 431 insertions, 1 deletions
diff --git a/chromium/chrome/browser/resources/pdf/browser_api.js b/chromium/chrome/browser/resources/pdf/browser_api.js index f03bdb567a5..789c9c29586 100644 --- a/chromium/chrome/browser/resources/pdf/browser_api.js +++ b/chromium/chrome/browser/resources/pdf/browser_api.js @@ -90,7 +90,7 @@ export class BrowserApi { * @param {string} url The URL to navigate the tab to. */ navigateInCurrentTab(url) { - const tabId = this.getStreamInfo().tabId; + const tabId = 0; // We need to use the tabs API to navigate because // |window.location.href| cannot be used. This PDF extension is not loaded // in the top level frame (it's embedded using MimeHandlerView). Using diff --git a/chromium/extensions/common/api/_webengine_api_features.json b/chromium/extensions/common/api/_webengine_api_features.json index b72dd74b4cf..e9b9bf83c2b 100644 --- a/chromium/extensions/common/api/_webengine_api_features.json +++ b/chromium/extensions/common/api/_webengine_api_features.json @@ -28,6 +28,12 @@ "dependencies": ["permission:enterprise.hardwarePlatform"], "contexts": ["blessed_extension"] }, + "tabs": { + "channel": "stable", + "extension_types": ["extension", "legacy_packaged_app"], + "contexts": ["blessed_extension"], + "default_parent": true + }, "webrtcDesktopCapturePrivate": { "dependencies": ["permission:webrtcDesktopCapturePrivate"], "contexts": ["blessed_extension"] diff --git a/chromium/qtwebengine/browser/extensions/api/BUILD.gn b/chromium/qtwebengine/browser/extensions/api/BUILD.gn index d0fbd45d1e0..177159b64e4 100644 --- a/chromium/qtwebengine/browser/extensions/api/BUILD.gn +++ b/chromium/qtwebengine/browser/extensions/api/BUILD.gn @@ -17,6 +17,17 @@ source_set("resources_private") { ] } +source_set("tabs") { + sources = [ + "tabs/tabs_api.cc", + "tabs/tabs_api.h", + ] + + deps = [ + "//content/public/browser", + ] +} + source_set("webrtc_desktop_capture_private") { sources = [ "webrtc_desktop_capture_private/webrtc_desktop_capture_private_api.cc", @@ -37,6 +48,7 @@ function_registration("api_registration") { deps = [ ":resources_private", + ":tabs", ":webrtc_desktop_capture_private", "//extensions/common/api", diff --git a/chromium/qtwebengine/browser/extensions/api/tabs/tabs_api.cc b/chromium/qtwebengine/browser/extensions/api/tabs/tabs_api.cc new file mode 100644 index 00000000000..c2b27a3e022 --- /dev/null +++ b/chromium/qtwebengine/browser/extensions/api/tabs/tabs_api.cc @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 //chrome/browser/extensions/api/tabs/tabs_api.cc +// Copyright 2015 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 "qtwebengine/browser/extensions/api/tabs/tabs_api.h" +#include "qtwebengine/common/extensions/api/tabs.h" + +#include "base/metrics/histogram_macros.h" +#include "chrome/common/url_constants.h" +#include "components/guest_view/browser/guest_view_base.h" +#include "components/url_formatter/url_fixer.h" +#include "content/public/browser/navigation_controller.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/url_constants.h" +#include "extensions/common/error_utils.h" +#include "extensions/common/permissions/permissions_data.h" + +using content::NavigationController; +using content::OpenURLParams; +using content::WebContents; + +namespace extensions { + +namespace tabs = api::tabs; + +static GURL ResolvePossiblyRelativeURL(const std::string& url_string, + const Extension* extension) { + GURL url = GURL(url_string); + if (!url.is_valid() && extension) + url = extension->GetResourceURL(url_string); + + return url; +} + +static bool IsKillURL(const GURL& url) { +#if DCHECK_IS_ON() + // Caller should ensure that |url| is already "fixed up" by + // url_formatter::FixupURL, which (among many other things) takes care + // of rewriting about:kill into chrome://kill/. + if (url.SchemeIs(url::kAboutScheme)) + DCHECK(url.IsAboutBlank() || url.IsAboutSrcdoc()); +#endif + + static const char* const kill_hosts[] = { + chrome::kChromeUICrashHost, chrome::kChromeUIDelayedHangUIHost, + chrome::kChromeUIHangUIHost, chrome::kChromeUIKillHost, + chrome::kChromeUIQuitHost, chrome::kChromeUIRestartHost, + content::kChromeUIBrowserCrashHost, content::kChromeUIMemoryExhaustHost, + }; + + if (!url.SchemeIs(content::kChromeUIScheme)) + return false; + + return base::Contains(kill_hosts, url.host_piece()); +} + +static bool PrepareURLForNavigation(const std::string& url_string, + const Extension* extension, + GURL* return_url, + std::string* error) { + GURL url = ResolvePossiblyRelativeURL(url_string, extension); + + // Ideally, the URL would only be "fixed" for user input (e.g. for URLs + // entered into the Omnibox), but some extensions rely on the legacy behavior + // where all navigations were subject to the "fixing". See also + // https://crbug.com/1145381. + url = url_formatter::FixupURL(url.spec(), "" /* = desired_tld */); + + // Reject invalid URLs. + if (!url.is_valid()) { + *error = ErrorUtils::FormatErrorMessage("Invalid url: \"*\".", url_string); + return false; + } + + // Don't let the extension crash the browser or renderers. + if (IsKillURL(url)) { + *error = "I'm sorry. I'm afraid I can't do that."; + return false; + } + + if (url.SchemeIs(content::kChromeDevToolsScheme)) { + *error = "Cannot navigate to a devtools:// page without either the devtools or " + "debugger permission."; + return false; + } + + return_url->Swap(&url); + return true; +} + +TabsUpdateFunction::TabsUpdateFunction() : web_contents_(nullptr) {} + +ExtensionFunction::ResponseAction TabsUpdateFunction::Run() { + std::unique_ptr<tabs::Update::Params> params( + tabs::Update::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + int tab_id = -1; + std::string error; + + web_contents_ = GetSenderWebContents(); + if (!web_contents_) { + return RespondNow(Error("The specified target is not found.")); + } else { + web_contents_ = guest_view::GuestViewBase::GetTopLevelWebContents(web_contents_); + } + + // Navigate the tab to a new location if the url is different. + if (params->update_properties.url.get()) { + std::string updated_url = *params->update_properties.url; + if (!UpdateURL(updated_url, tab_id, &error)) + return RespondNow(Error(std::move(error))); + } + + return RespondNow(GetResult()); +} + +bool TabsUpdateFunction::UpdateURL(const std::string& url_string, + int tab_id, + std::string* error) { + GURL url; + if (!PrepareURLForNavigation(url_string, extension(), &url, + error)) { + return false; + } + + const bool is_javascript_scheme = url.SchemeIs(url::kJavaScriptScheme); + UMA_HISTOGRAM_BOOLEAN("Extensions.ApiTabUpdateJavascript", + is_javascript_scheme); + // JavaScript URLs are forbidden in chrome.tabs.update(). + if (is_javascript_scheme) { + *error = "JavaScript URLs are not allowed in chrome.tabs.update. Use " + "chrome.tabs.executeScript instead."; + return false; + } + + NavigationController::LoadURLParams load_params(url); + + // Treat extension-initiated navigations as renderer-initiated so that the URL + // does not show in the omnibox until it commits. This avoids URL spoofs + // since URLs can be opened on behalf of untrusted content. + load_params.is_renderer_initiated = true; + // All renderer-initiated navigations need to have an initiator origin. + load_params.initiator_origin = extension()->origin(); + // |source_site_instance| needs to be set so that a renderer process + // compatible with |initiator_origin| is picked by Site Isolation. + load_params.source_site_instance = content::SiteInstance::CreateForURL( + web_contents_->GetBrowserContext(), + load_params.initiator_origin->GetURL()); + + // Marking the navigation as initiated via an API means that the focus + // will stay in the omnibox - see https://crbug.com/1085779. + load_params.transition_type = ui::PAGE_TRANSITION_FROM_API; + + web_contents_->GetController().LoadURLWithParams(load_params); + + DCHECK_EQ(url, + web_contents_->GetController().GetPendingEntry()->GetVirtualURL()); + + return true; +} + +ExtensionFunction::ResponseValue TabsUpdateFunction::GetResult() { + return NoArguments(); +} + +void TabsUpdateFunction::OnExecuteCodeFinished( + const std::string& error, + const GURL& url, + const base::ListValue& script_result) { + if (!error.empty()) { + Respond(Error(error)); + return; + } + + return Respond(GetResult()); +} + +} // namespace extensions diff --git a/chromium/qtwebengine/browser/extensions/api/tabs/tabs_api.h b/chromium/qtwebengine/browser/extensions/api/tabs/tabs_api.h new file mode 100644 index 00000000000..3c18c4ccb3a --- /dev/null +++ b/chromium/qtwebengine/browser/extensions/api/tabs/tabs_api.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 //chrome/browser/extensions/api/tabs/tabs_api.h +// Copyright 2015 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. + +#ifndef QTWEBENGINE_BROWSER_EXTENSIONS_API_TABS_API_H_ +#define QTWEBENGINE_BROWSER_EXTENSIONS_API_TABS_API_H_ + +#include "base/macros.h" +#include "extensions/browser/extension_function.h" + +namespace extensions { + +class TabsUpdateFunction : public ExtensionFunction { + public: + TabsUpdateFunction(); + + protected: + ~TabsUpdateFunction() override {} + bool UpdateURL(const std::string& url, + int tab_id, + std::string* error); + ResponseValue GetResult(); + + content::WebContents* web_contents_; + + private: + ResponseAction Run() override; + void OnExecuteCodeFinished(const std::string& error, + const GURL& on_url, + const base::ListValue& script_result); + + DECLARE_EXTENSION_FUNCTION("tabs.update", TABS_UPDATE) +}; + +} // namespace extensions + +#endif // QTWEBENGINE_BROWSER_EXTENSIONS_API_TABS_API_H_ diff --git a/chromium/qtwebengine/common/extensions/api/schema.gni b/chromium/qtwebengine/common/extensions/api/schema.gni index 7c3ee705d3a..2db0e4c7dbd 100644 --- a/chromium/qtwebengine/common/extensions/api/schema.gni +++ b/chromium/qtwebengine/common/extensions/api/schema.gni @@ -1,5 +1,6 @@ webengine_extensions_api_schema_files_ = [ "resources_private.idl", + "tabs.json", "webrtc_desktop_capture_private.idl", ] diff --git a/chromium/qtwebengine/common/extensions/api/tabs.json b/chromium/qtwebengine/common/extensions/api/tabs.json new file mode 100644 index 00000000000..fa0bfb4891a --- /dev/null +++ b/chromium/qtwebengine/common/extensions/api/tabs.json @@ -0,0 +1,112 @@ +// Copyright (c) 2012 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. + +[ + { + "namespace": "tabs", + "description": "Use the <code>chrome.tabs</code> API to interact with the browser's tab system. You can use this API to create, modify, and rearrange tabs in the browser.", + "types": [ + { + "id": "Tab", + "type": "object", + "properties": { + "id": {"type": "integer", "minimum": 0, "description": "The ID of the tab. Tab IDs are unique within a browser session."}, + "index": {"type": "integer", "minimum": 0, "description": "The zero-based index of the tab within its window."}, + "windowId": {"type": "integer", "minimum": 0, "description": "The ID of the window the tab is contained within."}, + "selected": {"type": "boolean", "description": "Whether the tab is selected.", "nodoc": true}, + "highlighted": {"type": "boolean", "description": "Whether the tab is highlighted."}, + "active": {"type": "boolean", "description": "Whether the tab is active in its window."}, + "pinned": {"type": "boolean", "description": "Whether the tab is pinned."}, + "url": {"type": "string", "description": "The URL the tab is displaying."}, + "title": {"type": "string", "optional": true, "description": "The title of the tab. This may not be available if the tab is loading."}, + "favIconUrl": {"type": "string", "optional": true, "description": "The URL of the tab's favicon. This may not be available if the tab is loading."}, + "status": {"type": "string", "optional": true, "description": "Either <em>loading</em> or <em>complete</em>."}, + "incognito": {"type": "boolean", "description": "Whether the tab is in an incognito window."} + } + } + ], + "properties": { + "TAB_ID_NONE": { + "value": -1, + "description": "An ID that represents the absence of a browser tab." + } + }, + "functions": [ + { + "name": "update", + "type": "function", + "description": "Modifies the properties of a tab. Properties that are not specified in <var>updateProperties</var> are not modified.", + "parameters": [ + { + "type": "integer", + "name": "tabId", + "minimum": 0, + "optional": true, + "description": "Defaults to the selected tab of the <a href='windows#current-window'>current window</a>." + }, + { + "type": "object", + "name": "updateProperties", + "properties": { + "url": { + "type": "string", + "optional": true, + "description": "A URL to navigate the tab to. JavaScript URLs are not supported; use $(ref:tabs.executeScript) instead." + }, + "active": { + "type": "boolean", + "optional": true, + "description": "Whether the tab should be active. Does not affect whether the window is focused (see $(ref:windows.update))." + }, + "highlighted": { + "type": "boolean", + "optional": true, + "description": "Adds or removes the tab from the current selection." + }, + "selected": { + "deprecated": "Please use <em>highlighted</em>.", + "type": "boolean", + "optional": true, + "description": "Whether the tab should be selected." + }, + "pinned": { + "type": "boolean", + "optional": true, + "description": "Whether the tab should be pinned." + }, + "muted": { + "type": "boolean", + "optional": true, + "description": "Whether the tab should be muted." + }, + "openerTabId": { + "type": "integer", + "minimum": 0, + "optional": true, + "description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as this tab." + }, + "autoDiscardable": { + "type": "boolean", + "optional": true, + "description": "Whether the tab should be discarded automatically by the browser when resources are low." + } + } + } + ], + "returns_async": { + "name": "callback", + "optional": true, + "parameters": [ + { + "name": "tab", + "$ref": "Tab", + "optional": true, + "description": "Details about the updated tab. The $(ref:tabs.Tab) object does not contain <code>url</code>, <code>pendingUrl</code>, <code>title</code>, and <code>favIconUrl</code> if the <code>\"tabs\"</code> permission has not been requested." + } + ] + } + } + ] + } +] |