User-agent-customization.patch 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200
  1. From: uazo <uazo@users.noreply.github.com>
  2. Date: Fri, 9 Apr 2021 20:09:08 +0000
  3. Subject: User agent customization
  4. Add flag to always view the desktop site for all websites
  5. Add possibility to define a custom User agent for mobile and desktop mode.
  6. Add possibility to reactivate the metatag view for desktop mode, allowing users to choose
  7. to use the flag in the hamburger menu to navigate with a custom useragent leaving the standard navigation unchanged.
  8. Original License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html
  9. License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
  10. ---
  11. base/base_switches.cc | 2 +
  12. base/base_switches.h | 2 +
  13. chrome/android/chrome_java_resources.gni | 2 +
  14. chrome/android/chrome_java_sources.gni | 1 +
  15. .../layout/custom_useragent_preferences.xml | 106 ++++++++++
  16. .../android/java/res/xml/main_preferences.xml | 5 +
  17. .../java/res/xml/useragent_preferences.xml | 31 +++
  18. .../init/ChromeBrowserInitializer.java | 3 +
  19. .../PrivacyPreferencesManagerImpl.java | 42 ++++
  20. .../settings/UserAgentPreferences.java | 188 ++++++++++++++++++
  21. .../chromium/chrome/browser/tab/TabImpl.java | 70 +++++++
  22. .../chromium/chrome/browser/tab/TabUtils.java | 25 +--
  23. .../browser/android/content/content_utils.cc | 28 +++
  24. .../preferences/browser_prefs_android.cc | 7 +
  25. .../privacy_preferences_manager_impl.cc | 121 +++++++++++
  26. .../preferences/ChromePreferenceKeys.java | 5 +
  27. .../settings/PrivacyPreferencesManager.java | 8 +
  28. .../org/chromium/chrome/browser/tab/Tab.java | 2 +
  29. .../browser/tabmodel/TabWindowManager.java | 2 +
  30. .../tabmodel/TabWindowManagerImpl.java | 18 ++
  31. .../strings/android_chrome_strings.grd | 35 ++++
  32. chrome/common/pref_names.cc | 13 ++
  33. chrome/common/pref_names.h | 8 +
  34. .../widget/RadioButtonWithEditText.java | 11 +
  35. .../embedder_support/user_agent_utils.cc | 11 +
  36. .../navigation_controller_android.cc | 4 +
  37. .../navigation_controller_android.h | 1 +
  38. .../renderer_host/render_process_host_impl.cc | 1 +
  39. .../browser/web_contents/web_contents_impl.cc | 4 +
  40. .../framehost/NavigationControllerImpl.java | 3 +-
  41. content/renderer/render_thread_impl.cc | 1 -
  42. 31 files changed, 746 insertions(+), 14 deletions(-)
  43. create mode 100644 chrome/android/java/res/layout/custom_useragent_preferences.xml
  44. create mode 100644 chrome/android/java/res/xml/useragent_preferences.xml
  45. create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/settings/UserAgentPreferences.java
  46. diff --git a/base/base_switches.cc b/base/base_switches.cc
  47. --- a/base/base_switches.cc
  48. +++ b/base/base_switches.cc
  49. @@ -183,6 +183,8 @@ const char kEnableThreadInstructionCount[] = "enable-thread-instruction-count";
  50. extern const char kEnableCrashpad[] = "enable-crashpad";
  51. #endif
  52. +const char kDesktopModeViewportMetaEnabled[] = "dm-viewport-meta-enabled";
  53. +
  54. #if BUILDFLAG(IS_CHROMEOS)
  55. // Override the default scheduling boosting value for urgent tasks.
  56. // This can be adjusted if a specific chromeos device shows better perf/power
  57. diff --git a/base/base_switches.h b/base/base_switches.h
  58. --- a/base/base_switches.h
  59. +++ b/base/base_switches.h
  60. @@ -72,6 +72,8 @@ extern const char kEnableCrashpad[];
  61. extern const char kSchedulerBoostUrgent[];
  62. #endif
  63. +extern const char kDesktopModeViewportMetaEnabled[];
  64. +
  65. } // namespace switches
  66. #endif // BASE_BASE_SWITCHES_H_
  67. diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
  68. --- a/chrome/android/chrome_java_resources.gni
  69. +++ b/chrome/android/chrome_java_resources.gni
  70. @@ -669,4 +669,6 @@ chrome_java_resources = [
  71. "java/res/xml/privacy_preferences.xml",
  72. "java/res/xml/search_widget_info.xml",
  73. "java/res/xml/tracing_preferences.xml",
  74. + "java/res/xml/useragent_preferences.xml",
  75. + "java/res/layout/custom_useragent_preferences.xml",
  76. ]
  77. diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
  78. --- a/chrome/android/chrome_java_sources.gni
  79. +++ b/chrome/android/chrome_java_sources.gni
  80. @@ -884,6 +884,7 @@ chrome_java_sources = [
  81. "java/src/org/chromium/chrome/browser/payments/ui/DimmingDialog.java",
  82. "java/src/org/chromium/chrome/browser/payments/ui/LineItem.java",
  83. "java/src/org/chromium/chrome/browser/payments/ui/PaymentAppComparator.java",
  84. + "java/src/org/chromium/chrome/browser/settings/UserAgentPreferences.java",
  85. "java/src/org/chromium/chrome/browser/payments/ui/PaymentInformation.java",
  86. "java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestBottomBar.java",
  87. "java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestHeader.java",
  88. diff --git a/chrome/android/java/res/layout/custom_useragent_preferences.xml b/chrome/android/java/res/layout/custom_useragent_preferences.xml
  89. new file mode 100644
  90. --- /dev/null
  91. +++ b/chrome/android/java/res/layout/custom_useragent_preferences.xml
  92. @@ -0,0 +1,106 @@
  93. +<?xml version="1.0" encoding="utf-8"?>
  94. +<!--
  95. + This file is part of Bromite.
  96. +
  97. + Bromite is free software: you can redistribute it and/or modify
  98. + it under the terms of the GNU General Public License as published by
  99. + the Free Software Foundation, either version 3 of the License, or
  100. + (at your option) any later version.
  101. +
  102. + Bromite is distributed in the hope that it will be useful,
  103. + but WITHOUT ANY WARRANTY; without even the implied warranty of
  104. + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  105. + GNU General Public License for more details.
  106. +
  107. + You should have received a copy of the GNU General Public License
  108. + along with Bromite. If not, see <https://www.gnu.org/licenses/>.
  109. +-->
  110. +
  111. +<!-- Layout used by the UserAgentPreferences. -->
  112. +
  113. +<ScrollView
  114. + xmlns:android="http://schemas.android.com/apk/res/android"
  115. + xmlns:app="http://schemas.android.com/apk/res-auto"
  116. + android:layout_width="match_parent"
  117. + android:layout_height="wrap_content">
  118. +
  119. + <LinearLayout
  120. + android:layout_width="match_parent"
  121. + android:layout_height="wrap_content"
  122. + android:focusable="false"
  123. + android:orientation="vertical"
  124. + android:divider="?android:dividerHorizontal">
  125. +
  126. + <TextView
  127. + android:layout_width="match_parent"
  128. + android:layout_height="wrap_content"
  129. + android:textAppearance="@style/TextAppearance.AccessibilityTextPreference"
  130. + android:background="@color/default_bg_color_secondary"
  131. + android:padding="16dp"
  132. + android:text="@string/custom_ua_text"/>
  133. +
  134. + <org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout
  135. + android:id="@+id/ua_radio_button_layout"
  136. + android:layout_width="match_parent"
  137. + android:layout_height="wrap_content">
  138. +
  139. + <org.chromium.components.browser_ui.widget.RadioButtonWithDescription
  140. + android:id="@+id/default_ua_switch"
  141. + android:layout_width="match_parent"
  142. + android:layout_height="wrap_content"
  143. + android:paddingStart="?android:attr/listPreferredItemPaddingStart"
  144. + app:primaryText="@string/custom_ua_flag_off" />
  145. +
  146. + <org.chromium.components.browser_ui.widget.RadioButtonWithEditText
  147. + android:id="@+id/custom_ua_switch"
  148. + android:layout_width="match_parent"
  149. + android:layout_height="wrap_content"
  150. + android:paddingStart="?android:attr/listPreferredItemPaddingStart"
  151. + android:inputType="text"
  152. + android:hint="@string/custom_ua_placeholder"
  153. + app:descriptionText="@string/custom_ua_flag_on" />
  154. +
  155. + </org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout>
  156. +
  157. + <TextView
  158. + android:layout_width="match_parent"
  159. + android:layout_height="wrap_content"
  160. + android:textAppearance="@style/TextAppearance.AccessibilityTextPreference"
  161. + android:background="@color/default_bg_color_secondary"
  162. + android:padding="16dp"
  163. + android:text="@string/custom_desktop_ua_text"/>
  164. +
  165. + <org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout
  166. + android:id="@+id/ua_radio_button_layout_dm"
  167. + android:layout_width="match_parent"
  168. + android:layout_height="wrap_content">
  169. +
  170. + <org.chromium.components.browser_ui.widget.RadioButtonWithDescription
  171. + android:id="@+id/default_ua_switch_dm"
  172. + android:layout_width="match_parent"
  173. + android:layout_height="wrap_content"
  174. + android:paddingStart="?android:attr/listPreferredItemPaddingStart"
  175. + app:primaryText="@string/custom_ua_flag_off" />
  176. +
  177. + <org.chromium.components.browser_ui.widget.RadioButtonWithEditText
  178. + android:id="@+id/custom_ua_switch_dm"
  179. + android:layout_width="match_parent"
  180. + android:layout_height="wrap_content"
  181. + android:paddingStart="?android:attr/listPreferredItemPaddingStart"
  182. + android:inputType="text"
  183. + android:hint="@string/custom_ua_placeholder"
  184. + app:descriptionText="@string/custom_ua_flag_on" />
  185. +
  186. + </org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout>
  187. +
  188. + <CheckBox
  189. + android:id="@+id/desktop_mode_viewportmeta"
  190. + android:layout_width="wrap_content"
  191. + android:layout_height="wrap_content"
  192. + android:layout_centerVertical="true"
  193. + android:layout_marginLeft="?android:attr/listPreferredItemPaddingStart"
  194. + android:text="@string/desktop_mode_viewportmeta_checkbox" />
  195. +
  196. + </LinearLayout>
  197. +
  198. +</ScrollView>
  199. diff --git a/chrome/android/java/res/xml/main_preferences.xml b/chrome/android/java/res/xml/main_preferences.xml
  200. --- a/chrome/android/java/res/xml/main_preferences.xml
  201. +++ b/chrome/android/java/res/xml/main_preferences.xml
  202. @@ -81,6 +81,11 @@
  203. android:key="content_settings"
  204. android:order="18"
  205. android:title="@string/prefs_site_settings"/>
  206. + <Preference
  207. + android:fragment="org.chromium.chrome.browser.settings.UserAgentPreferences"
  208. + android:key="useragent_settings"
  209. + android:order="20"
  210. + android:title="@string/prefs_useragent_settings"/>
  211. <Preference
  212. android:fragment="org.chromium.chrome.browser.language.settings.LanguageSettings"
  213. android:key="languages"
  214. diff --git a/chrome/android/java/res/xml/useragent_preferences.xml b/chrome/android/java/res/xml/useragent_preferences.xml
  215. new file mode 100644
  216. --- /dev/null
  217. +++ b/chrome/android/java/res/xml/useragent_preferences.xml
  218. @@ -0,0 +1,31 @@
  219. +<?xml version="1.0" encoding="utf-8"?>
  220. +<!--
  221. + This file is part of Bromite.
  222. +
  223. + Bromite is free software: you can redistribute it and/or modify
  224. + it under the terms of the GNU General Public License as published by
  225. + the Free Software Foundation, either version 3 of the License, or
  226. + (at your option) any later version.
  227. +
  228. + Bromite is distributed in the hope that it will be useful,
  229. + but WITHOUT ANY WARRANTY; without even the implied warranty of
  230. + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  231. + GNU General Public License for more details.
  232. +
  233. + You should have received a copy of the GNU General Public License
  234. + along with Bromite. If not, see <https://www.gnu.org/licenses/>.
  235. +-->
  236. +
  237. +<!-- Layout used by the UserAgentPreferences. -->
  238. +
  239. +<PreferenceScreen
  240. + xmlns:android="http://schemas.android.com/apk/res/android"
  241. + xmlns:app="http://schemas.android.com/apk/res-auto">
  242. +
  243. + <org.chromium.components.browser_ui.settings.ChromeSwitchPreference
  244. + android:key="desktop_mode_switch"
  245. + android:title="@string/option_desktop_flag"
  246. + android:summaryOn="@string/option_desktop_flag_on"
  247. + android:summaryOff="@string/option_desktop_flag_off" />
  248. +
  249. +</PreferenceScreen>
  250. diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java
  251. --- a/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java
  252. +++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java
  253. @@ -48,6 +48,7 @@ import org.chromium.content_public.browser.DeviceUtils;
  254. import org.chromium.content_public.browser.SpeechRecognition;
  255. import org.chromium.content_public.browser.UiThreadTaskTraits;
  256. import org.chromium.net.NetworkChangeNotifier;
  257. +import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
  258. import java.io.File;
  259. import java.util.ArrayList;
  260. @@ -313,11 +314,13 @@ public class ChromeBrowserInitializer {
  261. @Override
  262. public void onSuccess() {
  263. + PrivacyPreferencesManagerImpl.getInstance().updateOverrideUserAgent();
  264. tasks.start(false);
  265. }
  266. });
  267. } else {
  268. startChromeBrowserProcessesSync();
  269. + PrivacyPreferencesManagerImpl.getInstance().updateOverrideUserAgent();
  270. tasks.start(true);
  271. }
  272. }
  273. diff --git a/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManagerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManagerImpl.java
  274. --- a/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManagerImpl.java
  275. +++ b/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManagerImpl.java
  276. @@ -191,10 +191,52 @@ public class PrivacyPreferencesManagerImpl implements PrivacyPreferencesManager
  277. PrivacyPreferencesManagerImplJni.get().setMetricsReportingEnabled(enabled);
  278. }
  279. + @Override
  280. + public void updateOverrideUserAgent() {
  281. + PrivacyPreferencesManagerImplJni.get().updateOverrideUserAgent();
  282. + }
  283. +
  284. + @Override
  285. + public boolean isOverrideUserAgentEnabled(boolean desktopMode) {
  286. + return PrivacyPreferencesManagerImplJni.get().isOverrideUserAgentEnabled(desktopMode);
  287. + }
  288. +
  289. + @Override
  290. + public void setOverrideUserAgentEnabled(boolean enabled, boolean desktopMode) {
  291. + PrivacyPreferencesManagerImplJni.get().setOverrideUserAgentEnabled(enabled, desktopMode);
  292. + }
  293. +
  294. + @Override
  295. + public String getOverrideUserAgentValue(boolean desktopMode) {
  296. + return PrivacyPreferencesManagerImplJni.get().getOverrideUserAgentValue(desktopMode);
  297. + }
  298. +
  299. + @Override
  300. + public void setOverrideUserAgentValue(String user_agent, boolean desktopMode) {
  301. + PrivacyPreferencesManagerImplJni.get().setOverrideUserAgentValue(user_agent, desktopMode);
  302. + }
  303. +
  304. + @Override
  305. + public boolean isDesktopModeViewportMetaEnabled() {
  306. + return PrivacyPreferencesManagerImplJni.get().isDesktopModeViewportMetaEnabled();
  307. + }
  308. +
  309. + @Override
  310. + public void setDesktopModeViewportMetaEnabled(boolean enabled) {
  311. + PrivacyPreferencesManagerImplJni.get().setDesktopModeViewportMetaEnabled(enabled);
  312. + }
  313. +
  314. @NativeMethods
  315. public interface Natives {
  316. boolean isMetricsReportingEnabled();
  317. void setMetricsReportingEnabled(boolean enabled);
  318. boolean isMetricsReportingDisabledByPolicy();
  319. + void updateOverrideUserAgent();
  320. + boolean isOverrideUserAgentEnabled(boolean desktopMode);
  321. + void setOverrideUserAgentEnabled(boolean enabled, boolean desktopMode);
  322. + String getOverrideUserAgentValue(boolean desktopMode);
  323. + void setOverrideUserAgentValue(String timezone, boolean desktopMode);
  324. + boolean isDesktopModeViewportMetaEnabled();
  325. + void setDesktopModeViewportMetaEnabled(boolean enabled);
  326. }
  327. }
  328. diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/UserAgentPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/UserAgentPreferences.java
  329. new file mode 100644
  330. --- /dev/null
  331. +++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/UserAgentPreferences.java
  332. @@ -0,0 +1,188 @@
  333. +/*
  334. + This file is part of Bromite.
  335. +
  336. + Bromite is free software: you can redistribute it and/or modify
  337. + it under the terms of the GNU General Public License as published by
  338. + the Free Software Foundation, either version 3 of the License, or
  339. + (at your option) any later version.
  340. +
  341. + Bromite is distributed in the hope that it will be useful,
  342. + but WITHOUT ANY WARRANTY; without even the implied warranty of
  343. + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  344. + GNU General Public License for more details.
  345. +
  346. + You should have received a copy of the GNU General Public License
  347. + along with Bromite. If not, see <https://www.gnu.org/licenses/>.
  348. +*/
  349. +
  350. +package org.chromium.chrome.browser.settings;
  351. +
  352. +import android.os.Bundle;
  353. +import androidx.preference.Preference;
  354. +import androidx.preference.PreferenceFragmentCompat;
  355. +import androidx.preference.PreferenceViewHolder;
  356. +import androidx.annotation.NonNull;
  357. +import androidx.annotation.Nullable;
  358. +import android.view.LayoutInflater;
  359. +import android.widget.RadioGroup;
  360. +import android.content.Context;
  361. +import android.util.AttributeSet;
  362. +import android.view.View;
  363. +import android.view.ViewGroup;
  364. +import android.widget.LinearLayout;
  365. +import android.widget.ScrollView;
  366. +import android.widget.CheckBox;
  367. +import android.widget.CompoundButton;
  368. +import androidx.recyclerview.widget.RecyclerView;
  369. +
  370. +import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
  371. +import org.chromium.components.browser_ui.widget.RadioButtonWithDescription;
  372. +import org.chromium.components.browser_ui.widget.RadioButtonWithEditText;
  373. +import org.chromium.components.browser_ui.settings.SettingsUtils;
  374. +
  375. +import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
  376. +import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
  377. +
  378. +import org.chromium.chrome.browser.app.tabmodel.TabWindowManagerSingleton;
  379. +import org.chromium.chrome.browser.tabmodel.TabWindowManager;
  380. +import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
  381. +import org.chromium.chrome.R;
  382. +
  383. +/**
  384. + * Fragment that allows the user to configure User Agent related preferences.
  385. + */
  386. +public class UserAgentPreferences
  387. + extends PreferenceFragmentCompat implements RadioGroup.OnCheckedChangeListener {
  388. +
  389. + private static final String PREF_STICK_DESKTOP_MODE_SWITCH = "desktop_mode_switch";
  390. + private RadioButtonWithDescription useDefaultAgentSwitch;
  391. + private RadioButtonWithEditText useCustomAgentSwitch;
  392. + private RadioButtonWithDescription useDefaultAgentSwitchDesktopMode;
  393. + private RadioButtonWithEditText useCustomAgentSwitchDesktopMode;
  394. + private RadioGroup mRadioGroup;
  395. + private RadioGroup mRadioGroupDesktopMode;
  396. + private CheckBox mDesktopModeViewportmeta;
  397. +
  398. + @Override
  399. + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
  400. + getActivity().setTitle(R.string.useragent_settings_title);
  401. + SettingsUtils.addPreferencesFromResource(this, R.xml.useragent_preferences);
  402. +
  403. + ChromeSwitchPreference alwaysDesktopModeSwitch =
  404. + (ChromeSwitchPreference) findPreference(PREF_STICK_DESKTOP_MODE_SWITCH);
  405. + boolean enabled = SharedPreferencesManager.getInstance().readBoolean(
  406. + ChromePreferenceKeys.USERAGENT_STICKY_DESKTOP_MODE, false);
  407. + alwaysDesktopModeSwitch.setChecked(enabled);
  408. + alwaysDesktopModeSwitch.setOnPreferenceChangeListener((preference, newValue) -> {
  409. + SharedPreferencesManager.getInstance().writeBoolean(
  410. + ChromePreferenceKeys.USERAGENT_STICKY_DESKTOP_MODE, (boolean) newValue);
  411. + UpdateAllTabs();
  412. + return true;
  413. + });
  414. + }
  415. +
  416. + @Override
  417. + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
  418. + @Nullable Bundle savedInstanceState) {
  419. + LinearLayout viewGroup = (LinearLayout) super.onCreateView(inflater, container, savedInstanceState);
  420. + LinearLayout.LayoutParams params =
  421. + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
  422. + viewGroup.setLayoutParams(params);
  423. + ScrollView view = (ScrollView) inflater.inflate(R.layout.custom_useragent_preferences, viewGroup, false);
  424. + viewGroup.addView(view);
  425. +
  426. + boolean enabledCustomUA = PrivacyPreferencesManagerImpl.getInstance().isOverrideUserAgentEnabled(false);
  427. + boolean enabledCustomUADesktopMode = PrivacyPreferencesManagerImpl.getInstance().isOverrideUserAgentEnabled(true);
  428. + boolean enabledDesktopModeViewportmeta = PrivacyPreferencesManagerImpl.getInstance().isDesktopModeViewportMetaEnabled();
  429. +
  430. + useDefaultAgentSwitch =
  431. + (RadioButtonWithDescription) view.findViewById(R.id.default_ua_switch);
  432. + useCustomAgentSwitch =
  433. + (RadioButtonWithEditText) view.findViewById(R.id.custom_ua_switch);
  434. + useDefaultAgentSwitchDesktopMode =
  435. + (RadioButtonWithDescription) view.findViewById(R.id.default_ua_switch_dm);
  436. + useCustomAgentSwitchDesktopMode =
  437. + (RadioButtonWithEditText) view.findViewById(R.id.custom_ua_switch_dm);
  438. +
  439. + mRadioGroup = (RadioGroup) view.findViewById(R.id.ua_radio_button_layout);
  440. + mRadioGroup.setOnCheckedChangeListener(this);
  441. +
  442. + mRadioGroupDesktopMode = (RadioGroup) view.findViewById(R.id.ua_radio_button_layout_dm);
  443. + mRadioGroupDesktopMode.setOnCheckedChangeListener(this);
  444. +
  445. + mDesktopModeViewportmeta = (CheckBox) view.findViewById(R.id.desktop_mode_viewportmeta);
  446. + mDesktopModeViewportmeta.setChecked(enabledDesktopModeViewportmeta);
  447. + mDesktopModeViewportmeta.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
  448. + @Override
  449. + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
  450. + PrivacyPreferencesManagerImpl.getInstance().setDesktopModeViewportMetaEnabled(
  451. + mDesktopModeViewportmeta.isChecked());
  452. + }
  453. + });
  454. +
  455. + useDefaultAgentSwitch.setChecked(!enabledCustomUA);
  456. + useCustomAgentSwitch.setChecked(enabledCustomUA);
  457. +
  458. + useDefaultAgentSwitchDesktopMode.setChecked(!enabledCustomUADesktopMode);
  459. + useCustomAgentSwitchDesktopMode.setChecked(enabledCustomUADesktopMode);
  460. +
  461. + useCustomAgentSwitch.setPrimaryText(
  462. + PrivacyPreferencesManagerImpl.getInstance().getOverrideUserAgentValue(false));
  463. + useCustomAgentSwitch.addTextChangeListener(new RadioButtonWithEditText.OnTextChangeListener() {
  464. + @Override
  465. + public void onTextChanged(CharSequence newText) {
  466. + PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentValue(
  467. + newText.toString(), false);
  468. + }
  469. + });
  470. + useCustomAgentSwitch.setFocusChangeListener( hasFocus -> {
  471. + if( hasFocus )
  472. + PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentEnabled(true, false);
  473. + });
  474. +
  475. + useCustomAgentSwitchDesktopMode.setPrimaryText(
  476. + PrivacyPreferencesManagerImpl.getInstance().getOverrideUserAgentValue(true));
  477. + useCustomAgentSwitchDesktopMode.addTextChangeListener(new RadioButtonWithEditText.OnTextChangeListener() {
  478. + @Override
  479. + public void onTextChanged(CharSequence newText) {
  480. + PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentValue(
  481. + newText.toString(), true);
  482. + }
  483. + });
  484. + useCustomAgentSwitchDesktopMode.setFocusChangeListener( hasFocus -> {
  485. + if( hasFocus )
  486. + PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentEnabled(true, true);
  487. + });
  488. +
  489. + return viewGroup;
  490. + }
  491. +
  492. + private void UpdateAllTabs() {
  493. + final boolean alwaysDesktopModeEnabled = SharedPreferencesManager.getInstance().readBoolean(
  494. + ChromePreferenceKeys.USERAGENT_ALWAYS_DESKTOP_MODE, false);
  495. + TabWindowManagerSingleton.getInstance().SetOverrideUserAgentForAllTabs(alwaysDesktopModeEnabled);
  496. + }
  497. +
  498. + @Override
  499. + public void onCheckedChanged(RadioGroup group, int checkedId) {
  500. + if (useDefaultAgentSwitch.isChecked()) {
  501. + PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentEnabled(false, false);
  502. + } else if (useCustomAgentSwitch.isChecked()) {
  503. + PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentEnabled(true, false);
  504. + }
  505. +
  506. + if (useDefaultAgentSwitchDesktopMode.isChecked()) {
  507. + PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentEnabled(false, true);
  508. + } else if (useCustomAgentSwitchDesktopMode.isChecked()) {
  509. + PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentEnabled(true, true);
  510. + }
  511. +
  512. + UpdateAllTabs();
  513. + }
  514. +
  515. + @Override
  516. + public void onStop() {
  517. + super.onStop();
  518. + UpdateAllTabs();
  519. + }
  520. +}
  521. diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
  522. --- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
  523. +++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
  524. @@ -26,7 +26,11 @@ import org.chromium.base.annotations.CalledByNative;
  525. import org.chromium.base.annotations.NativeMethods;
  526. import org.chromium.base.metrics.RecordHistogram;
  527. import org.chromium.base.metrics.RecordUserAction;
  528. +import org.chromium.content_public.browser.NavigationController;
  529. import org.chromium.base.supplier.ObservableSupplierImpl;
  530. +import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
  531. +import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
  532. +import org.chromium.components.embedder_support.util.UrlUtilities;
  533. import org.chromium.base.supplier.Supplier;
  534. import org.chromium.chrome.R;
  535. import org.chromium.chrome.browser.ActivityUtils;
  536. @@ -513,6 +517,31 @@ public class TabImpl implements Tab, TabObscuringHandler.Observer {
  537. // Request desktop sites for large screen tablets if necessary.
  538. params.setOverrideUserAgent(calculateUserAgentOverrideOption());
  539. + final boolean stickyDesktopModeEnabled = SharedPreferencesManager.getInstance().readBoolean(
  540. + ChromePreferenceKeys.USERAGENT_STICKY_DESKTOP_MODE, false);
  541. + if (stickyDesktopModeEnabled) {
  542. + boolean alwaysDesktopModeEnabled = SharedPreferencesManager.getInstance().readBoolean(
  543. + ChromePreferenceKeys.USERAGENT_ALWAYS_DESKTOP_MODE, false);
  544. +
  545. + if (UrlUtilities.isInternalScheme(UrlFormatter.fixupUrl(params.getUrl()))) {
  546. + alwaysDesktopModeEnabled = false;
  547. + }
  548. +
  549. + WebContents webContents = this.getWebContents();
  550. + if (webContents != null) {
  551. + NavigationController navigationController = webContents.getNavigationController();
  552. + boolean currentUseDesktopUserAgent = navigationController.getUseDesktopUserAgent();
  553. + if (currentUseDesktopUserAgent != alwaysDesktopModeEnabled)
  554. + navigationController.setUseDesktopUserAgent(alwaysDesktopModeEnabled, false);
  555. + }
  556. +
  557. + if (alwaysDesktopModeEnabled) {
  558. + params.setOverrideUserAgent((int)UserAgentOverrideOption.TRUE);
  559. + } else {
  560. + params.setOverrideUserAgent((int)UserAgentOverrideOption.FALSE);
  561. + }
  562. + }
  563. +
  564. @TabLoadStatus
  565. int result = loadUrlInternal(params);
  566. @@ -1549,6 +1578,10 @@ public class TabImpl implements Tab, TabObscuringHandler.Observer {
  567. if (mWebContents != null) mWebContents.getNavigationController().loadIfNecessary();
  568. mIsBeingRestored = true;
  569. for (TabObserver observer : mObservers) observer.onRestoreStarted(this);
  570. + if(overrideUserAgentWhenUnFrozen != UserAgentOverrideOption.INHERIT) {
  571. + SetOverrideUserAgent(overrideUserAgentWhenUnFrozen == (int)UserAgentOverrideOption.TRUE ? true : false,
  572. + /*forcedByUser*/ true);
  573. + }
  574. } finally {
  575. TraceEvent.end("Tab.restoreIfNeeded");
  576. }
  577. @@ -1751,6 +1784,43 @@ public class TabImpl implements Tab, TabObscuringHandler.Observer {
  578. /* forcedByUser */ false);
  579. }
  580. + int overrideUserAgentWhenUnFrozen = (int)UserAgentOverrideOption.INHERIT;
  581. +
  582. + public void SetOverrideUserAgent(boolean usingDesktopUserAgent, boolean forcedByUser) {
  583. + WebContents webContents = this.getWebContents();
  584. + overrideUserAgentWhenUnFrozen = UserAgentOverrideOption.INHERIT;
  585. +
  586. + if (usingDesktopUserAgent) {
  587. + GURL url = this.getUrl();
  588. + if (webContents == null && this.getPendingLoadParams() != null) {
  589. + url = UrlFormatter.fixupUrl(this.getPendingLoadParams().getUrl());
  590. + }
  591. + if (UrlUtilities.isInternalScheme(url) == true)
  592. + usingDesktopUserAgent = false;
  593. + }
  594. +
  595. + if (webContents != null) {
  596. + ContentUtils.setUserAgentOverride(webContents, /*forcedByUser*/ true);
  597. +
  598. + NavigationController navigationController = webContents.getNavigationController();
  599. + navigationController.setUseDesktopUserAgent(
  600. + usingDesktopUserAgent, !this.isNativePage());
  601. + if (forcedByUser) CriticalPersistedTabData.from(this).setUserAgent(TabUserAgent.DESKTOP);
  602. + }
  603. + else if (this.getPendingLoadParams() != null) {
  604. + if (usingDesktopUserAgent) {
  605. + this.getPendingLoadParams().setOverrideUserAgent((int)UserAgentOverrideOption.TRUE);
  606. + }
  607. + else {
  608. + this.getPendingLoadParams().setOverrideUserAgent((int)UserAgentOverrideOption.FALSE);
  609. + }
  610. + }
  611. + else {
  612. + overrideUserAgentWhenUnFrozen = usingDesktopUserAgent ? UserAgentOverrideOption.TRUE :
  613. + UserAgentOverrideOption.FALSE;
  614. + }
  615. + }
  616. +
  617. @NativeMethods
  618. interface Natives {
  619. TabImpl fromWebContents(WebContents webContents);
  620. diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabUtils.java
  621. --- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabUtils.java
  622. +++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabUtils.java
  623. @@ -31,6 +31,10 @@ import org.chromium.ui.base.WindowAndroid;
  624. import org.chromium.ui.display.DisplayAndroidManager;
  625. import org.chromium.url.GURL;
  626. +import org.chromium.chrome.browser.app.tabmodel.TabWindowManagerSingleton;
  627. +import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
  628. +import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
  629. +
  630. /**
  631. * Collection of utility methods that operates on Tab.
  632. */
  633. @@ -93,18 +97,15 @@ public class TabUtils {
  634. * @param forcedByUser Whether this was triggered by users action.
  635. */
  636. public static void switchUserAgent(Tab tab, boolean switchToDesktop, boolean forcedByUser) {
  637. - final boolean reloadOnChange = !tab.isNativePage();
  638. - tab.getWebContents().getNavigationController().setUseDesktopUserAgent(
  639. - switchToDesktop, reloadOnChange);
  640. - if (forcedByUser) {
  641. - @TabUserAgent
  642. - int tabUserAgent = switchToDesktop ? TabUserAgent.DESKTOP : TabUserAgent.MOBILE;
  643. - if (ContentFeatureList.isEnabled(ContentFeatureList.REQUEST_DESKTOP_SITE_GLOBAL)
  644. - && isDesktopSiteGlobalEnabled(Profile.fromWebContents(tab.getWebContents()))
  645. - == switchToDesktop) {
  646. - tabUserAgent = TabUserAgent.DEFAULT;
  647. - }
  648. - CriticalPersistedTabData.from(tab).setUserAgent(tabUserAgent);
  649. + SharedPreferencesManager.getInstance().writeBoolean(
  650. + ChromePreferenceKeys.USERAGENT_ALWAYS_DESKTOP_MODE, switchToDesktop);
  651. +
  652. + final boolean stickyDesktopModeEnabled = SharedPreferencesManager.getInstance().readBoolean(
  653. + ChromePreferenceKeys.USERAGENT_STICKY_DESKTOP_MODE, false);
  654. + if (stickyDesktopModeEnabled) {
  655. + TabWindowManagerSingleton.getInstance().SetOverrideUserAgentForAllTabs(switchToDesktop);
  656. + } else {
  657. + tab.SetOverrideUserAgent(switchToDesktop, forcedByUser);
  658. }
  659. }
  660. diff --git a/chrome/browser/android/content/content_utils.cc b/chrome/browser/android/content/content_utils.cc
  661. --- a/chrome/browser/android/content/content_utils.cc
  662. +++ b/chrome/browser/android/content/content_utils.cc
  663. @@ -8,6 +8,20 @@
  664. #include "components/version_info/version_info.h"
  665. #include "content/public/browser/web_contents.h"
  666. +#include "base/android/jni_android.h"
  667. +#include "base/android/scoped_java_ref.h"
  668. +#include "chrome/browser/browser_process.h"
  669. +#include "components/prefs/pref_service.h"
  670. +#include "chrome/common/pref_names.h"
  671. +
  672. +using base::android::ConvertJavaStringToUTF8;
  673. +using base::android::ConvertUTF16ToJavaString;
  674. +using base::android::ConvertUTF8ToJavaString;
  675. +using base::android::JavaParamRef;
  676. +using base::android::JavaRef;
  677. +using base::android::ScopedJavaGlobalRef;
  678. +using base::android::ScopedJavaLocalRef;
  679. +
  680. static base::android::ScopedJavaLocalRef<jstring>
  681. JNI_ContentUtils_GetBrowserUserAgent(JNIEnv* env) {
  682. return base::android::ConvertUTF8ToJavaString(
  683. @@ -18,6 +32,20 @@ static void JNI_ContentUtils_SetUserAgentOverride(
  684. JNIEnv* env,
  685. const base::android::JavaParamRef<jobject>& jweb_contents,
  686. jboolean j_override_in_new_tabs) {
  687. + bool enabled =
  688. + g_browser_process->local_state()->GetBoolean(prefs::kOverrideUserAgentDesktopModeEnabled);
  689. +
  690. + if (enabled == true) {
  691. + std::string ua = g_browser_process->local_state()->GetString(prefs::kOverrideUserAgentDesktopMode);
  692. + blink::UserAgentOverride spoofed_ua;
  693. + spoofed_ua.ua_string_override = ua;
  694. +
  695. + content::WebContents* web_contents =
  696. + content::WebContents::FromJavaWebContents(jweb_contents);
  697. + web_contents->SetUserAgentOverride(spoofed_ua, false);
  698. + return;
  699. + }
  700. +
  701. content::WebContents* web_contents =
  702. content::WebContents::FromJavaWebContents(jweb_contents);
  703. embedder_support::SetDesktopUserAgentOverride(
  704. diff --git a/chrome/browser/android/preferences/browser_prefs_android.cc b/chrome/browser/android/preferences/browser_prefs_android.cc
  705. --- a/chrome/browser/android/preferences/browser_prefs_android.cc
  706. +++ b/chrome/browser/android/preferences/browser_prefs_android.cc
  707. @@ -10,12 +10,19 @@
  708. #include "chrome/browser/webauthn/android/cable_module_android.h"
  709. #include "components/pref_registry/pref_registry_syncable.h"
  710. #include "components/prefs/pref_registry_simple.h"
  711. +#include "chrome/common/pref_names.h"
  712. namespace android {
  713. void RegisterPrefs(PrefRegistrySimple* registry) {
  714. RegisterClipboardAndroidPrefs(registry);
  715. webauthn::authenticator::RegisterLocalState(registry);
  716. +
  717. + registry->RegisterBooleanPref(prefs::kOverrideUserAgentEnabled, false);
  718. + registry->RegisterStringPref(prefs::kOverrideUserAgent, "");
  719. + registry->RegisterBooleanPref(prefs::kOverrideUserAgentDesktopModeEnabled, false);
  720. + registry->RegisterStringPref(prefs::kOverrideUserAgentDesktopMode, "");
  721. + registry->RegisterBooleanPref(prefs::kDesktopModeViewportMetaEnabled, false);
  722. }
  723. void RegisterUserProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
  724. diff --git a/chrome/browser/android/preferences/privacy_preferences_manager_impl.cc b/chrome/browser/android/preferences/privacy_preferences_manager_impl.cc
  725. --- a/chrome/browser/android/preferences/privacy_preferences_manager_impl.cc
  726. +++ b/chrome/browser/android/preferences/privacy_preferences_manager_impl.cc
  727. @@ -14,6 +14,30 @@
  728. #include "components/policy/core/common/features.h"
  729. #include "components/prefs/pref_service.h"
  730. +#include "base/command_line.h"
  731. +#include "base/base_switches.h"
  732. +#include "chrome/common/chrome_switches.h"
  733. +#include "content/browser/renderer_host/render_process_host_impl.h"
  734. +#include "content/common/renderer.mojom.h"
  735. +#include "chrome/browser/chrome_content_browser_client.h"
  736. +
  737. +#include "components/embedder_support/content_settings_utils.h"
  738. +#include "components/embedder_support/switches.h"
  739. +#include "components/embedder_support/user_agent_utils.h"
  740. +
  741. +#include "base/android/jni_android.h"
  742. +#include "base/android/jni_array.h"
  743. +#include "base/android/jni_string.h"
  744. +#include "base/android/scoped_java_ref.h"
  745. +
  746. +using base::android::ConvertJavaStringToUTF8;
  747. +using base::android::ConvertUTF16ToJavaString;
  748. +using base::android::ConvertUTF8ToJavaString;
  749. +using base::android::JavaParamRef;
  750. +using base::android::JavaRef;
  751. +using base::android::ScopedJavaGlobalRef;
  752. +using base::android::ScopedJavaLocalRef;
  753. +
  754. static jboolean JNI_PrivacyPreferencesManagerImpl_IsMetricsReportingEnabled(
  755. JNIEnv* env) {
  756. PrefService* local_state = g_browser_process->local_state();
  757. @@ -41,3 +65,100 @@ JNI_PrivacyPreferencesManagerImpl_IsMetricsReportingDisabledByPolicy(
  758. metrics::prefs::kMetricsReportingEnabled) &&
  759. !local_state->GetBoolean(metrics::prefs::kMetricsReportingEnabled);
  760. }
  761. +
  762. +static void UpdateOverrideUserAgent() {
  763. + bool overrideUserAgentEnabled =
  764. + g_browser_process->local_state()->GetBoolean(prefs::kOverrideUserAgentEnabled);
  765. + std::string ua = g_browser_process->local_state()->GetString(prefs::kOverrideUserAgent);
  766. + if (ua.empty()) {
  767. + ua = ChromeContentBrowserClient().GetUserAgent();
  768. + }
  769. +
  770. + base::CommandLine* parsed_command_line =
  771. + base::CommandLine::ForCurrentProcess();
  772. + parsed_command_line->RemoveSwitch(embedder_support::kUserAgent);
  773. + if (!ua.empty()) {
  774. + if (overrideUserAgentEnabled) {
  775. + parsed_command_line->AppendSwitchASCII(embedder_support::kUserAgent, ua);
  776. + }
  777. +
  778. + for (auto iter = content::RenderProcessHost::AllHostsIterator(); !iter.IsAtEnd();
  779. + iter.Advance()) {
  780. + if (iter.GetCurrentValue()->IsInitializedAndNotDead()) {
  781. + std::vector<std::string> cors_exempt_header_list;
  782. + iter.GetCurrentValue()->GetRendererInterface()->InitializeRenderer(
  783. + /*user_agent*/ ua, /*full_user_agent*/ ua, /*reduced_user_agent*/ ua,
  784. + /*metadata*/ blink::UserAgentMetadata(), cors_exempt_header_list);
  785. + }
  786. + }
  787. + }
  788. +
  789. + parsed_command_line->RemoveSwitch(switches::kDesktopModeViewportMetaEnabled);
  790. + if (g_browser_process->local_state()->GetBoolean(prefs::kDesktopModeViewportMetaEnabled))
  791. + parsed_command_line->AppendSwitch(switches::kDesktopModeViewportMetaEnabled);
  792. +}
  793. +
  794. +static void JNI_PrivacyPreferencesManagerImpl_UpdateOverrideUserAgent(
  795. + JNIEnv* env) {
  796. + UpdateOverrideUserAgent();
  797. +}
  798. +
  799. +static jboolean JNI_PrivacyPreferencesManagerImpl_IsOverrideUserAgentEnabled(
  800. + JNIEnv* env, jboolean desktopMode) {
  801. + if (desktopMode == false)
  802. + return g_browser_process->local_state()->GetBoolean(prefs::kOverrideUserAgentEnabled);
  803. + else
  804. + return g_browser_process->local_state()->GetBoolean(prefs::kOverrideUserAgentDesktopModeEnabled);
  805. +}
  806. +
  807. +static void JNI_PrivacyPreferencesManagerImpl_SetOverrideUserAgentEnabled(
  808. + JNIEnv* env,
  809. + jboolean enabled, jboolean desktopMode) {
  810. + if (desktopMode == false) {
  811. + g_browser_process->local_state()->SetBoolean(prefs::kOverrideUserAgentEnabled,
  812. + enabled);
  813. + UpdateOverrideUserAgent();
  814. + } else {
  815. + g_browser_process->local_state()->SetBoolean(prefs::kOverrideUserAgentDesktopModeEnabled,
  816. + enabled);
  817. + }
  818. +}
  819. +
  820. +static void JNI_PrivacyPreferencesManagerImpl_SetOverrideUserAgentValue(
  821. + JNIEnv* env,
  822. + const JavaParamRef<jstring>& ua, jboolean desktopMode) {
  823. + std::string new_ua = ConvertJavaStringToUTF8(env, ua);
  824. + if (desktopMode == false) {
  825. + g_browser_process->local_state()->SetString(prefs::kOverrideUserAgent,
  826. + new_ua);
  827. + UpdateOverrideUserAgent();
  828. + } else {
  829. + g_browser_process->local_state()->SetString(prefs::kOverrideUserAgentDesktopMode,
  830. + new_ua);
  831. + }
  832. +}
  833. +
  834. +static base::android::ScopedJavaLocalRef<jstring>
  835. + JNI_PrivacyPreferencesManagerImpl_GetOverrideUserAgentValue(
  836. + JNIEnv* env, jboolean desktopMode) {
  837. + if (desktopMode == false) {
  838. + std::string ua = g_browser_process->local_state()->GetString(prefs::kOverrideUserAgent);
  839. + return ConvertUTF8ToJavaString(env, ua);
  840. + } else {
  841. + std::string ua = g_browser_process->local_state()->GetString(prefs::kOverrideUserAgentDesktopMode);
  842. + return ConvertUTF8ToJavaString(env, ua);
  843. + }
  844. +}
  845. +
  846. +static jboolean JNI_PrivacyPreferencesManagerImpl_IsDesktopModeViewportMetaEnabled(
  847. + JNIEnv* env) {
  848. + return g_browser_process->local_state()->GetBoolean(prefs::kDesktopModeViewportMetaEnabled);
  849. +}
  850. +
  851. +static void JNI_PrivacyPreferencesManagerImpl_SetDesktopModeViewportMetaEnabled(
  852. + JNIEnv* env,
  853. + jboolean enabled) {
  854. + g_browser_process->local_state()->SetBoolean(prefs::kDesktopModeViewportMetaEnabled,
  855. + enabled);
  856. + UpdateOverrideUserAgent();
  857. +}
  858. diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
  859. --- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
  860. +++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
  861. @@ -1018,6 +1018,9 @@ public final class ChromePreferenceKeys {
  862. public static final String BLUETOOTH_NOTIFICATION_IDS = "Chrome.Bluetooth.NotificationIds";
  863. + public static final String USERAGENT_STICKY_DESKTOP_MODE = "Chrome.UserAgent.StickyDesktopMode";
  864. + public static final String USERAGENT_ALWAYS_DESKTOP_MODE = "Chrome.UserAgent.AlwaysDesktopMode";
  865. +
  866. /**
  867. * These values are currently used as SharedPreferences keys, along with the keys in
  868. * {@link LegacyChromePreferenceKeys#getKeysInUse()}. Add new SharedPreferences keys
  869. @@ -1147,6 +1150,8 @@ public final class ChromePreferenceKeys {
  870. TAP_FEED_CARDS_COUNT,
  871. TAP_MV_TILES_COUNT,
  872. TWA_DISCLOSURE_SEEN_PACKAGES,
  873. + USERAGENT_STICKY_DESKTOP_MODE,
  874. + USERAGENT_ALWAYS_DESKTOP_MODE,
  875. VIDEO_TUTORIALS_SHARE_URL_SET,
  876. WEB_FEED_INTRO_LAST_SHOWN_TIME_MS,
  877. WEB_FEED_INTRO_WEB_FEED_ID_SHOWN_TIME_MS_PREFIX.pattern(),
  878. diff --git a/chrome/browser/privacy/settings/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManager.java b/chrome/browser/privacy/settings/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManager.java
  879. --- a/chrome/browser/privacy/settings/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManager.java
  880. +++ b/chrome/browser/privacy/settings/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManager.java
  881. @@ -81,6 +81,14 @@ public interface PrivacyPreferencesManager extends CrashReportingPermissionManag
  882. */
  883. boolean isMetricsReportingEnabled();
  884. + void updateOverrideUserAgent();
  885. + boolean isOverrideUserAgentEnabled(boolean desktopMode);
  886. + void setOverrideUserAgentEnabled(boolean enabled, boolean desktopMode);
  887. + String getOverrideUserAgentValue(boolean desktopMode);
  888. + void setOverrideUserAgentValue(String timezone, boolean desktopMode);
  889. + boolean isDesktopModeViewportMetaEnabled();
  890. + void setDesktopModeViewportMetaEnabled(boolean enabled);
  891. +
  892. /**
  893. * Sets whether the usage and crash reporting pref should be enabled.
  894. */
  895. diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java
  896. --- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java
  897. +++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java
  898. @@ -279,6 +279,8 @@ public interface Tab extends TabLifecycle {
  899. */
  900. void setIsTabSaveEnabled(boolean isSaveEnabled);
  901. + void SetOverrideUserAgent(boolean usingDesktopUserAgent, boolean forcedByUser);
  902. +
  903. /**
  904. * @return true if the {@link Tab} is a custom tab.
  905. */
  906. diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManager.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManager.java
  907. --- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManager.java
  908. +++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManager.java
  909. @@ -89,6 +89,8 @@ public interface TabWindowManager {
  910. */
  911. Tab getTabById(int tabId);
  912. + void SetOverrideUserAgentForAllTabs(boolean usingDesktopUserAgent);
  913. +
  914. /**
  915. * Finds the {@link TabModelSelector} bound to an Activity instance of a given index.
  916. * @param index The index of {@link TabModelSelector} to get.
  917. diff --git a/chrome/browser/tabmodel/internal/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerImpl.java b/chrome/browser/tabmodel/internal/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerImpl.java
  918. --- a/chrome/browser/tabmodel/internal/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerImpl.java
  919. +++ b/chrome/browser/tabmodel/internal/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerImpl.java
  920. @@ -133,6 +133,24 @@ public class TabWindowManagerImpl implements ActivityStateListener, TabWindowMan
  921. return null;
  922. }
  923. + @Override
  924. + public void SetOverrideUserAgentForAllTabs(boolean usingDesktopUserAgent) {
  925. + for (int selectorIndex = 0; selectorIndex < mSelectors.size(); selectorIndex++) {
  926. + TabModelSelector selector = mSelectors.get(selectorIndex);
  927. + if (selector != null) {
  928. + List<TabModel> models = selector.getModels();
  929. + for (int modelIndex = 0; modelIndex < models.size(); modelIndex++) {
  930. + TabModel model = models.get(modelIndex);
  931. +
  932. + for (int tabIdex = 0; tabIdex < model.getCount(); tabIdex++) {
  933. + Tab theTab = model.getTabAt(tabIdex);
  934. + theTab.SetOverrideUserAgent(usingDesktopUserAgent, /*forcedByUser*/ true);
  935. + }
  936. + }
  937. + }
  938. + }
  939. + }
  940. +
  941. @Override
  942. public Tab getTabById(int tabId) {
  943. for (int i = 0; i < mSelectors.size(); i++) {
  944. diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
  945. --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
  946. +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
  947. @@ -229,6 +229,41 @@ CHAR_LIMIT guidelines:
  948. Visit help page
  949. </message>
  950. + <!-- User Agent settings -->
  951. + <message name="IDS_PREFS_USERAGENT_SETTINGS" desc="Title of the User Agent preference. [CHAR-LIMIT=32]">
  952. + User Agent
  953. + </message>
  954. + <message name="IDS_USERAGENT_SETTINGS_TITLE" desc="Title of the User Agent screen. [CHAR-LIMIT=32]">
  955. + Customize User Agent
  956. + </message>
  957. + <message name="IDS_OPTION_DESKTOP_FLAG" desc="The label of the option that allows users to sticky desktop mode view flag under hambuger menu.">
  958. + Current behaviour for desktop mode toggle in hamburger menu
  959. + </message>
  960. + <message name="IDS_OPTION_DESKTOP_FLAG_ON" desc="The label of the option that allows users to sticky desktop mode view flag under hambuger menu. [CHAR-LIMIT=32]">
  961. + Applies to all tabs (sticky mode)
  962. + </message>
  963. + <message name="IDS_OPTION_DESKTOP_FLAG_OFF" desc="The label of the option that revert the hambuger menu flag to actual behaviour. [CHAR-LIMIT=32]">
  964. + Applies to current tab only
  965. + </message>
  966. + <message name="IDS_CUSTOM_UA_FLAG_ON" desc="The label of the option that allows users to define custom user agent.">
  967. + Use custom user agent
  968. + </message>
  969. + <message name="IDS_CUSTOM_UA_FLAG_OFF" desc="The label of the option that revert the user agent to actual value.">
  970. + Use standard user agent
  971. + </message>
  972. + <message name="IDS_CUSTOM_UA_PLACEHOLDER" desc="The label of the placeholder for user agent textbox.">
  973. + Insert a valid user agent
  974. + </message>
  975. + <message name="IDS_CUSTOM_UA_TEXT" desc="The label of the placeholder for user agent textbox.">
  976. + Mobile User Agent
  977. + </message>
  978. + <message name="IDS_CUSTOM_DESKTOP_UA_TEXT" desc="The label of the placeholder for user agent textbox.">
  979. + Desktop Mode User Agent
  980. + </message>
  981. + <message name="IDS_DESKTOP_MODE_VIEWPORTMETA_CHECKBOX" desc="The label of the enable viewport meta checkbox for user desktop mode.">
  982. + Enable processing of the viewport meta tag also for desktop mode
  983. + </message>
  984. +
  985. <!-- Notification channels -->
  986. <message name="IDS_NOTIFICATION_CATEGORY_GROUP_GENERAL" desc='Subheading for "General" section of a list of notification categories. [CHAR_LIMIT=32]'>
  987. General
  988. diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
  989. --- a/chrome/common/pref_names.cc
  990. +++ b/chrome/common/pref_names.cc
  991. @@ -445,6 +445,19 @@ const char kAllowJavascriptAppleEvents[] =
  992. #endif
  993. +#if BUILDFLAG(IS_ANDROID)
  994. +const char kOverrideUserAgentEnabled[] =
  995. + "override_user_agent_enabled";
  996. +const char kOverrideUserAgent[] =
  997. + "override_user_agent";
  998. +const char kOverrideUserAgentDesktopModeEnabled[] =
  999. + "override_user_agent_dm_enabled";
  1000. +const char kOverrideUserAgentDesktopMode[] =
  1001. + "override_user_agent_dm";
  1002. +const char kDesktopModeViewportMetaEnabled[] =
  1003. + "dm-viewport-meta-enabled";
  1004. +#endif
  1005. +
  1006. // Boolean which specifies whether we should ask the user if we should download
  1007. // a file (true) or just download it automatically.
  1008. const char kPromptForDownload[] = "download.prompt_for_download";
  1009. diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
  1010. --- a/chrome/common/pref_names.h
  1011. +++ b/chrome/common/pref_names.h
  1012. @@ -1236,6 +1236,14 @@ extern const char kDesktopSharingHubEnabled[];
  1013. #if !BUILDFLAG(IS_ANDROID)
  1014. extern const char kLastWhatsNewVersion[];
  1015. #endif
  1016. +#if BUILDFLAG(IS_ANDROID)
  1017. +extern const char kOverrideUserAgentEnabled[];
  1018. +extern const char kOverrideUserAgent[];
  1019. +extern const char kOverrideUserAgentDesktopModeEnabled[];
  1020. +extern const char kOverrideUserAgentDesktopMode[];
  1021. +extern const char kDesktopModeViewportMetaEnabled[];
  1022. +#endif
  1023. +
  1024. #if !BUILDFLAG(IS_ANDROID)
  1025. extern const char kLensRegionSearchEnabled[];
  1026. diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RadioButtonWithEditText.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RadioButtonWithEditText.java
  1027. --- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RadioButtonWithEditText.java
  1028. +++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RadioButtonWithEditText.java
  1029. @@ -167,6 +167,17 @@ public class RadioButtonWithEditText extends RadioButtonWithDescription {
  1030. mEditText.setCursorVisible(false);
  1031. KeyboardVisibilityDelegate.getInstance().hideKeyboard(mEditText);
  1032. }
  1033. + if (mRadioButtonWithEditTextFocusListener != null) {
  1034. + mRadioButtonWithEditTextFocusListener.onRadioButtonWithEditTextFocusChanged(hasFocus);
  1035. + }
  1036. + }
  1037. +
  1038. + public interface RadioButtonWithEditTextFocusListener {
  1039. + void onRadioButtonWithEditTextFocusChanged(boolean hasFocus);
  1040. + }
  1041. + private RadioButtonWithEditTextFocusListener mRadioButtonWithEditTextFocusListener;
  1042. + public void setFocusChangeListener(RadioButtonWithEditTextFocusListener listener) {
  1043. + mRadioButtonWithEditTextFocusListener = listener;
  1044. }
  1045. /**
  1046. diff --git a/components/embedder_support/user_agent_utils.cc b/components/embedder_support/user_agent_utils.cc
  1047. --- a/components/embedder_support/user_agent_utils.cc
  1048. +++ b/components/embedder_support/user_agent_utils.cc
  1049. @@ -383,6 +383,13 @@ std::string GetUserAgent(
  1050. std::string GetReducedUserAgent(
  1051. ForceMajorVersionToMinorPosition force_major_to_minor) {
  1052. + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  1053. + if (command_line->HasSwitch(kUserAgent)) {
  1054. + std::string ua = command_line->GetSwitchValueASCII(kUserAgent);
  1055. + if (net::HttpUtil::IsValidHeaderValue(ua))
  1056. + return ua;
  1057. + LOG(WARNING) << "Ignored invalid value for flag --" << kUserAgent;
  1058. + }
  1059. return content::GetReducedUserAgent(
  1060. base::CommandLine::ForCurrentProcess()->HasSwitch(
  1061. switches::kUseMobileUserAgent),
  1062. @@ -563,6 +570,10 @@ blink::UserAgentMetadata GetUserAgentMetadata(const PrefService* pref_service) {
  1063. policy::policy_prefs::kUserAgentClientHintsGREASEUpdateEnabled);
  1064. ua_options.force_major_to_minor = GetMajorToMinorFromPrefs(pref_service);
  1065. }
  1066. + if (base::CommandLine::ForCurrentProcess()->HasSwitch(kUserAgent)) {
  1067. + //NOTE: metadata is not updated with custom UA information
  1068. + return metadata;
  1069. + }
  1070. metadata.brand_version_list = GetBrandMajorVersionList(
  1071. enable_updated_grease_by_policy, ua_options.force_major_to_minor);
  1072. metadata.brand_full_version_list = GetBrandFullVersionList(
  1073. diff --git a/content/browser/renderer_host/navigation_controller_android.cc b/content/browser/renderer_host/navigation_controller_android.cc
  1074. --- a/content/browser/renderer_host/navigation_controller_android.cc
  1075. +++ b/content/browser/renderer_host/navigation_controller_android.cc
  1076. @@ -245,6 +245,7 @@ void NavigationControllerAndroid::LoadUrl(
  1077. jboolean can_load_local_resources,
  1078. jboolean is_renderer_initiated,
  1079. jboolean should_replace_current_entry,
  1080. + jint user_agent_override_option,
  1081. const JavaParamRef<jobject>& j_initiator_origin,
  1082. jboolean has_user_gesture,
  1083. jboolean should_clear_history_list,
  1084. @@ -311,6 +312,9 @@ void NavigationControllerAndroid::LoadUrl(
  1085. if (input_start != 0)
  1086. params.input_start = base::TimeTicks::FromUptimeMillis(input_start);
  1087. + params.override_user_agent = static_cast<NavigationController::UserAgentOverrideOption>(
  1088. + user_agent_override_option);
  1089. +
  1090. navigation_controller_->LoadURLWithParams(params);
  1091. }
  1092. diff --git a/content/browser/renderer_host/navigation_controller_android.h b/content/browser/renderer_host/navigation_controller_android.h
  1093. --- a/content/browser/renderer_host/navigation_controller_android.h
  1094. +++ b/content/browser/renderer_host/navigation_controller_android.h
  1095. @@ -86,6 +86,7 @@ class CONTENT_EXPORT NavigationControllerAndroid {
  1096. jboolean can_load_local_resources,
  1097. jboolean is_renderer_initiated,
  1098. jboolean should_replace_current_entry,
  1099. + jint user_agent_override_option,
  1100. const base::android::JavaParamRef<jobject>& j_initiator_origin,
  1101. jboolean has_user_gesture,
  1102. jboolean should_clear_history_list,
  1103. diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
  1104. --- a/content/browser/renderer_host/render_process_host_impl.cc
  1105. +++ b/content/browser/renderer_host/render_process_host_impl.cc
  1106. @@ -3383,6 +3383,7 @@ void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer(
  1107. switches::kLacrosUseChromeosProtectedMedia,
  1108. switches::kLacrosUseChromeosProtectedAv1,
  1109. #endif
  1110. + switches::kDesktopModeViewportMetaEnabled,
  1111. };
  1112. renderer_cmd->CopySwitchesFrom(browser_cmd, kSwitchNames,
  1113. std::size(kSwitchNames));
  1114. diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
  1115. --- a/content/browser/web_contents/web_contents_impl.cc
  1116. +++ b/content/browser/web_contents/web_contents_impl.cc
  1117. @@ -15,6 +15,7 @@
  1118. #include "base/allocator/partition_alloc_features.h"
  1119. #include "base/allocator/partition_allocator/starscan/pcscan.h"
  1120. +#include "base/base_switches.h"
  1121. #include "base/bind.h"
  1122. #include "base/check_op.h"
  1123. #include "base/command_line.h"
  1124. @@ -2756,6 +2757,9 @@ const blink::web_pref::WebPreferences WebContentsImpl::ComputeWebPreferences() {
  1125. !renderer_preferences_.user_agent_override.ua_metadata_override->mobile)
  1126. #endif
  1127. prefs.viewport_meta_enabled = false;
  1128. + if (!command_line.HasSwitch(switches::kDesktopModeViewportMetaEnabled)) {
  1129. + prefs.viewport_meta_enabled = false;
  1130. + }
  1131. }
  1132. prefs.spatial_navigation_enabled =
  1133. diff --git a/content/public/android/java/src/org/chromium/content/browser/framehost/NavigationControllerImpl.java b/content/public/android/java/src/org/chromium/content/browser/framehost/NavigationControllerImpl.java
  1134. --- a/content/public/android/java/src/org/chromium/content/browser/framehost/NavigationControllerImpl.java
  1135. +++ b/content/public/android/java/src/org/chromium/content/browser/framehost/NavigationControllerImpl.java
  1136. @@ -176,6 +176,7 @@ import org.chromium.url.Origin;
  1137. params.getBaseUrl(), params.getVirtualUrlForDataUrl(),
  1138. params.getDataUrlAsString(), params.getCanLoadLocalResources(),
  1139. params.getIsRendererInitiated(), params.getShouldReplaceCurrentEntry(),
  1140. + params.getUserAgentOverrideOption(),
  1141. params.getInitiatorOrigin(), params.getHasUserGesture(),
  1142. params.getShouldClearHistoryList(), inputStart);
  1143. }
  1144. @@ -356,7 +357,7 @@ import org.chromium.url.Origin;
  1145. int referrerPolicy, int uaOverrideOption, String extraHeaders,
  1146. ResourceRequestBody postData, String baseUrlForDataUrl, String virtualUrlForDataUrl,
  1147. String dataUrlAsString, boolean canLoadLocalResources, boolean isRendererInitiated,
  1148. - boolean shouldReplaceCurrentEntry, Origin initiatorOrigin, boolean hasUserGesture,
  1149. + boolean shouldReplaceCurrentEntry, int userAgentOverrideOption, Origin initiatorOrigin, boolean hasUserGesture,
  1150. boolean shouldClearHistoryList, long inputStart);
  1151. void clearHistory(long nativeNavigationControllerAndroid, NavigationControllerImpl caller);
  1152. int getNavigationHistory(long nativeNavigationControllerAndroid,
  1153. diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
  1154. --- a/content/renderer/render_thread_impl.cc
  1155. +++ b/content/renderer/render_thread_impl.cc
  1156. @@ -973,7 +973,6 @@ void RenderThreadImpl::InitializeRenderer(
  1157. const std::string& reduced_user_agent,
  1158. const blink::UserAgentMetadata& user_agent_metadata,
  1159. const std::vector<std::string>& cors_exempt_header_list) {
  1160. - DCHECK(user_agent_.IsNull());
  1161. DCHECK(reduced_user_agent_.IsNull());
  1162. DCHECK(full_user_agent_.IsNull());
  1163. --
  1164. 2.25.1