web_contents_zoom_controller.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. // Copyright (c) 2017 GitHub, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/web_contents_zoom_controller.h"
  5. #include <string>
  6. #include "content/public/browser/browser_thread.h"
  7. #include "content/public/browser/navigation_details.h"
  8. #include "content/public/browser/navigation_entry.h"
  9. #include "content/public/browser/navigation_handle.h"
  10. #include "content/public/browser/render_frame_host.h"
  11. #include "content/public/browser/render_process_host.h"
  12. #include "content/public/browser/render_view_host.h"
  13. #include "content/public/browser/web_contents.h"
  14. #include "content/public/browser/web_contents_user_data.h"
  15. #include "content/public/common/page_type.h"
  16. #include "net/base/url_util.h"
  17. #include "shell/browser/web_contents_zoom_observer.h"
  18. #include "third_party/blink/public/common/page/page_zoom.h"
  19. using content::BrowserThread;
  20. namespace electron {
  21. namespace {
  22. const double kPageZoomEpsilon = 0.001;
  23. } // namespace
  24. WebContentsZoomController::WebContentsZoomController(
  25. content::WebContents* web_contents)
  26. : content::WebContentsObserver(web_contents),
  27. content::WebContentsUserData<WebContentsZoomController>(*web_contents) {
  28. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  29. host_zoom_map_ = content::HostZoomMap::GetForWebContents(web_contents);
  30. zoom_level_ = host_zoom_map_->GetDefaultZoomLevel();
  31. default_zoom_factor_ = kPageZoomEpsilon;
  32. zoom_subscription_ = host_zoom_map_->AddZoomLevelChangedCallback(
  33. base::BindRepeating(&WebContentsZoomController::OnZoomLevelChanged,
  34. base::Unretained(this)));
  35. UpdateState(std::string());
  36. }
  37. WebContentsZoomController::~WebContentsZoomController() {
  38. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  39. for (auto& observer : observers_) {
  40. observer.OnZoomControllerDestroyed(this);
  41. }
  42. }
  43. void WebContentsZoomController::AddObserver(WebContentsZoomObserver* observer) {
  44. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  45. observers_.AddObserver(observer);
  46. }
  47. void WebContentsZoomController::RemoveObserver(
  48. WebContentsZoomObserver* observer) {
  49. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  50. observers_.RemoveObserver(observer);
  51. }
  52. void WebContentsZoomController::SetEmbedderZoomController(
  53. WebContentsZoomController* controller) {
  54. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  55. embedder_zoom_controller_ = controller;
  56. }
  57. bool WebContentsZoomController::SetZoomLevel(double level) {
  58. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  59. content::NavigationEntry* entry =
  60. web_contents()->GetController().GetLastCommittedEntry();
  61. // Cannot zoom in disabled mode. Also, don't allow changing zoom level on
  62. // a crashed tab, an error page or an interstitial page.
  63. if (zoom_mode_ == ZOOM_MODE_DISABLED ||
  64. !web_contents()->GetPrimaryMainFrame()->IsRenderFrameLive())
  65. return false;
  66. // Do not actually rescale the page in manual mode.
  67. if (zoom_mode_ == ZOOM_MODE_MANUAL) {
  68. // If the zoom level hasn't changed, early out to avoid sending an event.
  69. if (blink::PageZoomValuesEqual(zoom_level_, level))
  70. return true;
  71. double old_zoom_level = zoom_level_;
  72. zoom_level_ = level;
  73. ZoomChangedEventData zoom_change_data(web_contents(), old_zoom_level,
  74. zoom_level_, false /* temporary */,
  75. zoom_mode_);
  76. for (auto& observer : observers_)
  77. observer.OnZoomChanged(zoom_change_data);
  78. return true;
  79. }
  80. content::HostZoomMap* zoom_map =
  81. content::HostZoomMap::GetForWebContents(web_contents());
  82. DCHECK(zoom_map);
  83. DCHECK(!event_data_);
  84. event_data_ = std::make_unique<ZoomChangedEventData>(
  85. web_contents(), GetZoomLevel(), level, false /* temporary */, zoom_mode_);
  86. content::GlobalRenderFrameHostId rfh_id =
  87. web_contents()->GetPrimaryMainFrame()->GetGlobalId();
  88. if (zoom_mode_ == ZOOM_MODE_ISOLATED ||
  89. zoom_map->UsesTemporaryZoomLevel(rfh_id)) {
  90. zoom_map->SetTemporaryZoomLevel(rfh_id, level);
  91. ZoomChangedEventData zoom_change_data(web_contents(), zoom_level_, level,
  92. true /* temporary */, zoom_mode_);
  93. for (auto& observer : observers_)
  94. observer.OnZoomChanged(zoom_change_data);
  95. } else {
  96. if (!entry) {
  97. // If we exit without triggering an update, we should clear event_data_,
  98. // else we may later trigger a DCHECK(event_data_).
  99. event_data_.reset();
  100. return false;
  101. }
  102. std::string host =
  103. net::GetHostOrSpecFromURL(content::HostZoomMap::GetURLFromEntry(entry));
  104. zoom_map->SetZoomLevelForHost(host, level);
  105. }
  106. DCHECK(!event_data_);
  107. return true;
  108. }
  109. double WebContentsZoomController::GetZoomLevel() const {
  110. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  111. return zoom_mode_ == ZOOM_MODE_MANUAL
  112. ? zoom_level_
  113. : content::HostZoomMap::GetZoomLevel(web_contents());
  114. }
  115. void WebContentsZoomController::SetDefaultZoomFactor(double factor) {
  116. default_zoom_factor_ = factor;
  117. }
  118. double WebContentsZoomController::GetDefaultZoomFactor() {
  119. return default_zoom_factor_;
  120. }
  121. void WebContentsZoomController::SetTemporaryZoomLevel(double level) {
  122. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  123. content::GlobalRenderFrameHostId old_rfh_id_ =
  124. web_contents()->GetPrimaryMainFrame()->GetGlobalId();
  125. host_zoom_map_->SetTemporaryZoomLevel(old_rfh_id_, level);
  126. // Notify observers of zoom level changes.
  127. ZoomChangedEventData zoom_change_data(web_contents(), zoom_level_, level,
  128. true /* temporary */, zoom_mode_);
  129. for (WebContentsZoomObserver& observer : observers_)
  130. observer.OnZoomChanged(zoom_change_data);
  131. }
  132. bool WebContentsZoomController::UsesTemporaryZoomLevel() {
  133. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  134. content::GlobalRenderFrameHostId rfh_id =
  135. web_contents()->GetPrimaryMainFrame()->GetGlobalId();
  136. return host_zoom_map_->UsesTemporaryZoomLevel(rfh_id);
  137. }
  138. void WebContentsZoomController::SetZoomMode(ZoomMode new_mode) {
  139. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  140. if (new_mode == zoom_mode_)
  141. return;
  142. content::HostZoomMap* zoom_map =
  143. content::HostZoomMap::GetForWebContents(web_contents());
  144. DCHECK(zoom_map);
  145. content::GlobalRenderFrameHostId rfh_id =
  146. web_contents()->GetPrimaryMainFrame()->GetGlobalId();
  147. double original_zoom_level = GetZoomLevel();
  148. DCHECK(!event_data_);
  149. event_data_ = std::make_unique<ZoomChangedEventData>(
  150. web_contents(), original_zoom_level, original_zoom_level,
  151. false /* temporary */, new_mode);
  152. switch (new_mode) {
  153. case ZOOM_MODE_DEFAULT: {
  154. content::NavigationEntry* entry =
  155. web_contents()->GetController().GetLastCommittedEntry();
  156. if (entry) {
  157. GURL url = content::HostZoomMap::GetURLFromEntry(entry);
  158. std::string host = net::GetHostOrSpecFromURL(url);
  159. if (zoom_map->HasZoomLevel(url.scheme(), host)) {
  160. // If there are other tabs with the same origin, then set this tab's
  161. // zoom level to match theirs. The temporary zoom level will be
  162. // cleared below, but this call will make sure this tab re-draws at
  163. // the correct zoom level.
  164. double origin_zoom_level =
  165. zoom_map->GetZoomLevelForHostAndScheme(url.scheme(), host);
  166. event_data_->new_zoom_level = origin_zoom_level;
  167. zoom_map->SetTemporaryZoomLevel(rfh_id, origin_zoom_level);
  168. } else {
  169. // The host will need a level prior to removing the temporary level.
  170. // We don't want the zoom level to change just because we entered
  171. // default mode.
  172. zoom_map->SetZoomLevelForHost(host, original_zoom_level);
  173. }
  174. }
  175. // Remove per-tab zoom data for this tab. No event callback expected.
  176. zoom_map->ClearTemporaryZoomLevel(rfh_id);
  177. break;
  178. }
  179. case ZOOM_MODE_ISOLATED: {
  180. // Unless the zoom mode was |ZOOM_MODE_DISABLED| before this call, the
  181. // page needs an initial isolated zoom back to the same level it was at
  182. // in the other mode.
  183. if (zoom_mode_ != ZOOM_MODE_DISABLED) {
  184. zoom_map->SetTemporaryZoomLevel(rfh_id, original_zoom_level);
  185. } else {
  186. // When we don't call any HostZoomMap set functions, we send the event
  187. // manually.
  188. for (auto& observer : observers_)
  189. observer.OnZoomChanged(*event_data_);
  190. event_data_.reset();
  191. }
  192. break;
  193. }
  194. case ZOOM_MODE_MANUAL: {
  195. // Unless the zoom mode was |ZOOM_MODE_DISABLED| before this call, the
  196. // page needs to be resized to the default zoom. While in manual mode,
  197. // the zoom level is handled independently.
  198. if (zoom_mode_ != ZOOM_MODE_DISABLED) {
  199. zoom_map->SetTemporaryZoomLevel(rfh_id, GetDefaultZoomLevel());
  200. zoom_level_ = original_zoom_level;
  201. } else {
  202. // When we don't call any HostZoomMap set functions, we send the event
  203. // manually.
  204. for (auto& observer : observers_)
  205. observer.OnZoomChanged(*event_data_);
  206. event_data_.reset();
  207. }
  208. break;
  209. }
  210. case ZOOM_MODE_DISABLED: {
  211. // The page needs to be zoomed back to default before disabling the zoom
  212. double new_zoom_level = GetDefaultZoomLevel();
  213. event_data_->new_zoom_level = new_zoom_level;
  214. zoom_map->SetTemporaryZoomLevel(rfh_id, new_zoom_level);
  215. break;
  216. }
  217. }
  218. // Any event data we've stored should have been consumed by this point.
  219. DCHECK(!event_data_);
  220. zoom_mode_ = new_mode;
  221. }
  222. void WebContentsZoomController::ResetZoomModeOnNavigationIfNeeded(
  223. const GURL& url) {
  224. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  225. if (zoom_mode_ != ZOOM_MODE_ISOLATED && zoom_mode_ != ZOOM_MODE_MANUAL)
  226. return;
  227. content::HostZoomMap* zoom_map =
  228. content::HostZoomMap::GetForWebContents(web_contents());
  229. zoom_level_ = zoom_map->GetDefaultZoomLevel();
  230. double old_zoom_level = zoom_map->GetZoomLevel(web_contents());
  231. double new_zoom_level = zoom_map->GetZoomLevelForHostAndScheme(
  232. url.scheme(), net::GetHostOrSpecFromURL(url));
  233. event_data_ = std::make_unique<ZoomChangedEventData>(
  234. web_contents(), old_zoom_level, new_zoom_level, false, ZOOM_MODE_DEFAULT);
  235. // The call to ClearTemporaryZoomLevel() doesn't generate any events from
  236. // HostZoomMap, but the call to UpdateState() at the end of
  237. // DidFinishNavigation will notify our observers.
  238. // Note: it's possible the render_process/frame ids have disappeared (e.g.
  239. // if we navigated to a new origin), but this won't cause a problem in the
  240. // call below.
  241. zoom_map->ClearTemporaryZoomLevel(
  242. web_contents()->GetPrimaryMainFrame()->GetGlobalId());
  243. zoom_mode_ = ZOOM_MODE_DEFAULT;
  244. }
  245. void WebContentsZoomController::DidFinishNavigation(
  246. content::NavigationHandle* navigation_handle) {
  247. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  248. if (!navigation_handle->IsInPrimaryMainFrame() ||
  249. !navigation_handle->HasCommitted()) {
  250. return;
  251. }
  252. if (navigation_handle->IsErrorPage())
  253. content::HostZoomMap::SendErrorPageZoomLevelRefresh(web_contents());
  254. if (!navigation_handle->IsSameDocument()) {
  255. ResetZoomModeOnNavigationIfNeeded(navigation_handle->GetURL());
  256. SetZoomFactorOnNavigationIfNeeded(navigation_handle->GetURL());
  257. }
  258. // If the main frame's content has changed, the new page may have a different
  259. // zoom level from the old one.
  260. UpdateState(std::string());
  261. DCHECK(!event_data_);
  262. }
  263. void WebContentsZoomController::WebContentsDestroyed() {
  264. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  265. // At this point we should no longer be sending any zoom events with this
  266. // WebContents.
  267. for (auto& observer : observers_) {
  268. observer.OnZoomControllerDestroyed(this);
  269. }
  270. embedder_zoom_controller_ = nullptr;
  271. }
  272. void WebContentsZoomController::RenderFrameHostChanged(
  273. content::RenderFrameHost* old_host,
  274. content::RenderFrameHost* new_host) {
  275. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  276. // If our associated HostZoomMap changes, update our subscription.
  277. content::HostZoomMap* new_host_zoom_map =
  278. content::HostZoomMap::GetForWebContents(web_contents());
  279. if (new_host_zoom_map == host_zoom_map_)
  280. return;
  281. host_zoom_map_ = new_host_zoom_map;
  282. zoom_subscription_ = host_zoom_map_->AddZoomLevelChangedCallback(
  283. base::BindRepeating(&WebContentsZoomController::OnZoomLevelChanged,
  284. base::Unretained(this)));
  285. }
  286. void WebContentsZoomController::SetZoomFactorOnNavigationIfNeeded(
  287. const GURL& url) {
  288. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  289. if (blink::PageZoomValuesEqual(GetDefaultZoomFactor(), kPageZoomEpsilon))
  290. return;
  291. content::GlobalRenderFrameHostId old_rfh_id_ =
  292. content::GlobalRenderFrameHostId(old_process_id_, old_view_id_);
  293. if (host_zoom_map_->UsesTemporaryZoomLevel(old_rfh_id_)) {
  294. host_zoom_map_->ClearTemporaryZoomLevel(old_rfh_id_);
  295. }
  296. if (embedder_zoom_controller_ &&
  297. embedder_zoom_controller_->UsesTemporaryZoomLevel()) {
  298. double level = embedder_zoom_controller_->GetZoomLevel();
  299. SetTemporaryZoomLevel(level);
  300. return;
  301. }
  302. // When kZoomFactor is available, it takes precedence over
  303. // pref store values but if the host has zoom factor set explicitly
  304. // then it takes precedence.
  305. // pref store < kZoomFactor < setZoomLevel
  306. std::string host = net::GetHostOrSpecFromURL(url);
  307. std::string scheme = url.scheme();
  308. double zoom_factor = GetDefaultZoomFactor();
  309. double zoom_level = blink::PageZoomFactorToZoomLevel(zoom_factor);
  310. if (host_zoom_map_->HasZoomLevel(scheme, host)) {
  311. zoom_level = host_zoom_map_->GetZoomLevelForHostAndScheme(scheme, host);
  312. }
  313. if (blink::PageZoomValuesEqual(zoom_level, GetZoomLevel()))
  314. return;
  315. SetZoomLevel(zoom_level);
  316. }
  317. void WebContentsZoomController::OnZoomLevelChanged(
  318. const content::HostZoomMap::ZoomLevelChange& change) {
  319. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  320. UpdateState(change.host);
  321. }
  322. void WebContentsZoomController::UpdateState(const std::string& host) {
  323. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  324. // If |host| is empty, all observers should be updated.
  325. if (!host.empty()) {
  326. // Use the navigation entry's URL instead of the WebContents' so virtual
  327. // URLs work (e.g. chrome://settings). http://crbug.com/153950
  328. content::NavigationEntry* entry =
  329. web_contents()->GetController().GetLastCommittedEntry();
  330. if (!entry || host != net::GetHostOrSpecFromURL(
  331. content::HostZoomMap::GetURLFromEntry(entry))) {
  332. return;
  333. }
  334. }
  335. if (event_data_) {
  336. // For state changes initiated within the ZoomController, information about
  337. // the change should be sent.
  338. ZoomChangedEventData zoom_change_data = *event_data_;
  339. event_data_.reset();
  340. for (auto& observer : observers_)
  341. observer.OnZoomChanged(zoom_change_data);
  342. } else {
  343. double zoom_level = GetZoomLevel();
  344. ZoomChangedEventData zoom_change_data(web_contents(), zoom_level,
  345. zoom_level, false, zoom_mode_);
  346. for (auto& observer : observers_)
  347. observer.OnZoomChanged(zoom_change_data);
  348. }
  349. }
  350. WEB_CONTENTS_USER_DATA_KEY_IMPL(WebContentsZoomController);
  351. } // namespace electron