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