Commit c670a321 authored by John Abd-El-Malek's avatar John Abd-El-Malek Committed by Chromium LUCI CQ

Add an API to trigger a download from a context menu.

The ContextMenuParams object becomes a token that is a parameter to Tab.download, as we need some internal data from content::ContextMenuParams to initiate the download with the right headers.

Bug: 1153694
Change-Id: I4660f0cb313910c91dbde09bdeac93ea5921eb70
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2573388Reviewed-by: default avatarBo <boliu@chromium.org>
Commit-Queue: John Abd-El-Malek <jam@chromium.org>
Cr-Commit-Position: refs/heads/master@{#835387}
parent 8e4b7d75
......@@ -103,9 +103,8 @@ void ContextMenuNativeDelegateImpl::StartDownload(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
jboolean jis_link) {
std::string headers;
DownloadControllerBase::Get()->StartContextMenuDownload(
*context_menu_params_, web_contents_, jis_link, headers);
*context_menu_params_, web_contents_, jis_link);
}
void ContextMenuNativeDelegateImpl::SearchForImage(
......
......@@ -34,20 +34,17 @@
#include "chrome/browser/permissions/permission_update_infobar_delegate_android.h"
#include "chrome/browser/vr/vr_tab_helper.h"
#include "chrome/grit/chromium_strings.h"
#include "components/download/content/public/context_menu_download.h"
#include "components/download/public/common/auto_resumption_handler.h"
#include "components/download/public/common/download_features.h"
#include "components/download/public/common/download_url_parameters.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_item_utils.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/download_request_utils.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/common/referrer.h"
#include "net/base/filename_util.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "ui/android/view_android.h"
#include "ui/android/window_android.h"
#include "ui/base/page_transition_types.h"
......@@ -66,11 +63,11 @@ namespace {
// Guards download_controller_
base::LazyInstance<base::Lock>::DestructorAtExit g_download_controller_lock_;
void CreateContextMenuDownload(const content::WebContents::Getter& wc_getter,
const content::ContextMenuParams& params,
bool is_link,
const std::string& extra_headers,
bool granted) {
void CreateContextMenuDownloadInternal(
const content::WebContents::Getter& wc_getter,
const content::ContextMenuParams& params,
bool is_link,
bool granted) {
content::WebContents* web_contents = wc_getter.Run();
if (!granted)
return;
......@@ -82,38 +79,10 @@ void CreateContextMenuDownload(const content::WebContents::Getter& wc_getter,
return;
}
const GURL& url = is_link ? params.link_url : params.src_url;
const GURL& referring_url =
params.frame_url.is_empty() ? params.page_url : params.frame_url;
content::DownloadManager* dlm = content::BrowserContext::GetDownloadManager(
web_contents->GetBrowserContext());
std::unique_ptr<download::DownloadUrlParameters> dl_params(
content::DownloadRequestUtils::CreateDownloadForWebContentsMainFrame(
web_contents, url,
TRAFFIC_ANNOTATION_WITHOUT_PROTO("Download via context menu")));
content::Referrer referrer = content::Referrer::SanitizeForRequest(
url,
content::Referrer(referring_url.GetAsReferrer(), params.referrer_policy));
dl_params->set_referrer(referrer.url);
dl_params->set_referrer_policy(
content::Referrer::ReferrerPolicyForUrlRequest(referrer.policy));
if (is_link)
dl_params->set_referrer_encoding(params.frame_charset);
net::HttpRequestHeaders headers;
headers.AddHeadersFromString(extra_headers);
for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext();)
dl_params->add_request_header(it.name(), it.value());
if (!is_link && extra_headers.empty())
dl_params->set_prefer_cache(true);
dl_params->set_prompt(false);
dl_params->set_request_origin(
offline_pages::android::OfflinePageBridge::GetEncodedOriginApp(
web_contents));
dl_params->set_suggested_name(params.suggested_filename);
RecordDownloadSource(DOWNLOAD_INITIATED_BY_CONTEXT_MENU);
dl_params->set_download_source(download::DownloadSource::CONTEXT_MENU);
dlm->DownloadUrl(std::move(dl_params));
auto origin = offline_pages::android::OfflinePageBridge::GetEncodedOriginApp(
web_contents);
download::CreateContextMenuDownload(web_contents, params, origin, is_link);
}
// Helper class for retrieving a DownloadManager.
......@@ -454,8 +423,7 @@ void DownloadController::OnDangerousDownload(DownloadItem* item) {
void DownloadController::StartContextMenuDownload(
const ContextMenuParams& params,
WebContents* web_contents,
bool is_link,
const std::string& extra_headers) {
bool is_link) {
int process_id = web_contents->GetRenderViewHost()->GetProcess()->GetID();
int routing_id = web_contents->GetRenderViewHost()->GetRoutingID();
......@@ -463,8 +431,8 @@ void DownloadController::StartContextMenuDownload(
base::Bind(&GetWebContents, process_id, routing_id));
AcquireFileAccessPermission(
wc_getter, base::BindOnce(&CreateContextMenuDownload, wc_getter, params,
is_link, extra_headers));
wc_getter, base::BindOnce(&CreateContextMenuDownloadInternal, wc_getter,
params, is_link));
}
bool DownloadController::IsInterruptedDownloadAutoResumable(
......
......@@ -73,8 +73,7 @@ class DownloadController : public DownloadControllerBase {
void OnDownloadStarted(download::DownloadItem* download_item) override;
void StartContextMenuDownload(const content::ContextMenuParams& params,
content::WebContents* web_contents,
bool is_link,
const std::string& extra_headers) override;
bool is_link) override;
// DownloadItem::Observer interface.
void OnDownloadUpdated(download::DownloadItem* item) override;
......
......@@ -68,8 +68,7 @@ class DownloadControllerBase : public download::DownloadItem::Observer,
virtual void StartContextMenuDownload(
const content::ContextMenuParams& params,
content::WebContents* web_contents,
bool is_link,
const std::string& extra_headers) = 0;
bool is_link) = 0;
// Callback when user permission prompt finishes. Args: whether file access
// permission is acquired.
......
......@@ -24,8 +24,7 @@ void MockDownloadController::OnDownloadStarted(
void MockDownloadController::StartContextMenuDownload(
const content::ContextMenuParams& params,
content::WebContents* web_contents,
bool is_link,
const std::string& extra_headers) {}
bool is_link) {}
void MockDownloadController::AcquireFileAccessPermission(
const content::WebContents::Getter& wc_getter,
......
......@@ -5,8 +5,6 @@
#ifndef CHROME_BROWSER_DOWNLOAD_ANDROID_MOCK_DOWNLOAD_CONTROLLER_H_
#define CHROME_BROWSER_DOWNLOAD_ANDROID_MOCK_DOWNLOAD_CONTROLLER_H_
#include <string>
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/singleton.h"
......@@ -25,8 +23,7 @@ class MockDownloadController : public DownloadControllerBase {
void OnDownloadStarted(download::DownloadItem* download_item) override;
void StartContextMenuDownload(const content::ContextMenuParams& params,
content::WebContents* web_contents,
bool is_link,
const std::string& extra_headers) override;
bool is_link) override;
void AcquireFileAccessPermission(
const content::WebContents::Getter& wc_getter,
AcquireFileAccessPermissionCallback callback) override;
......
......@@ -2,6 +2,7 @@ include_rules = [
"+components/keyed_service",
"+components/leveldb_proto",
"+content/public/browser",
"+content/public/common",
"+content/public/test",
"+base",
"+net",
......
......@@ -13,6 +13,10 @@ source_set("internal") {
"download_driver_impl.h",
]
if (is_android) {
sources += [ "context_menu_download.cc" ]
}
public_deps = [
"//components/download/content/public",
"//components/download/internal/background_service:internal",
......
// Copyright 2020 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 "components/download/content/public/context_menu_download.h"
#include "components/download/public/common/download_url_parameters.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/context_menu_params.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/download_request_utils.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/referrer.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
namespace download {
void CreateContextMenuDownload(content::WebContents* web_contents,
const content::ContextMenuParams& params,
const std::string& origin,
bool is_link) {
const GURL& url = is_link ? params.link_url : params.src_url;
const GURL& referring_url =
params.frame_url.is_empty() ? params.page_url : params.frame_url;
content::DownloadManager* dlm = content::BrowserContext::GetDownloadManager(
web_contents->GetBrowserContext());
std::unique_ptr<download::DownloadUrlParameters> dl_params(
content::DownloadRequestUtils::CreateDownloadForWebContentsMainFrame(
web_contents, url,
TRAFFIC_ANNOTATION_WITHOUT_PROTO("Download via context menu")));
content::Referrer referrer = content::Referrer::SanitizeForRequest(
url,
content::Referrer(referring_url.GetAsReferrer(), params.referrer_policy));
dl_params->set_referrer(referrer.url);
dl_params->set_referrer_policy(
content::Referrer::ReferrerPolicyForUrlRequest(referrer.policy));
if (is_link)
dl_params->set_referrer_encoding(params.frame_charset);
if (!is_link)
dl_params->set_prefer_cache(true);
dl_params->set_prompt(false);
dl_params->set_request_origin(origin);
dl_params->set_suggested_name(params.suggested_filename);
dl_params->set_download_source(download::DownloadSource::CONTEXT_MENU);
dlm->DownloadUrl(std::move(dl_params));
}
} // namespace download
......@@ -15,6 +15,10 @@ source_set("public") {
"download_navigation_observer.h",
]
if (is_android) {
sources += [ "context_menu_download.h" ]
}
public_deps = [ "//base" ]
deps = [
......
// Copyright 2020 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 COMPONENTS_DOWNLOAD_CONTENT_PUBLIC_CONTEXT_MENU_DOWNLOAD_H_
#define COMPONENTS_DOWNLOAD_CONTENT_PUBLIC_CONTEXT_MENU_DOWNLOAD_H_
#include <string>
namespace content {
class WebContents;
struct ContextMenuParams;
} // namespace content
namespace download {
// Starts a download for the given ContextMenuParams.
void CreateContextMenuDownload(content::WebContents* web_contents,
const content::ContextMenuParams& params,
const std::string& origin,
bool is_link);
} // namespace download
#endif // COMPONENTS_DOWNLOAD_CONTENT_PUBLIC_CONTEXT_MENU_DOWNLOAD_H_
......@@ -381,6 +381,8 @@ source_set("weblayer_lib_base") {
"//components/crash/content/browser",
"//components/crash/core/app",
"//components/crash/core/common",
"//components/download/content/factory",
"//components/download/content/public",
"//components/embedder_support",
"//components/embedder_support/origin_trials",
"//components/error_page/common",
......
......@@ -18,6 +18,7 @@ include_rules = [
"+components/content_settings/core/common",
"+components/crash/content/browser",
"+components/crash/core/common",
"+components/download/content/public",
"+components/download/public/common",
"+components/embedder_support",
"+components/error_page/content/browser",
......
......@@ -21,11 +21,13 @@ import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.content_public.browser.test.util.TestTouchUtils;
import org.chromium.weblayer.ContextMenuParams;
import org.chromium.weblayer.Profile;
import org.chromium.weblayer.ScrollNotificationType;
import org.chromium.weblayer.Tab;
import org.chromium.weblayer.TabCallback;
import org.chromium.weblayer.shell.InstrumentationActivity;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
......@@ -84,11 +86,8 @@ public class TabCallbackTest {
callback.visibleUriChangedCallback.waitUntilValueObserved(url);
}
// Requires implementation M82.
@Test
@SmallTest
public void testShowContextMenu() throws TimeoutException {
String pageUrl = mActivityTestRule.getTestDataURL("download.html");
private ContextMenuParams runContextMenuTest(String file) throws TimeoutException {
String pageUrl = mActivityTestRule.getTestDataURL(file);
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(pageUrl);
ContextMenuParams params[] = new ContextMenuParams[1];
......@@ -109,39 +108,81 @@ public class TabCallbackTest {
InstrumentationRegistry.getInstrumentation(), activity.getWindow().getDecorView());
callbackHelper.waitForFirst();
Assert.assertEquals(Uri.parse(pageUrl), params[0].pageUri);
return params[0];
}
// Requires implementation M82.
@Test
@SmallTest
public void testShowContextMenu() throws TimeoutException {
ContextMenuParams params = runContextMenuTest("download.html");
Assert.assertEquals(
Uri.parse(mActivityTestRule.getTestDataURL("lorem_ipsum.txt")), params[0].linkUri);
Assert.assertEquals("anchor text", params[0].linkText);
Uri.parse(mActivityTestRule.getTestDataURL("lorem_ipsum.txt")), params.linkUri);
Assert.assertEquals("anchor text", params.linkText);
}
// Requires implementation M82.
@Test
@SmallTest
public void testShowContextMenuImg() throws TimeoutException {
String pageUrl = mActivityTestRule.getTestDataURL("img.html");
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(pageUrl);
ContextMenuParams params = runContextMenuTest("img.html");
Assert.assertEquals(
Uri.parse(mActivityTestRule.getTestDataURL("favicon.png")), params.srcUri);
Assert.assertEquals("alt_text", params.titleOrAltText);
}
private File setTempDownloadDir() {
// Don't fill up the default download directory on the device.
File tempDownloadDirectory = new File(
InstrumentationRegistry.getInstrumentation().getTargetContext().getCacheDir()
+ "/weblayer/Downloads");
ContextMenuParams params[] = new ContextMenuParams[1];
CallbackHelper callbackHelper = new CallbackHelper();
TestThreadUtils.runOnUiThreadBlocking(() -> {
Tab tab = activity.getTab();
TabCallback callback = new TabCallback() {
@Override
public void showContextMenu(ContextMenuParams param) {
params[0] = param;
callbackHelper.notifyCalled();
}
};
tab.registerTabCallback(callback);
Profile profile = mActivityTestRule.getActivity().getBrowser().getProfile();
profile.setDownloadDirectory(tempDownloadDirectory);
});
return tempDownloadDirectory;
}
TestTouchUtils.longClickView(
InstrumentationRegistry.getInstrumentation(), activity.getWindow().getDecorView());
callbackHelper.waitForFirst();
Assert.assertEquals(Uri.parse(pageUrl), params[0].pageUri);
Assert.assertEquals(
Uri.parse(mActivityTestRule.getTestDataURL("notfound.png")), params[0].srcUri);
Assert.assertEquals("alt_text", params[0].titleOrAltText);
private void waitForFileExist(File filePath, String fileName) {
File file = new File(filePath, fileName);
CriteriaHelper.pollInstrumentationThread(() -> {
Criteria.checkThat("Invalid file existence state for: " + fileName, file.exists(),
Matchers.is(true));
});
file.delete();
}
@MinWebLayerVersion(89)
@Test
@SmallTest
public void testDownloadFromContextMenu() throws TimeoutException {
ContextMenuParams params = runContextMenuTest("download.html");
;
Assert.assertFalse(params.isImage);
Assert.assertFalse(params.isVideo);
Assert.assertTrue(params.canDownload);
File tempDownloadDirectory = setTempDownloadDir();
TestThreadUtils.runOnUiThreadBlocking(
() -> { mActivityTestRule.getActivity().getTab().download(params); });
waitForFileExist(tempDownloadDirectory, "lorem_ipsum.txt");
}
@MinWebLayerVersion(89)
@Test
@SmallTest
public void testDownloadFromContextMenuImg() throws TimeoutException {
ContextMenuParams params = runContextMenuTest("img.html");
;
Assert.assertTrue(params.isImage);
Assert.assertFalse(params.isVideo);
Assert.assertTrue(params.canDownload);
File tempDownloadDirectory = setTempDownloadDir();
TestThreadUtils.runOnUiThreadBlocking(
() -> { mActivityTestRule.getActivity().getTab().download(params); });
waitForFileExist(tempDownloadDirectory, "favicon.png");
}
@Test
......
......@@ -442,6 +442,7 @@ android_aidl("aidl") {
"org/chromium/weblayer_private/interfaces/IChildProcessService.aidl",
"org/chromium/weblayer_private/interfaces/IClientDownload.aidl",
"org/chromium/weblayer_private/interfaces/IClientNavigation.aidl",
"org/chromium/weblayer_private/interfaces/IContextMenuParams.aidl",
"org/chromium/weblayer_private/interfaces/ICookieChangedCallbackClient.aidl",
"org/chromium/weblayer_private/interfaces/ICookieManager.aidl",
"org/chromium/weblayer_private/interfaces/ICrashReporterController.aidl",
......
......@@ -4,7 +4,9 @@
package org.chromium.weblayer_private;
import android.Manifest.permission;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.RectF;
import android.os.Build;
......@@ -36,6 +38,7 @@ import org.chromium.components.browser_ui.util.BrowserControlsVisibilityDelegate
import org.chromium.components.browser_ui.util.ComposedBrowserControlsVisibilityDelegate;
import org.chromium.components.browser_ui.widget.InsetObserverView;
import org.chromium.components.embedder_support.contextmenu.ContextMenuParams;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.external_intents.InterceptNavigationDelegateImpl;
import org.chromium.components.find_in_page.FindInPageBridge;
import org.chromium.components.find_in_page.FindMatchRectsDetails;
......@@ -56,6 +59,7 @@ import org.chromium.ui.base.ViewAndroidDelegate;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.url.GURL;
import org.chromium.weblayer_private.interfaces.APICallException;
import org.chromium.weblayer_private.interfaces.IContextMenuParams;
import org.chromium.weblayer_private.interfaces.IDownloadCallbackClient;
import org.chromium.weblayer_private.interfaces.IErrorPageCallbackClient;
import org.chromium.weblayer_private.interfaces.IFaviconFetcher;
......@@ -656,6 +660,31 @@ public final class TabImpl extends ITab.Stub {
return TabImplJni.get().isDesktopUserAgentEnabled(mNativeTab);
}
@Override
public void download(IContextMenuParams contextMenuParams) {
StrictModeWorkaround.apply();
NativeContextMenuParamsHolder nativeContextMenuParamsHolder =
(NativeContextMenuParamsHolder) contextMenuParams;
WindowAndroid window = getBrowser().getWindowAndroid();
if (window.hasPermission(permission.WRITE_EXTERNAL_STORAGE)) {
continueDownload(nativeContextMenuParamsHolder);
return;
}
String[] requestPermissions = new String[] {permission.WRITE_EXTERNAL_STORAGE};
window.requestPermissions(requestPermissions, (permissions, grantResults) -> {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
continueDownload(nativeContextMenuParamsHolder);
}
});
}
private void continueDownload(NativeContextMenuParamsHolder nativeContextMenuParamsHolder) {
TabImplJni.get().download(
mNativeTab, nativeContextMenuParamsHolder.mNativeContextMenuParams);
}
public void removeFaviconCallbackProxy(FaviconCallbackProxy proxy) {
mFaviconCallbackProxies.remove(proxy);
}
......@@ -1048,14 +1077,56 @@ public final class TabImpl extends ITab.Stub {
return TextUtils.isEmpty(s) ? null : s;
}
private static class NativeContextMenuParamsHolder extends IContextMenuParams.Stub {
// Note: avoid adding more members since an object with a finalizer will delay GC of any
// object it references.
private final long mNativeContextMenuParams;
NativeContextMenuParamsHolder(long nativeContextMenuParams) {
mNativeContextMenuParams = nativeContextMenuParams;
}
/**
* A finalizer is required to ensure that the native object associated with
* this object gets destructed, otherwise there would be a memory leak.
*
* This is safe because it makes a simple call into C++ code that is both
* thread-safe and very fast.
*
* @see java.lang.Object#finalize()
*/
@Override
protected final void finalize() throws Throwable {
super.finalize();
TabImplJni.get().destroyContextMenuParams(mNativeContextMenuParams);
}
}
@CalledByNative
private void showContextMenu(ContextMenuParams params) throws RemoteException {
private void showContextMenu(ContextMenuParams params, long nativeContextMenuParams)
throws RemoteException {
if (WebLayerFactoryImpl.getClientMajorVersion() < 82) return;
mClient.showContextMenu(ObjectWrapper.wrap(params.getPageUrl()),
if (WebLayerFactoryImpl.getClientMajorVersion() < 89) {
mClient.showContextMenu(ObjectWrapper.wrap(params.getPageUrl()),
ObjectWrapper.wrap(nonEmptyOrNull(params.getLinkUrl())),
ObjectWrapper.wrap(nonEmptyOrNull(params.getLinkText())),
ObjectWrapper.wrap(nonEmptyOrNull(params.getTitleText())),
ObjectWrapper.wrap(nonEmptyOrNull(params.getSrcUrl())));
return;
}
boolean canDownload =
(params.isImage() && UrlUtilities.isDownloadableScheme(params.getSrcUrl()))
|| (params.isVideo() && UrlUtilities.isDownloadableScheme(params.getSrcUrl())
&& params.canSaveMedia())
|| (params.isAnchor() && UrlUtilities.isDownloadableScheme(params.getLinkUrl()));
mClient.showContextMenu2(ObjectWrapper.wrap(params.getPageUrl()),
ObjectWrapper.wrap(nonEmptyOrNull(params.getLinkUrl())),
ObjectWrapper.wrap(nonEmptyOrNull(params.getLinkText())),
ObjectWrapper.wrap(nonEmptyOrNull(params.getTitleText())),
ObjectWrapper.wrap(nonEmptyOrNull(params.getSrcUrl())));
ObjectWrapper.wrap(nonEmptyOrNull(params.getSrcUrl())), params.isImage(),
params.isVideo(), canDownload,
new NativeContextMenuParamsHolder(nativeContextMenuParams));
}
@VisibleForTesting
......@@ -1195,5 +1266,7 @@ public final class TabImpl extends ITab.Stub {
void setTranslateTargetLanguage(long nativeTabImpl, String targetLanguage);
void setDesktopUserAgentEnabled(long nativeTabImpl, boolean enable);
boolean isDesktopUserAgentEnabled(long nativeTabImpl);
void download(long nativeTabImpl, long nativeContextMenuParams);
void destroyContextMenuParams(long contextMenuParams);
}
}
// Copyright 2020 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.
package org.chromium.weblayer_private.interfaces;
/**
* Holds on to the native ContextMenuParams object.
*/
interface IContextMenuParams {
}
......@@ -6,6 +6,7 @@ package org.chromium.weblayer_private.interfaces;
import java.util.List;
import org.chromium.weblayer_private.interfaces.IContextMenuParams;
import org.chromium.weblayer_private.interfaces.IDownloadCallbackClient;
import org.chromium.weblayer_private.interfaces.IErrorPageCallbackClient;
import org.chromium.weblayer_private.interfaces.IFaviconFetcher;
......@@ -81,4 +82,7 @@ interface ITab {
boolean willAutomaticallyReloadAfterCrash() = 28;
void setDesktopUserAgentEnabled(in boolean enable) = 29;
boolean isDesktopUserAgentEnabled() = 30;
// Added in 89
void download(in IContextMenuParams contextMenuParams) = 31;
}
......@@ -4,6 +4,7 @@
package org.chromium.weblayer_private.interfaces;
import org.chromium.weblayer_private.interfaces.IContextMenuParams;
import org.chromium.weblayer_private.interfaces.IObjectWrapper;
/**
......@@ -21,6 +22,7 @@ interface ITabClient {
// void onCloseTab() = 3;
// Added in M82.
// Deprecated in M89.
void showContextMenu(in IObjectWrapper pageUrl, in IObjectWrapper linkUrl,
in IObjectWrapper linkText, in IObjectWrapper titleOrAltText,
in IObjectWrapper srcUrl) = 4;
......@@ -50,4 +52,10 @@ interface ITabClient {
// Added in M88
void onActionItemClicked(
in int actionModeItemType, in IObjectWrapper selectedString) = 12;
// Added in M89.
void showContextMenu2(in IObjectWrapper pageUrl, in IObjectWrapper linkUrl,
in IObjectWrapper linkText, in IObjectWrapper titleOrAltText,
in IObjectWrapper srcUrl, in boolean isImage, in boolean isVideo, in boolean canDownload,
in IContextMenuParams contextMenuParams) = 13;
}
......@@ -38,6 +38,7 @@
#include "components/webrtc/media_stream_devices_controller.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/context_menu_params.h"
#include "content/public/browser/file_select_listener.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
......@@ -97,6 +98,7 @@
#include "base/trace_event/trace_event.h"
#include "components/autofill/android/provider/autofill_provider_android.h"
#include "components/browser_ui/sms/android/sms_infobar.h"
#include "components/download/content/public/context_menu_download.h"
#include "components/embedder_support/android/contextmenu/context_menu_builder.h"
#include "components/embedder_support/android/delegate/color_chooser_android.h"
#include "components/javascript_dialogs/tab_modal_dialog_manager.h" // nogncheck
......@@ -262,6 +264,15 @@ static ScopedJavaLocalRef<jobject> JNI_TabImpl_FromWebContents(
return nullptr;
}
static void JNI_TabImpl_DestroyContextMenuParams(
JNIEnv* env,
jlong native_context_menu_params) {
// Note: this runs on the finalizer thread which isn't the UI thread.
auto* context_menu_params =
reinterpret_cast<content::ContextMenuParams*>(native_context_menu_params);
delete context_menu_params;
}
TabImpl::TabImpl(ProfileImpl* profile,
const JavaParamRef<jobject>& java_impl,
std::unique_ptr<content::WebContents> web_contents)
......@@ -548,7 +559,8 @@ void TabImpl::ShowContextMenu(const content::ContextMenuParams& params) {
#if defined(OS_ANDROID)
Java_TabImpl_showContextMenu(
base::android::AttachCurrentThread(), java_impl_,
context_menu::BuildJavaContextMenuParams(params));
context_menu::BuildJavaContextMenuParams(params),
reinterpret_cast<jlong>(new content::ContextMenuParams(params)));
#endif
}
......@@ -838,6 +850,19 @@ jboolean TabImpl::IsDesktopUserAgentEnabled(JNIEnv* env) {
return entry->GetIsOverridingUserAgent();
}
void TabImpl::Download(JNIEnv* env, jlong native_context_menu_params) {
auto* context_menu_params =
reinterpret_cast<content::ContextMenuParams*>(native_context_menu_params);
bool is_link = context_menu_params->media_type !=
blink::ContextMenuDataMediaType::kImage &&
context_menu_params->media_type !=
blink::ContextMenuDataMediaType::kVideo;
download::CreateContextMenuDownload(web_contents_.get(), *context_menu_params,
std::string(), is_link);
}
#endif // OS_ANDROID
content::WebContents* TabImpl::OpenURLFromTab(
......
......@@ -198,6 +198,7 @@ class TabImpl : public Tab,
const base::android::JavaParamRef<jstring>& translate_target_lang);
void SetDesktopUserAgentEnabled(JNIEnv* env, jboolean enable);
jboolean IsDesktopUserAgentEnabled(JNIEnv* env);
void Download(JNIEnv* env, jlong native_context_menu_params);
#endif
ErrorPageDelegate* error_page_delegate() { return error_page_delegate_; }
......
......@@ -9,6 +9,8 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.chromium.weblayer_private.interfaces.IContextMenuParams;
/**
* Parameters for constructing context menu.
*/
......@@ -45,12 +47,46 @@ public class ContextMenuParams {
@Nullable
public final Uri srcUri;
public ContextMenuParams(
/**
* Whether srcUri points to an image.
*
* @since 89
*/
public final boolean isImage;
/**
* Whether srcUri points to a video.
*
* @since 89
*/
public final boolean isVideo;
/**
* Whether this link or image/video can be downloaded. Only if this is true can
* {@link Tab.download} be called.
*
* @since 89
*/
public final boolean canDownload;
final IContextMenuParams mContextMenuParams;
protected ContextMenuParams(
Uri pageUri, Uri linkUri, String linkText, String titleOrAltText, Uri srcUri) {
this(pageUri, linkUri, linkText, titleOrAltText, srcUri, false, false, false, null);
}
protected ContextMenuParams(Uri pageUri, Uri linkUri, String linkText, String titleOrAltText,
Uri srcUri, boolean isImage, boolean isVideo, boolean canDownload,
IContextMenuParams contextMenuParams) {
this.pageUri = pageUri;
this.linkUri = linkUri;
this.linkText = linkText;
this.titleOrAltText = titleOrAltText;
this.srcUri = srcUri;
this.isImage = isImage;
this.isVideo = isVideo;
this.canDownload = canDownload;
this.mContextMenuParams = contextMenuParams;
}
}
......@@ -18,6 +18,7 @@ import org.json.JSONObject;
import org.chromium.weblayer_private.interfaces.APICallException;
import org.chromium.weblayer_private.interfaces.IClientNavigation;
import org.chromium.weblayer_private.interfaces.IContextMenuParams;
import org.chromium.weblayer_private.interfaces.IErrorPageCallbackClient;
import org.chromium.weblayer_private.interfaces.IFullscreenCallbackClient;
import org.chromium.weblayer_private.interfaces.IGoogleAccountsCallbackClient;
......@@ -825,6 +826,33 @@ public class Tab {
}
}
/**
* Downloads the item linked to from the context menu. This could be an image/video or link.
* This will request the WRITE_EXTERNAL_STORAGE permission if it's not granted to the app.
*
* @throws IllegalArgumentException if {@link ContextMenuParams.canDownload} is false or if
* the ContextMenuParams object parameter wasn't constructed by WebLayer.
*
* @since 89
*/
public void download(ContextMenuParams contextMenuParams) {
if (WebLayer.getSupportedMajorVersionInternal() < 89) {
throw new UnsupportedOperationException();
}
if (!contextMenuParams.canDownload) {
throw new IllegalArgumentException("ContextMenuParams not downloadable.");
}
if (contextMenuParams.mContextMenuParams == null) {
throw new IllegalArgumentException("ContextMenuParams not constructed by WebLayer.");
}
try {
mImpl.download(contextMenuParams.mContextMenuParams);
} catch (RemoteException e) {
throw new APICallException(e);
}
}
// Called by Browser when removed.
void onRemovedFromBrowser() {
if (mDestroyOnRemove) {
......@@ -928,6 +956,15 @@ public class Tab {
@Override
public void showContextMenu(IObjectWrapper pageUrl, IObjectWrapper linkUrl,
IObjectWrapper linkText, IObjectWrapper titleOrAltText, IObjectWrapper srcUrl) {
showContextMenu2(
pageUrl, linkUrl, linkText, titleOrAltText, srcUrl, false, false, false, null);
}
@Override
public void showContextMenu2(IObjectWrapper pageUrl, IObjectWrapper linkUrl,
IObjectWrapper linkText, IObjectWrapper titleOrAltText, IObjectWrapper srcUrl,
boolean isImage, boolean isVideo, boolean canDownload,
IContextMenuParams contextMenuParams) {
StrictModeWorkaround.apply();
String pageUrlString = ObjectWrapper.unwrap(pageUrl, String.class);
String linkUrlString = ObjectWrapper.unwrap(linkUrl, String.class);
......@@ -936,7 +973,8 @@ public class Tab {
linkUrlString != null ? Uri.parse(linkUrlString) : null,
ObjectWrapper.unwrap(linkText, String.class),
ObjectWrapper.unwrap(titleOrAltText, String.class),
srcUrlString != null ? Uri.parse(srcUrlString) : null);
srcUrlString != null ? Uri.parse(srcUrlString) : null, isImage, isVideo,
canDownload, contextMenuParams);
for (TabCallback callback : mCallbacks) {
callback.showContextMenu(params);
}
......
......@@ -96,12 +96,17 @@ public class WebLayerShellActivity extends AppCompatActivity {
implements View.OnCreateContextMenuListener, MenuItem.OnMenuItemClickListener {
private static final int MENU_ID_COPY_LINK_URI = 1;
private static final int MENU_ID_COPY_LINK_TEXT = 2;
private static final int MENU_ID_DOWNLOAD_IMAGE = 3;
private static final int MENU_ID_DOWNLOAD_VIDEO = 4;
private static final int MENU_ID_DOWNLOAD_LINK = 5;
private ContextMenuParams mParams;
private Tab mTab;
private Context mContext;
public ContextMenuCreator(ContextMenuParams params) {
public ContextMenuCreator(ContextMenuParams params, Tab tab) {
mParams = params;
mTab = tab;
}
@Override
......@@ -119,6 +124,21 @@ public class WebLayerShellActivity extends AppCompatActivity {
menu.add(Menu.NONE, MENU_ID_COPY_LINK_TEXT, Menu.NONE, "Copy link text");
copyLinkTextItem.setOnMenuItemClickListener(this);
}
if (mParams.canDownload) {
if (mParams.isImage) {
MenuItem downloadImageItem = menu.add(
Menu.NONE, MENU_ID_DOWNLOAD_IMAGE, Menu.NONE, "Download image");
downloadImageItem.setOnMenuItemClickListener(this);
} else if (mParams.isVideo) {
MenuItem downloadVideoItem = menu.add(
Menu.NONE, MENU_ID_DOWNLOAD_VIDEO, Menu.NONE, "Download video");
downloadVideoItem.setOnMenuItemClickListener(this);
} else if (mParams.linkUri != null) {
MenuItem downloadVideoItem =
menu.add(Menu.NONE, MENU_ID_DOWNLOAD_LINK, Menu.NONE, "Download link");
downloadVideoItem.setOnMenuItemClickListener(this);
}
}
if (!TextUtils.isEmpty(mParams.titleOrAltText)) {
TextView altTextView = new TextView(mContext);
altTextView.setText(mParams.titleOrAltText);
......@@ -139,6 +159,11 @@ public class WebLayerShellActivity extends AppCompatActivity {
case MENU_ID_COPY_LINK_TEXT:
clipboard.setPrimaryClip(ClipData.newPlainText("link text", mParams.linkText));
break;
case MENU_ID_DOWNLOAD_IMAGE:
case MENU_ID_DOWNLOAD_VIDEO:
case MENU_ID_DOWNLOAD_LINK:
mTab.download(mParams);
break;
default:
break;
}
......@@ -629,7 +654,7 @@ public class WebLayerShellActivity extends AppCompatActivity {
@Override
public void showContextMenu(ContextMenuParams params) {
View webLayerView = getSupportFragmentManager().getFragments().get(0).getView();
webLayerView.setOnCreateContextMenuListener(new ContextMenuCreator(params));
webLayerView.setOnCreateContextMenuListener(new ContextMenuCreator(params, tab));
webLayerView.showContextMenu();
}
......
......@@ -13,7 +13,7 @@
</style>
</head>
<body>
<img src="notfound.png" alt="alt_text" />
<img src="favicon.png" alt="alt_text" />
</body>
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment