Commit d6962e5b authored by John Abd-El-Malek's avatar John Abd-El-Malek Committed by Commit Bot

Add support for enabling desktop mode user agent in WebLayer.

Bug: 1147533
Change-Id: I47353963624662f5b09c3be7db314effb0aa75c5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2545368Reviewed-by: default avatarScott Violet <sky@chromium.org>
Commit-Queue: John Abd-El-Malek <jam@chromium.org>
Cr-Commit-Position: refs/heads/master@{#829778}
parent f9e44a87
......@@ -5,9 +5,8 @@
#include "base/android/jni_string.h"
#include "chrome/android/chrome_jni_headers/ContentUtils_jni.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "components/embedder_support/android/util/user_agent_utils.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/user_agent.h"
static base::android::ScopedJavaLocalRef<jstring>
JNI_ContentUtils_GetBrowserUserAgent(JNIEnv* env) {
......@@ -17,21 +16,8 @@ JNI_ContentUtils_GetBrowserUserAgent(JNIEnv* env) {
static void JNI_ContentUtils_SetUserAgentOverride(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jweb_contents) {
const char kLinuxInfoStr[] = "X11; Linux x86_64";
std::string product = version_info::GetProductNameAndVersionForUserAgent();
blink::UserAgentOverride spoofed_ua;
spoofed_ua.ua_string_override =
content::BuildUserAgentFromOSAndProduct(kLinuxInfoStr, product);
spoofed_ua.ua_metadata_override = ::GetUserAgentMetadata();
spoofed_ua.ua_metadata_override->platform = "Linux";
spoofed_ua.ua_metadata_override->platform_version =
std::string(); // match content::GetOSVersion(false) on Linux
spoofed_ua.ua_metadata_override->architecture = "x86";
spoofed_ua.ua_metadata_override->model = std::string();
spoofed_ua.ua_metadata_override->mobile = false;
content::WebContents* web_contents =
content::WebContents::FromJavaWebContents(jweb_contents);
web_contents->SetUserAgentOverride(spoofed_ua, false);
embedder_support::SetDesktopUserAgentOverride(web_contents,
::GetUserAgentMetadata());
}
......@@ -70,6 +70,8 @@ static_library("util") {
"util/response_delegate_impl.cc",
"util/response_delegate_impl.h",
"util/url_utilities.cc",
"util/user_agent_utils.cc",
"util/user_agent_utils.h",
"util/web_resource_response.cc",
"util/web_resource_response.h",
]
......@@ -78,6 +80,7 @@ static_library("util") {
":util_jni_headers",
"//base",
"//components/google/core/common",
"//components/version_info",
"//content/public/browser",
"//mojo/public/cpp/bindings:bindings",
"//mojo/public/cpp/system:system",
......
include_rules = [
"+components/google/core/common",
"+components/version_info",
"+mojo/public/cpp/bindings",
"+mojo/public/cpp/system",
"+net",
"+services/network/public",
"+third_party/blink/public/common/user_agent",
"+third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h",
]
......
// 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/embedder_support/android/util/user_agent_utils.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/user_agent.h"
#include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
namespace embedder_support {
void SetDesktopUserAgentOverride(content::WebContents* web_contents,
const blink::UserAgentMetadata& metadata) {
const char kLinuxInfoStr[] = "X11; Linux x86_64";
std::string product = version_info::GetProductNameAndVersionForUserAgent();
blink::UserAgentOverride spoofed_ua;
spoofed_ua.ua_string_override =
content::BuildUserAgentFromOSAndProduct(kLinuxInfoStr, product);
spoofed_ua.ua_metadata_override = metadata;
spoofed_ua.ua_metadata_override->platform = "Linux";
spoofed_ua.ua_metadata_override->platform_version =
std::string(); // match content::GetOSVersion(false) on Linux
spoofed_ua.ua_metadata_override->architecture = "x86";
spoofed_ua.ua_metadata_override->model = std::string();
spoofed_ua.ua_metadata_override->mobile = false;
web_contents->SetUserAgentOverride(spoofed_ua, false);
}
} // namespace embedder_support
// 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_EMBEDDER_SUPPORT_ANDROID_UTIL_USER_AGENT_UTILS_H_
#define COMPONENTS_EMBEDDER_SUPPORT_ANDROID_UTIL_USER_AGENT_UTILS_H_
namespace blink {
struct UserAgentMetadata;
}
namespace content {
class WebContents;
}
namespace embedder_support {
void SetDesktopUserAgentOverride(content::WebContents* web_contents,
const blink::UserAgentMetadata& metadata);
} // namespace embedder_support
#endif // COMPONENTS_EMBEDDER_SUPPORT_ANDROID_UTIL_USER_AGENT_UTILS_H_
......@@ -201,6 +201,8 @@ source_set("weblayer_lib_base") {
"browser/js_communication/web_message_host_factory_wrapper.h",
"browser/navigation_controller_impl.cc",
"browser/navigation_controller_impl.h",
"browser/navigation_entry_data.cc",
"browser/navigation_entry_data.h",
"browser/navigation_error_navigation_throttle.cc",
"browser/navigation_error_navigation_throttle.h",
"browser/navigation_impl.cc",
......
......@@ -577,6 +577,14 @@ public class NavigationTest {
.registerNavigationCallback(callback));
}
private void unregisterNavigationCallback(NavigationCallback callback) {
runOnUiThreadBlocking(()
-> mActivityTestRule.getActivity()
.getTab()
.getNavigationController()
.unregisterNavigationCallback(callback));
}
private void navigateAndWaitForCompletion(String expectedUrl, Runnable navigateRunnable)
throws Exception {
int currentCallCount = mCallback.onCompletedCallback.getCallCount();
......@@ -736,6 +744,7 @@ public class NavigationTest {
// NavigationCallback implementation that sets the user-agent string in onNavigationStarted().
private static final class UserAgentSetter extends NavigationCallback {
private final String mValue;
public boolean mGotIllegalStateException;
UserAgentSetter(String value) {
mValue = value;
......@@ -743,7 +752,11 @@ public class NavigationTest {
@Override
public void onNavigationStarted(Navigation navigation) {
navigation.setUserAgentString(mValue);
try {
navigation.setUserAgentString(mValue);
} catch (IllegalStateException e) {
mGotIllegalStateException = true;
}
}
}
......@@ -762,6 +775,73 @@ public class NavigationTest {
assertEquals(customUserAgent, actualUserAgent);
}
@Test
@SmallTest
@MinWebLayerVersion(89)
public void testCantUsePerNavigationAndDesktopMode() throws Exception {
TestWebServer testServer = TestWebServer.start();
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
UserAgentSetter setter = new UserAgentSetter("foo");
registerNavigationCallback(setter);
String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
runOnUiThreadBlocking(() -> { activity.getTab().setDesktopUserAgentEnabled(true); });
mActivityTestRule.navigateAndWait(url);
assertTrue(setter.mGotIllegalStateException);
}
@Test
@SmallTest
@MinWebLayerVersion(89)
public void testDesktopMode() throws Exception {
TestWebServer testServer = TestWebServer.start();
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
runOnUiThreadBlocking(() -> { activity.getTab().setDesktopUserAgentEnabled(true); });
mActivityTestRule.navigateAndWait(url);
String actualUserAgent = testServer.getLastRequest("/ok.html").headerValue("User-Agent");
assertFalse(actualUserAgent.contains("Android"));
}
@Test
@SmallTest
@MinWebLayerVersion(89)
public void testDesktopModeSticks() throws Exception {
TestWebServer testServer = TestWebServer.start();
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
String url = testServer.setResponse("/ok.html", "<html>ok</html>", null);
String url2 = testServer.setResponse("/ok2.html", "<html>ok</html>", null);
runOnUiThreadBlocking(() -> { activity.getTab().setDesktopUserAgentEnabled(true); });
mActivityTestRule.navigateAndWait(url);
mActivityTestRule.navigateAndWait(url2);
String actualUserAgent = testServer.getLastRequest("/ok2.html").headerValue("User-Agent");
assertFalse(actualUserAgent.contains("Android"));
}
@Test
@SmallTest
@MinWebLayerVersion(89)
public void testDesktopModeGetter() throws Exception {
TestWebServer testServer = TestWebServer.start();
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
setNavigationCallback(activity);
UserAgentSetter setter = new UserAgentSetter("foo");
registerNavigationCallback(setter);
mActivityTestRule.navigateAndWait(URL1);
unregisterNavigationCallback(setter);
runOnUiThreadBlocking(
() -> { assertFalse(activity.getTab().isDesktopUserAgentEnabled()); });
runOnUiThreadBlocking(() -> { activity.getTab().setDesktopUserAgentEnabled(true); });
mActivityTestRule.navigateAndWait(URL2);
runOnUiThreadBlocking(() -> { assertTrue(activity.getTab().isDesktopUserAgentEnabled()); });
navigateAndWaitForCompletion(
URL1, () -> activity.getTab().getNavigationController().goBack());
runOnUiThreadBlocking(
() -> { assertFalse(activity.getTab().isDesktopUserAgentEnabled()); });
}
@Test
@SmallTest
@MinWebLayerVersion(85)
......
......@@ -640,6 +640,18 @@ public final class TabImpl extends ITab.Stub {
mActionModeCallback.setOverride(actionModeItemTypes);
}
@Override
public void setDesktopUserAgentEnabled(boolean enable) {
StrictModeWorkaround.apply();
TabImplJni.get().setDesktopUserAgentEnabled(mNativeTab, enable);
}
@Override
public boolean isDesktopUserAgentEnabled() {
StrictModeWorkaround.apply();
return TabImplJni.get().isDesktopUserAgentEnabled(mNativeTab);
}
public void removeFaviconCallbackProxy(FaviconCallbackProxy proxy) {
mFaviconCallbackProxies.remove(proxy);
}
......@@ -1177,5 +1189,7 @@ public final class TabImpl extends ITab.Stub {
boolean canTranslate(long nativeTabImpl);
void showTranslateUi(long nativeTabImpl);
void setTranslateTargetLanguage(long nativeTabImpl, String targetLanguage);
void setDesktopUserAgentEnabled(long nativeTabImpl, boolean enable);
boolean isDesktopUserAgentEnabled(long nativeTabImpl);
}
}
......@@ -79,4 +79,8 @@ interface ITab {
// Added in 88
void setFloatingActionModeOverride(in int actionModeItemTypes) = 27;
boolean willAutomaticallyReloadAfterCrash() = 28;
// Added in 89
void setDesktopUserAgentEnabled(in boolean enable) = 29;
boolean isDesktopUserAgentEnabled() = 30;
}
......@@ -9,11 +9,13 @@
#include "base/auto_reset.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/page_transition_types.h"
#include "weblayer/browser/navigation_entry_data.h"
#include "weblayer/browser/navigation_ui_data_impl.h"
#include "weblayer/browser/tab_impl.h"
#include "weblayer/public/navigation_observer.h"
......@@ -353,7 +355,18 @@ void NavigationControllerImpl::DidStartNavigation(
base::AutoReset<NavigationImpl*> auto_reset(&navigation_starting_,
navigation);
navigation->set_safe_to_set_request_headers(true);
navigation->set_safe_to_set_user_agent(true);
#if defined(OS_ANDROID)
// Desktop mode and per-navigation UA use the same mechanism and so don't
// interact well. It's not possible to support both at the same time since
// if there's a per-navigation UA active and desktop mode is turned on, or
// was on previously, the WebContent's state would have to change before
// navigation even though that would be wrong for the previous navigation if
// the new navigation didn't commit.
if (!TabImpl::FromWebContents(web_contents())->desktop_user_agent_enabled())
#endif
navigation->set_safe_to_set_user_agent(true);
#if defined(OS_ANDROID)
NavigationUIDataImpl* navigation_ui_data = static_cast<NavigationUIDataImpl*>(
navigation_handle->GetNavigationUIData());
......@@ -416,6 +429,21 @@ void NavigationControllerImpl::DidFinishNavigation(
DelayDeletionHelper deletion_helper(this);
DCHECK(navigation_map_.find(navigation_handle) != navigation_map_.end());
auto* navigation = navigation_map_[navigation_handle].get();
if (navigation_handle->HasCommitted()) {
// Set state on NavigationEntry user data if a per-navigation user agent was
// specified. This can't be done earlier because a NavigationEntry might not
// have existed at the time that SetUserAgentString was called.
if (navigation->set_user_agent_string_called()) {
auto* entry = web_contents()->GetController().GetLastCommittedEntry();
if (entry) {
auto* entry_data = NavigationEntryData::Get(entry);
if (entry_data)
entry_data->set_per_navigation_user_agent_override(true);
}
}
}
if (navigation_handle->GetNetErrorCode() == net::OK &&
!navigation_handle->IsErrorPage()) {
#if defined(OS_ANDROID)
......@@ -528,10 +556,18 @@ void NavigationControllerImpl::NotifyLoadStateChanged() {
void NavigationControllerImpl::DoNavigate(
std::unique_ptr<content::NavigationController::LoadURLParams> params) {
// Navigations should use the default user-agent. If the embedder wants a
// custom user-agent, the embedder will call Navigation::SetUserAgentString().
params->override_user_agent =
content::NavigationController::UA_OVERRIDE_FALSE;
// Navigations should use the default user-agent (which may be overridden if
// desktop mode is turned on). If the embedder wants a custom user-agent, the
// embedder will call Navigation::SetUserAgentString() in DidStartNavigation.
#if defined(OS_ANDROID)
// We need to set UA_OVERRIDE_FALSE if per navigation UA is set. However at
// this point we don't know if the embedder will call that later. Since we
// ensure that the two can't be set at the same time, it's sufficient to
// not enable it if desktop mode is turned on.
if (!TabImpl::FromWebContents(web_contents())->desktop_user_agent_enabled())
#endif
params->override_user_agent =
content::NavigationController::UA_OVERRIDE_FALSE;
if (navigation_starting_) {
// DoNavigate() is being called reentrantly. Delay processing until it's
// safe.
......
// 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 "weblayer/browser/navigation_entry_data.h"
#include "base/memory/ptr_util.h"
#include "content/public/browser/navigation_entry.h"
namespace weblayer {
namespace {
const char kCacheKey[] = "weblayer_navigation_entry_data";
} // namespace
NavigationEntryData::ResponseData::ResponseData() = default;
NavigationEntryData::ResponseData::~ResponseData() = default;
NavigationEntryData::NavigationEntryData() = default;
NavigationEntryData::~NavigationEntryData() = default;
std::unique_ptr<base::SupportsUserData::Data> NavigationEntryData::Clone() {
auto rv = base::WrapUnique(new NavigationEntryData);
rv->per_navigation_user_agent_override_ = per_navigation_user_agent_override_;
if (response_data_) {
rv->response_data_ = std::make_unique<ResponseData>();
rv->response_data_->response_head = response_data_->response_head.Clone();
rv->response_data_->data = response_data_->data;
rv->response_data_->request_time = response_data_->request_time;
rv->response_data_->response_time = response_data_->response_time;
}
return rv;
}
NavigationEntryData* NavigationEntryData::Get(content::NavigationEntry* entry) {
auto* data = static_cast<NavigationEntryData*>(entry->GetUserData(kCacheKey));
if (!data) {
auto data_object = base::WrapUnique(new NavigationEntryData);
data = data_object.get();
entry->SetUserData(kCacheKey, std::move(data_object));
}
return data;
}
} // namespace weblayer
// 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 WEBLAYER_BROWSER_NAVIGATION_ENTRY_DATA_H_
#define WEBLAYER_BROWSER_NAVIGATION_ENTRY_DATA_H_
#include "base/supports_user_data.h"
#include "base/time/time.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
namespace content {
class NavigationEntry;
}
namespace weblayer {
// Holds extra data stored on content::NavigationEntry.
class NavigationEntryData : public base::SupportsUserData::Data {
public:
~NavigationEntryData() override;
// base::SupportsUserData::Data implementation:
std::unique_ptr<Data> Clone() override;
static NavigationEntryData* Get(content::NavigationEntry* entry);
// Stored on the NavigationEntry when we have a cached response from an
// InputStream.
struct ResponseData {
ResponseData();
~ResponseData();
network::mojom::URLResponseHeadPtr response_head;
std::string data;
base::Time request_time;
base::Time response_time;
};
void set_response_data(std::unique_ptr<ResponseData> data) {
response_data_ = std::move(data);
}
void reset_response_data() { response_data_.reset(); }
ResponseData* response_data() { return response_data_.get(); }
void set_per_navigation_user_agent_override(bool value) {
per_navigation_user_agent_override_ = value;
}
bool per_navigation_user_agent_override() {
return per_navigation_user_agent_override_;
}
private:
NavigationEntryData();
std::unique_ptr<ResponseData> response_data_;
bool per_navigation_user_agent_override_ = false;
};
} // namespace weblayer
#endif // WEBLAYER_BROWSER_NAVIGATION_ENTRY_DATA_H_
......@@ -192,10 +192,17 @@ void NavigationImpl::SetRequestHeader(const std::string& name,
void NavigationImpl::SetUserAgentString(const std::string& value) {
DCHECK(safe_to_set_user_agent_);
// By default renderer initiated navigations inherit the user-agent override
// of the current NavigationEntry. But we don't want this per-navigation UA to
// be inherited.
navigation_handle_->GetWebContents()
->SetRendererInitiatedUserAgentOverrideOption(
content::NavigationController::UA_OVERRIDE_FALSE);
navigation_handle_->GetWebContents()->SetUserAgentOverride(
blink::UserAgentOverride::UserAgentOnly(value),
/* override_in_new_tabs */ false);
navigation_handle_->SetIsOverridingUserAgent(!value.empty());
set_user_agent_string_called_ = true;
}
#if defined(OS_ANDROID)
......
......@@ -51,6 +51,8 @@ class NavigationImpl : public Navigation {
void set_was_stopped() { was_stopped_ = true; }
bool set_user_agent_string_called() { return set_user_agent_string_called_; }
void SetParamsToLoadWhenSafe(
std::unique_ptr<content::NavigationController::LoadURLParams> params);
std::unique_ptr<content::NavigationController::LoadURLParams>
......@@ -123,6 +125,9 @@ class NavigationImpl : public Navigation {
// Whether NavigationController::Stop() was called for this navigation.
bool was_stopped_ = false;
// Whether SetUserAgentString was called.
bool set_user_agent_string_called_ = false;
#if defined(OS_ANDROID)
base::android::ScopedJavaGlobalRef<jobject> java_navigation_;
std::unique_ptr<embedder_support::WebResourceResponse> response_;
......
......@@ -13,13 +13,12 @@
#include "content/public/browser/web_contents.h"
#include "mojo/public/cpp/system/data_pipe_producer.h"
#include "mojo/public/cpp/system/string_data_source.h"
#include "weblayer/browser/navigation_entry_data.h"
namespace weblayer {
namespace {
const char kCacheKey[] = "weblayer_entry_cache_data";
struct WriteData {
mojo::Remote<network::mojom::URLLoaderClient> client;
std::string data;
......@@ -109,37 +108,6 @@ bool IsCachedResponseValid(net::HttpResponseHeaders* headers,
base::Time::Now()) == net::VALIDATION_NONE;
}
// Stored on the NavigationEntry when we have a cached response from an
// InputStream.
class NavigationEntryCache : public base::SupportsUserData::Data {
public:
NavigationEntryCache(network::mojom::URLResponseHeadPtr response_head,
const std::string& data,
base::Time request_time,
base::Time response_time)
: response_head_(std::move(response_head)),
data_(data),
request_time_(request_time),
response_time_(response_time) {}
std::unique_ptr<Data> Clone() override {
return std::make_unique<NavigationEntryCache>(
response_head_.Clone(), data_, request_time_, response_time_);
}
network::mojom::URLResponseHead* response_head() {
return response_head_.get();
}
const std::string& data() const { return data_; }
const base::Time& request_time() const { return request_time_; }
const base::Time& response_time() const { return response_time_; }
private:
network::mojom::URLResponseHeadPtr response_head_;
std::string data_;
base::Time request_time_;
base::Time response_time_;
};
// A ResponseDelegate for AndroidStreamReaderURLLoader which will cache the
// response if it's successful. This allows back-forward navigations to reuse an
......@@ -177,9 +145,13 @@ class CachingResponseDelegate : public embedder_support::ResponseDelegateImpl {
if (!entry)
return;
auto cache_data = std::make_unique<NavigationEntryCache>(
std::move(response_head_), data, request_time_, response_time_);
entry->SetUserData(kCacheKey, std::move(cache_data));
auto* entry_data = NavigationEntryData::Get(entry);
auto response_data = std::make_unique<NavigationEntryData::ResponseData>();
response_data->response_head = std::move(response_head_);
response_data->data = data;
response_data->request_time = request_time_;
response_data->response_time = response_time_;
entry_data->set_response_data(std::move(response_data));
}
private:
......@@ -230,15 +202,19 @@ bool ProxyingURLLoaderFactoryImpl::HasCachedInputStream(
if (!entry)
return false;
auto* cache =
static_cast<NavigationEntryCache*>(entry->GetUserData(kCacheKey));
if (!cache)
auto* entry_data = NavigationEntryData::Get(entry);
if (!entry_data)
return false;
auto* response_data = entry_data->response_data();
if (!response_data)
return false;
if (!IsCachedResponseValid(cache->response_head()->headers.get(),
cache->request_time(), cache->response_time())) {
if (!IsCachedResponseValid(response_data->response_head->headers.get(),
response_data->request_time,
response_data->response_time)) {
// Cache expired so remove it.
entry->RemoveUserData(kCacheKey);
entry_data->reset_response_data();
return false;
}
......@@ -269,10 +245,10 @@ void ProxyingURLLoaderFactoryImpl::CreateLoaderAndStart(
navigation_entry_unique_id_)) {
auto* entry = GetNavigationEntryFromUniqueId(frame_tree_node_id_,
navigation_entry_unique_id_);
auto* cache =
static_cast<NavigationEntryCache*>(entry->GetUserData(kCacheKey));
StartCachedLoad(std::move(client), cache->response_head()->Clone(),
cache->data());
auto* entry_data = NavigationEntryData::Get(entry);
auto* response_data = entry_data->response_data();
StartCachedLoad(std::move(client), response_data->response_head->Clone(),
response_data->data);
return;
}
}
......
......@@ -22,6 +22,7 @@
#include "components/blocked_content/popup_tracker.h"
#include "components/captive_portal/core/buildflags.h"
#include "components/content_settings/browser/page_specific_content_settings.h"
#include "components/embedder_support/android/util/user_agent_utils.h"
#include "components/find_in_page/find_tab_helper.h"
#include "components/find_in_page/find_types.h"
#include "components/js_injection/browser/js_communication_host.h"
......@@ -39,6 +40,7 @@
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/file_select_listener.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
......@@ -63,6 +65,7 @@
#include "weblayer/browser/infobar_service.h"
#include "weblayer/browser/js_communication/web_message_host_factory_wrapper.h"
#include "weblayer/browser/navigation_controller_impl.h"
#include "weblayer/browser/navigation_entry_data.h"
#include "weblayer/browser/no_state_prefetch/prerender_tab_helper.h"
#include "weblayer/browser/page_load_metrics_initialize.h"
#include "weblayer/browser/page_specific_content_settings_delegate.h"
......@@ -72,6 +75,7 @@
#include "weblayer/browser/popup_navigation_delegate_impl.h"
#include "weblayer/browser/profile_impl.h"
#include "weblayer/browser/translate_client_impl.h"
#include "weblayer/browser/user_agent.h"
#include "weblayer/browser/weblayer_features.h"
#include "weblayer/common/isolated_world_ids.h"
#include "weblayer/public/fullscreen_delegate.h"
......@@ -282,12 +286,6 @@ TabImpl::TabImpl(ProfileImpl* profile,
// notifying weblayer observers of changes.
FaviconTabHelper::CreateForWebContents(web_contents_.get());
// By default renderer initiated navigations inherit the user-agent override
// of the current NavigationEntry. For WebLayer, the user-agent override is
// set on a per NavigationEntry entry basis.
web_contents_->SetRendererInitiatedUserAgentOverrideOption(
content::NavigationController::UA_OVERRIDE_FALSE);
UpdateRendererPrefs(false);
locale_change_subscription_ =
i18n::RegisterLocaleChangeCallback(base::BindRepeating(
......@@ -798,6 +796,45 @@ void TabImpl::SetTranslateTargetLanguage(
translate_manager->SetPredefinedTargetLanguage(
base::android::ConvertJavaStringToUTF8(env, translate_target_lang));
}
void TabImpl::SetDesktopUserAgentEnabled(JNIEnv* env, jboolean enable) {
if (desktop_user_agent_enabled_ == enable)
return;
desktop_user_agent_enabled_ = enable;
// Reset state that an earlier call to Navigation::SetUserAgentString()
// could have modified.
embedder_support::SetDesktopUserAgentOverride(web_contents_.get(),
GetUserAgentMetadata());
web_contents_->SetRendererInitiatedUserAgentOverrideOption(
content::NavigationController::UA_OVERRIDE_INHERIT);
content::NavigationEntry* entry =
web_contents_->GetController().GetLastCommittedEntry();
if (!entry)
return;
entry->SetIsOverridingUserAgent(enable);
web_contents_->NotifyPreferencesChanged();
web_contents_->GetController().Reload(
content::ReloadType::ORIGINAL_REQUEST_URL, true);
}
jboolean TabImpl::IsDesktopUserAgentEnabled(JNIEnv* env) {
auto* entry = web_contents_->GetController().GetLastCommittedEntry();
if (!entry)
return false;
// The same user agent override mechanism is used for per-navigation user
// agent and desktop mode. Make sure not to return desktop mode for
// navigation entries which used a per-navigation user agent.
auto* entry_data = NavigationEntryData::Get(entry);
if (entry_data && entry_data->per_navigation_user_agent_override())
return false;
return entry->GetIsOverridingUserAgent();
}
#endif // OS_ANDROID
content::WebContents* TabImpl::OpenURLFromTab(
......
......@@ -139,6 +139,8 @@ class TabImpl : public Tab,
return java_impl_;
}
bool desktop_user_agent_enabled() { return desktop_user_agent_enabled_; }
// Call this method to disable integration with the system-level Autofill
// infrastructure. Useful in conjunction with InitializeAutofillForTests().
// Should be called early in the lifetime of WebLayer, and in
......@@ -194,6 +196,8 @@ class TabImpl : public Tab,
void SetTranslateTargetLanguage(
JNIEnv* env,
const base::android::JavaParamRef<jstring>& translate_target_lang);
void SetDesktopUserAgentEnabled(JNIEnv* env, jboolean enable);
jboolean IsDesktopUserAgentEnabled(JNIEnv* env);
#endif
ErrorPageDelegate* error_page_delegate() { return error_page_delegate_; }
......@@ -387,6 +391,8 @@ class TabImpl : public Tab,
std::map<std::string, std::unique_ptr<WebMessageHostFactoryProxy>>
js_name_to_proxy_;
bool desktop_user_agent_enabled_ = false;
#endif
bool is_fullscreen_ = false;
......
......@@ -170,6 +170,10 @@ public class Navigation extends IClientNavigation.Stub {
* reset during the redirect. In other words, if you need to set a referer that applies to
* redirects, then this must be called from {@link onNavigationRedirected}.
*
* Note that any headers that are set here won't be sent again if the frame html is fetched
* again due to a user reloading the page, navigating back and forth etc... when this fetch
* couldn't be cached (either in the disk cache or in the back-forward cache).
*
* @param name The name of the header. The name must be rfc 2616 compliant.
* @param value The value of the header. The value must not contain '\0', '\n' or '\r'.
*
......@@ -192,11 +196,16 @@ public class Navigation extends IClientNavigation.Stub {
* sticky, it applies to this navigation only (and any redirects or resources that are loaded).
* This method may only be called from {@link NavigationCallback.onNavigationStarted}.
*
* Note that this user agent won't be sent again if the frame html is fetched again due to a
* user reloading the page, navigating back and forth etc... when this fetch couldn't be cached
* (either in the disk cache or in the back-forward cache).
*
* @param value The user-agent string. The value must not contain '\0', '\n' or '\r'. An empty
* string results in the default user-agent string.
*
* @throws IllegalArgumentException If supplied an invalid value.
* @throws IllegalStateException If not called during start.
* @throws IllegalStateException If not called during start or if {@link
* Tab.setDesktopUserAgent} was called with a value of true.
*
* @since 84
*/
......
......@@ -787,6 +787,42 @@ public class Tab {
}
}
/**
* Turns on desktop user agent if enable is true, otherwise reverts back to mobile user agent.
* The selected user agent will be used for future navigations until this method is called
* again. Each navigation saves the user agent mode it was navigated with and will reuse that on
* back/forward navigations. The tab will be reloaded with the new user agent.
* @param enable if true requests desktop site, otherwise mobile site.
*
* @since 89
*/
public void setDesktopUserAgentEnabled(boolean enable) {
if (WebLayer.getSupportedMajorVersionInternal() < 89) {
throw new UnsupportedOperationException();
}
try {
mImpl.setDesktopUserAgentEnabled(enable);
} catch (RemoteException e) {
throw new APICallException(e);
}
}
/**
* Returns true if the currently loaded page used a desktop user agent.
*
* @since 89
*/
public boolean isDesktopUserAgentEnabled() {
if (WebLayer.getSupportedMajorVersionInternal() < 89) {
throw new UnsupportedOperationException();
}
try {
return mImpl.isDesktopUserAgentEnabled();
} catch (RemoteException e) {
throw new APICallException(e);
}
}
// Called by Browser when removed.
void onRemovedFromBrowser() {
if (mDestroyOnRemove) {
......
......@@ -26,4 +26,8 @@
android:title="Set translate target language to German" />
<item android:id="@+id/clear_translate_target_lang_menu_id"
android:title="Clear translate target language override" />
<item android:id="@+id/desktop_site_menu_id"
android:title="Request desktop site" />
<item android:id="@+id/no_desktop_site_menu_id"
android:title="Request mobile site" />
</menu>
......@@ -241,6 +241,9 @@ public class WebLayerShellActivity extends AppCompatActivity {
.setVisible(mBrowser.getActiveTab().canTranslate());
popup.getMenu().findItem(R.id.webview_compat_menu_id).setVisible(!mEnableWebViewCompat);
popup.getMenu().findItem(R.id.no_webview_compat_menu_id).setVisible(mEnableWebViewCompat);
boolean isDesktopUserAgent = mBrowser.getActiveTab().isDesktopUserAgentEnabled();
popup.getMenu().findItem(R.id.desktop_site_menu_id).setVisible(!isDesktopUserAgent);
popup.getMenu().findItem(R.id.no_desktop_site_menu_id).setVisible(isDesktopUserAgent);
popup.setOnMenuItemClickListener(item -> {
if (item.getItemId() == R.id.reload_menu_id) {
mBrowser.getActiveTab().getNavigationController().reload();
......@@ -296,6 +299,14 @@ public class WebLayerShellActivity extends AppCompatActivity {
restartShell(false);
}
if (item.getItemId() == R.id.desktop_site_menu_id) {
mBrowser.getActiveTab().setDesktopUserAgentEnabled(true);
}
if (item.getItemId() == R.id.no_desktop_site_menu_id) {
mBrowser.getActiveTab().setDesktopUserAgentEnabled(false);
}
if (item.getItemId() == R.id.set_translate_target_lang_menu_id) {
mBrowser.getActiveTab().setTranslateTargetLanguage("de");
}
......
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