1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef COMPONENTS_ZOOM_ZOOM_CONTROLLER_H_
6 #define COMPONENTS_ZOOM_ZOOM_CONTROLLER_H_
7 
8 #include <memory>
9 
10 #include "base/compiler_specific.h"
11 #include "base/macros.h"
12 #include "base/memory/ref_counted.h"
13 #include "base/observer_list.h"
14 #include "components/prefs/pref_member.h"
15 #include "content/public/browser/host_zoom_map.h"
16 #include "content/public/browser/web_contents_observer.h"
17 #include "content/public/browser/web_contents_user_data.h"
18 
19 class ZoomControllerTest;
20 
21 namespace content {
22 class WebContents;
23 }
24 
25 namespace zoom {
26 class ZoomObserver;
27 
28 class ZoomRequestClient : public base::RefCounted<ZoomRequestClient> {
29  public:
ZoomRequestClient()30   ZoomRequestClient() {}
31   virtual bool ShouldSuppressBubble() const = 0;
32 
33  protected:
~ZoomRequestClient()34   virtual ~ZoomRequestClient() {}
35 
36  private:
37   friend class base::RefCounted<ZoomRequestClient>;
38 
39   DISALLOW_COPY_AND_ASSIGN(ZoomRequestClient);
40 };
41 
42 // Per-tab class to manage zoom changes and the Omnibox zoom icon. Lives on the
43 // UI thread.
44 class ZoomController : public content::WebContentsObserver,
45                        public content::WebContentsUserData<ZoomController> {
46  public:
47   // Defines how zoom changes are handled.
48   enum ZoomMode {
49     // Results in default zoom behavior, i.e. zoom changes are handled
50     // automatically and on a per-origin basis, meaning that other tabs
51     // navigated to the same origin will also zoom.
52     ZOOM_MODE_DEFAULT,
53     // Results in zoom changes being handled automatically, but on a per-tab
54     // basis. Tabs in this zoom mode will not be affected by zoom changes in
55     // other tabs, and vice versa.
56     ZOOM_MODE_ISOLATED,
57     // Overrides the automatic handling of zoom changes. The |onZoomChange|
58     // event will still be dispatched, but the page will not actually be zoomed.
59     // These zoom changes can be handled manually by listening for the
60     // |onZoomChange| event. Zooming in this mode is also on a per-tab basis.
61     ZOOM_MODE_MANUAL,
62     // Disables all zooming in this tab. The tab will revert to the default
63     // zoom level, and all attempted zoom changes will be ignored.
64     ZOOM_MODE_DISABLED,
65   };
66 
67   enum RelativeZoom {
68     ZOOM_BELOW_DEFAULT_ZOOM,
69     ZOOM_AT_DEFAULT_ZOOM,
70     ZOOM_ABOVE_DEFAULT_ZOOM
71   };
72 
73   struct ZoomChangedEventData {
ZoomChangedEventDataZoomChangedEventData74     ZoomChangedEventData(content::WebContents* web_contents,
75                          double old_zoom_level,
76                          double new_zoom_level,
77                          ZoomController::ZoomMode zoom_mode,
78                          bool can_show_bubble)
79         : web_contents(web_contents),
80           old_zoom_level(old_zoom_level),
81           new_zoom_level(new_zoom_level),
82           zoom_mode(zoom_mode),
83           can_show_bubble(can_show_bubble) {}
84     content::WebContents* web_contents;
85     double old_zoom_level;
86     double new_zoom_level;
87     ZoomController::ZoomMode zoom_mode;
88     bool can_show_bubble;
89   };
90 
91   // Since it's possible for a WebContents to not have a ZoomController, provide
92   // a simple, safe and reliable method to find the current zoom level for a
93   // given WebContents*.
94   static double GetZoomLevelForWebContents(content::WebContents* web_contents);
95 
96   ~ZoomController() override;
97 
zoom_mode()98   ZoomMode zoom_mode() const { return zoom_mode_; }
99 
100   // Convenience method to get default zoom level. Implemented here for
101   // inlining.
GetDefaultZoomLevel()102   double GetDefaultZoomLevel() const {
103     return content::HostZoomMap::GetForWebContents(web_contents())
104         ->GetDefaultZoomLevel();
105   }
106 
107   // Convenience method to quickly check if the tab's at default zoom.
108   // Virtual for testing.
109   virtual bool IsAtDefaultZoom() const;
110 
111   // Returns which image should be loaded for the current zoom level.
112   RelativeZoom GetZoomRelativeToDefault() const;
113 
last_client()114   const ZoomRequestClient* last_client() const { return last_client_.get(); }
115 
116   void AddObserver(ZoomObserver* observer);
117   void RemoveObserver(ZoomObserver* observer);
118 
119   // Used to set whether the zoom notification bubble can be shown when the
120   // zoom level is changed for this controller. Default behavior is to show
121   // the bubble.
SetShowsNotificationBubble(bool can_show_bubble)122   void SetShowsNotificationBubble(bool can_show_bubble) {
123     can_show_bubble_ = can_show_bubble;
124   }
125 
126   // Gets the current zoom level by querying HostZoomMap (if not in manual zoom
127   // mode) or from the ZoomController local value otherwise.
128   double GetZoomLevel() const;
129   // Calls GetZoomLevel() then converts the returned value to a percentage
130   // zoom factor.
131   // Virtual for testing.
132   virtual int GetZoomPercent() const;
133 
134   // Sets the zoom level through HostZoomMap.
135   // Returns true on success.
136   bool SetZoomLevel(double zoom_level);
137 
138   // Sets the zoom level via HostZoomMap (or stores it locally if in manual zoom
139   // mode), and attributes the zoom to |client|. Returns true on success.
140   bool SetZoomLevelByClient(
141       double zoom_level,
142       const scoped_refptr<const ZoomRequestClient>& client);
143 
144   // Sets the zoom mode, which defines zoom behavior (see enum ZoomMode).
145   void SetZoomMode(ZoomMode zoom_mode);
146 
147   // Set and query whether or not the page scale factor is one.
148   void SetPageScaleFactorIsOneForTesting(bool is_one);
149   bool PageScaleFactorIsOne() const;
150 
151   // content::WebContentsObserver overrides:
152   void DidFinishNavigation(
153       content::NavigationHandle* navigation_handle) override;
154   void WebContentsDestroyed() override;
155   void RenderFrameHostChanged(content::RenderFrameHost* old_host,
156                               content::RenderFrameHost* new_host) override;
157 
158  protected:
159   // Protected for testing.
160   explicit ZoomController(content::WebContents* web_contents);
161 
162  private:
163   friend class content::WebContentsUserData<ZoomController>;
164   friend class ::ZoomControllerTest;
165 
166   void ResetZoomModeOnNavigationIfNeeded(const GURL& url);
167   void OnZoomLevelChanged(const content::HostZoomMap::ZoomLevelChange& change);
168 
169   // Updates the zoom icon and zoom percentage based on current values and
170   // notifies the observer if changes have occurred. |host| may be empty,
171   // meaning the change should apply to ~all sites. If it is not empty, the
172   // change only affects sites with the given host.
173   void UpdateState(const std::string& host);
174 
175   // True if changes to zoom level can trigger the zoom notification bubble.
176   bool can_show_bubble_;
177 
178   // The current zoom mode.
179   ZoomMode zoom_mode_;
180 
181   // Current zoom level.
182   double zoom_level_;
183 
184   std::unique_ptr<ZoomChangedEventData> event_data_;
185 
186   // Keeps track of the extension (if any) that initiated the last zoom change
187   // that took effect.
188   scoped_refptr<const ZoomRequestClient> last_client_;
189 
190   // Observer receiving notifications on state changes.
191   base::ObserverList<ZoomObserver>::Unchecked observers_;
192 
193   content::BrowserContext* browser_context_;
194   // Keep track of the HostZoomMap we're currently subscribed to.
195   content::HostZoomMap* host_zoom_map_;
196 
197   std::unique_ptr<content::HostZoomMap::Subscription> zoom_subscription_;
198 
199   WEB_CONTENTS_USER_DATA_KEY_DECL();
200 
201   DISALLOW_COPY_AND_ASSIGN(ZoomController);
202 };
203 
204 }  // namespace zoom
205 
206 #endif  // COMPONENTS_ZOOM_ZOOM_CONTROLLER_H_
207