web_contents_zoom_controller.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  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::ZoomValuesEqual(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_, true /* 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. void WebContentsZoomController::SetTemporaryZoomLevel(double level) {
  119. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  120. content::GlobalRenderFrameHostId old_rfh_id_ =
  121. web_contents()->GetPrimaryMainFrame()->GetGlobalId();
  122. host_zoom_map_->SetTemporaryZoomLevel(old_rfh_id_, level);
  123. // Notify observers of zoom level changes.
  124. ZoomChangedEventData zoom_change_data(web_contents(), zoom_level_, level,
  125. true /* temporary */, zoom_mode_);
  126. for (auto& observer : observers_)
  127. observer.OnZoomChanged(zoom_change_data);
  128. }
  129. bool WebContentsZoomController::UsesTemporaryZoomLevel() {
  130. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  131. content::GlobalRenderFrameHostId rfh_id =
  132. web_contents()->GetPrimaryMainFrame()->GetGlobalId();
  133. return host_zoom_map_->UsesTemporaryZoomLevel(rfh_id);
  134. }
  135. void WebContentsZoomController::SetZoomMode(ZoomMode new_mode) {
  136. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  137. if (new_mode == zoom_mode_)
  138. return;
  139. content::HostZoomMap* zoom_map =
  140. content::HostZoomMap::GetForWebContents(web_contents());
  141. DCHECK(zoom_map);
  142. content::GlobalRenderFrameHostId rfh_id =
  143. web_contents()->GetPrimaryMainFrame()->GetGlobalId();
  144. double original_zoom_level = GetZoomLevel();
  145. DCHECK(!event_data_);
  146. event_data_ = std::make_unique<ZoomChangedEventData>(
  147. web_contents(), original_zoom_level, original_zoom_level,
  148. false /* temporary */, new_mode);
  149. switch (new_mode) {
  150. case ZOOM_MODE_DEFAULT: {
  151. content::NavigationEntry* entry =
  152. web_contents()->GetController().GetLastCommittedEntry();
  153. if (entry) {
  154. GURL url = content::HostZoomMap::GetURLFromEntry(entry);
  155. const std::string host = net::GetHostOrSpecFromURL(url);
  156. const std::string scheme = url.scheme();
  157. if (zoom_map->HasZoomLevel(scheme, host)) {
  158. // If there are other tabs with the same origin, then set this tab's
  159. // zoom level to match theirs. The temporary zoom level will be
  160. // cleared below, but this call will make sure this tab re-draws at
  161. // the correct zoom level.
  162. double origin_zoom_level =
  163. zoom_map->GetZoomLevelForHostAndScheme(scheme, host);
  164. event_data_->new_zoom_level = origin_zoom_level;
  165. zoom_map->SetTemporaryZoomLevel(rfh_id, origin_zoom_level);
  166. } else {
  167. // The host will need a level prior to removing the temporary level.
  168. // We don't want the zoom level to change just because we entered
  169. // default mode.
  170. zoom_map->SetZoomLevelForHost(host, original_zoom_level);
  171. }
  172. }
  173. // Remove per-tab zoom data for this tab. No event callback expected.
  174. zoom_map->ClearTemporaryZoomLevel(rfh_id);
  175. break;
  176. }
  177. case ZOOM_MODE_ISOLATED: {
  178. // Unless the zoom mode was |ZOOM_MODE_DISABLED| before this call, the
  179. // page needs an initial isolated zoom back to the same level it was at
  180. // in the other mode.
  181. if (zoom_mode_ != ZOOM_MODE_DISABLED) {
  182. zoom_map->SetTemporaryZoomLevel(rfh_id, original_zoom_level);
  183. } else {
  184. // When we don't call any HostZoomMap set functions, we send the event
  185. // manually.
  186. for (auto& observer : observers_)
  187. observer.OnZoomChanged(*event_data_);
  188. event_data_.reset();
  189. }
  190. break;
  191. }
  192. case ZOOM_MODE_MANUAL: {
  193. // Unless the zoom mode was |ZOOM_MODE_DISABLED| before this call, the
  194. // page needs to be resized to the default zoom. While in manual mode,
  195. // the zoom level is handled independently.
  196. if (zoom_mode_ != ZOOM_MODE_DISABLED) {
  197. zoom_map->SetTemporaryZoomLevel(rfh_id, GetDefaultZoomLevel());
  198. zoom_level_ = original_zoom_level;
  199. } else {
  200. // When we don't call any HostZoomMap set functions, we send the event
  201. // manually.
  202. for (auto& observer : observers_)
  203. observer.OnZoomChanged(*event_data_);
  204. event_data_.reset();
  205. }
  206. break;
  207. }
  208. case ZOOM_MODE_DISABLED: {
  209. // The page needs to be zoomed back to default before disabling the zoom
  210. double new_zoom_level = GetDefaultZoomLevel();
  211. event_data_->new_zoom_level = new_zoom_level;
  212. zoom_map->SetTemporaryZoomLevel(rfh_id, new_zoom_level);
  213. break;
  214. }
  215. }
  216. // Any event data we've stored should have been consumed by this point.
  217. DCHECK(!event_data_);
  218. zoom_mode_ = new_mode;
  219. }
  220. void WebContentsZoomController::ResetZoomModeOnNavigationIfNeeded(
  221. const GURL& url) {
  222. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  223. if (zoom_mode_ != ZOOM_MODE_ISOLATED && zoom_mode_ != ZOOM_MODE_MANUAL)
  224. return;
  225. content::HostZoomMap* zoom_map =
  226. content::HostZoomMap::GetForWebContents(web_contents());
  227. zoom_level_ = zoom_map->GetDefaultZoomLevel();
  228. double old_zoom_level = zoom_map->GetZoomLevel(web_contents());
  229. double new_zoom_level = zoom_map->GetZoomLevelForHostAndScheme(
  230. url.scheme(), net::GetHostOrSpecFromURL(url));
  231. event_data_ = std::make_unique<ZoomChangedEventData>(
  232. web_contents(), old_zoom_level, new_zoom_level, false, ZOOM_MODE_DEFAULT);
  233. // The call to ClearTemporaryZoomLevel() doesn't generate any events from
  234. // HostZoomMap, but the call to UpdateState() at the end of
  235. // DidFinishNavigation will notify our observers.
  236. // Note: it's possible the render_process/frame ids have disappeared (e.g.
  237. // if we navigated to a new origin), but this won't cause a problem in the
  238. // call below.
  239. zoom_map->ClearTemporaryZoomLevel(
  240. web_contents()->GetPrimaryMainFrame()->GetGlobalId());
  241. zoom_mode_ = ZOOM_MODE_DEFAULT;
  242. }
  243. void WebContentsZoomController::DidFinishNavigation(
  244. content::NavigationHandle* navigation_handle) {
  245. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  246. if (!navigation_handle->IsInPrimaryMainFrame() ||
  247. !navigation_handle->HasCommitted()) {
  248. return;
  249. }
  250. if (navigation_handle->IsErrorPage())
  251. content::HostZoomMap::SendErrorPageZoomLevelRefresh(web_contents());
  252. if (!navigation_handle->IsSameDocument()) {
  253. ResetZoomModeOnNavigationIfNeeded(navigation_handle->GetURL());
  254. SetZoomFactorOnNavigationIfNeeded(navigation_handle->GetURL());
  255. // If the main frame's content has changed, the new page may have a
  256. // different zoom level from the old one.
  257. UpdateState(std::string());
  258. }
  259. DCHECK(!event_data_);
  260. }
  261. void WebContentsZoomController::WebContentsDestroyed() {
  262. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  263. // At this point we should no longer be sending any zoom events with this
  264. // WebContents.
  265. for (auto& observer : observers_) {
  266. observer.OnZoomControllerDestroyed(this);
  267. }
  268. embedder_zoom_controller_ = nullptr;
  269. }
  270. void WebContentsZoomController::RenderFrameHostChanged(
  271. content::RenderFrameHost* old_host,
  272. content::RenderFrameHost* new_host) {
  273. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  274. // If our associated HostZoomMap changes, update our subscription.
  275. content::HostZoomMap* new_host_zoom_map =
  276. content::HostZoomMap::GetForWebContents(web_contents());
  277. if (new_host_zoom_map == host_zoom_map_)
  278. return;
  279. host_zoom_map_ = new_host_zoom_map;
  280. zoom_subscription_ = host_zoom_map_->AddZoomLevelChangedCallback(
  281. base::BindRepeating(&WebContentsZoomController::OnZoomLevelChanged,
  282. base::Unretained(this)));
  283. }
  284. void WebContentsZoomController::SetZoomFactorOnNavigationIfNeeded(
  285. const GURL& url) {
  286. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  287. if (blink::ZoomValuesEqual(default_zoom_factor(), kPageZoomEpsilon))
  288. return;
  289. content::GlobalRenderFrameHostId old_rfh_id_ =
  290. content::GlobalRenderFrameHostId(old_process_id_, old_view_id_);
  291. if (host_zoom_map_->UsesTemporaryZoomLevel(old_rfh_id_)) {
  292. host_zoom_map_->ClearTemporaryZoomLevel(old_rfh_id_);
  293. }
  294. if (embedder_zoom_controller_ &&
  295. embedder_zoom_controller_->UsesTemporaryZoomLevel()) {
  296. double level = embedder_zoom_controller_->GetZoomLevel();
  297. SetTemporaryZoomLevel(level);
  298. return;
  299. }
  300. // When kZoomFactor is available, it takes precedence over
  301. // pref store values but if the host has zoom factor set explicitly
  302. // then it takes precedence.
  303. // pref store < kZoomFactor < setZoomLevel
  304. std::string host = net::GetHostOrSpecFromURL(url);
  305. std::string scheme = url.scheme();
  306. double zoom_factor = default_zoom_factor();
  307. double zoom_level = blink::ZoomFactorToZoomLevel(zoom_factor);
  308. if (host_zoom_map_->HasZoomLevel(scheme, host)) {
  309. zoom_level = host_zoom_map_->GetZoomLevelForHostAndScheme(scheme, host);
  310. }
  311. if (blink::ZoomValuesEqual(zoom_level, GetZoomLevel()))
  312. return;
  313. SetZoomLevel(zoom_level);
  314. }
  315. void WebContentsZoomController::OnZoomLevelChanged(
  316. const content::HostZoomMap::ZoomLevelChange& change) {
  317. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  318. UpdateState(change.host);
  319. }
  320. void WebContentsZoomController::UpdateState(const std::string& host) {
  321. DCHECK_CURRENTLY_ON(BrowserThread::UI);
  322. // If |host| is empty, all observers should be updated.
  323. if (!host.empty()) {
  324. // Use the navigation entry's URL instead of the WebContents' so virtual
  325. // URLs work (e.g. chrome://settings). http://crbug.com/153950
  326. content::NavigationEntry* entry =
  327. web_contents()->GetController().GetLastCommittedEntry();
  328. if (!entry || host != net::GetHostOrSpecFromURL(
  329. content::HostZoomMap::GetURLFromEntry(entry))) {
  330. return;
  331. }
  332. }
  333. if (event_data_) {
  334. // For state changes initiated within the ZoomController, information about
  335. // the change should be sent.
  336. ZoomChangedEventData zoom_change_data = *event_data_;
  337. event_data_.reset();
  338. for (auto& observer : observers_)
  339. observer.OnZoomChanged(zoom_change_data);
  340. } else {
  341. double zoom_level = GetZoomLevel();
  342. ZoomChangedEventData zoom_change_data(web_contents(), zoom_level,
  343. zoom_level, false, zoom_mode_);
  344. for (auto& observer : observers_)
  345. observer.OnZoomChanged(zoom_change_data);
  346. }
  347. }
  348. WEB_CONTENTS_USER_DATA_KEY_IMPL(WebContentsZoomController);
  349. } // namespace electron