Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(514)

Side by Side Diff: chrome/browser/resources/settings/settings_page/main_page_behavior.js

Issue 2224673002: MD Settings: simplify animation scheduling (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: cleanup Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * Calls |readyTest| repeatedly until it returns true, then calls 6 * Calls |readyTest| repeatedly until it returns true, then calls
7 * |readyCallback|. 7 * |readyCallback|.
8 * @param {function():boolean} readyTest 8 * @param {function():boolean} readyTest
9 * @param {!Function} readyCallback 9 * @param {!Function} readyCallback
10 */ 10 */
(...skipping 24 matching lines...) Expand all
35 this.scroller = this.domHost.parentNode.scroller; 35 this.scroller = this.domHost.parentNode.scroller;
36 else 36 else
37 this.scroller = document.body; // Used in unit tests. 37 this.scroller = document.body; // Used in unit tests.
38 }, 38 },
39 39
40 /** 40 /**
41 * @param {!settings.Route} newRoute 41 * @param {!settings.Route} newRoute
42 * @param {!settings.Route} oldRoute 42 * @param {!settings.Route} oldRoute
43 */ 43 */
44 currentRouteChanged: function(newRoute, oldRoute) { 44 currentRouteChanged: function(newRoute, oldRoute) {
45 var newRouteIsSubpage = newRoute && newRoute.subpage.length; 45 // Allow the page to load before expanding the section. TODO(michaelpg):
46 var oldRouteIsSubpage = oldRoute && oldRoute.subpage.length; 46 // Time this better when refactoring settings-animated-pages.
47 if (!oldRoute && newRoute.subpage.length)
48 setTimeout(this.tryTransitionToSection_.bind(this));
49 else
50 this.tryTransitionToSection_();
51 },
47 52
48 if (!oldRoute && newRouteIsSubpage) { 53 /**
49 // Allow the page to load before expanding the section. TODO(michaelpg): 54 * If possible, transitions to the current route's section (by expanding or
50 // Time this better when refactoring settings-animated-pages. 55 * scrolling to it). If another transition is running, finishes or cancels
51 setTimeout(function() { 56 * that one, then schedules this function again. This ensures the current
52 var section = this.getSection_(newRoute.section); 57 * section is quickly shown, without getting the page into a broken state --
53 if (section) 58 * if currentRoute changes in between calls, just transition to the new route.
54 this.expandSection_(section); 59 * @private
55 }.bind(this)); 60 */
61 tryTransitionToSection_: function() {
62 var currentRoute = settings.getCurrentRoute();
63 var currentSection = this.getSection_(currentRoute.section);
64
65 // If an animation is already playing, try finishing or canceling it.
66 if (this.currentAnimation_) {
67 this.maybeStopCurrentAnimation_();
68 // Either way, this function will be called again once the current
69 // animation ends.
56 return; 70 return;
57 } 71 }
58 72
59 if (newRouteIsSubpage) { 73 var promise;
60 if (!oldRouteIsSubpage || newRoute.section != oldRoute.section) { 74 var expandedSection = /** @type {?SettingsSectionElement} */(
61 var section = this.getSection_(newRoute.section); 75 this.$$('settings-section.expanded'));
62 if (section) 76 if (expandedSection) {
63 this.expandSection_(section); 77 // If the section shouldn't be expanded, collapse it.
78 if (!currentRoute.subpage.length || expandedSection != currentSection) {
79 promise = this.collapseSection_(expandedSection);
80 // Scroll to the collapsed section. TODO(michaelpg): This can look weird
81 // because the collapse we just scheduled calculated its end target
82 // based on the current scroll position. This bug existed before, and is
83 // fixed in the next patch by making the card position: absolute.
84 if (currentSection)
85 this.scrollToSection_();
64 } 86 }
65 } else { 87 } else if (currentSection) {
66 if (oldRouteIsSubpage) { 88 // Expand the section into a subpage or scroll to it on the main page.
67 var section = this.getSection_(oldRoute.section); 89 if (currentRoute.subpage.length)
68 if (section) 90 promise = this.expandSection_(currentSection);
69 this.collapseSection_(section); 91 else
70 }
71
72 // Scrolls to the section if this main page contains the route's section.
73 if (newRoute && newRoute.section && this.getSection_(newRoute.section))
74 this.scrollToSection_(); 92 this.scrollToSection_();
75 } 93 }
94
95 // When this animation ends, another may be necessary. Call this function
96 // again after the promise resolves.
97 if (promise)
98 promise.then(this.tryTransitionToSection_.bind(this));
99 },
100
101 /**
102 * If the current animation is inconsistent with the current route, stops the
Dan Beam 2016/08/09 18:15:19 stops -> stop?
michaelpg 2016/08/09 20:44:03 figure it should still be indicative, like other f
103 * animation by finishing or canceling it so the new route can be animated to.
104 * @private
105 */
106 maybeStopCurrentAnimation_: function() {
107 var currentRoute = settings.getCurrentRoute();
108 var animatingSection = /** @type {?SettingsSectionElement} */(
109 this.$$('settings-section.expanding, settings-section.collapsing'));
Dan Beam 2016/08/09 18:15:19 i assume it's impossible for these both to exist a
michaelpg 2016/08/09 20:44:03 That's the idea, enforced by tryTransitionToSectio
110 assert(animatingSection);
111
112 if (animatingSection.classList.contains('expanding')) {
113 // Cancel the animation to go back to the main page if the animating
114 // section shouldn't be expanded.
115 if (animatingSection.section != currentRoute.section ||
116 !currentRoute.subpage.length) {
117 this.currentAnimation_.cancel();
118 }
119 // Otherwise, let the expand animation continue.
120 return;
121 }
122
123 assert(animatingSection.classList.contains('collapsing'));
124 if (!currentRoute.subpage.length)
125 return;
126
127 // If the collapsing section actually matches the current route's section,
128 // we can just cancel the animation to re-expand the section.
129 if (animatingSection.section == currentRoute.section) {
130 this.currentAnimation_.cancel();
131 return;
132 }
133
134 // The current route is a subpage, so that section needs to expand.
135 // Immediately finish the current collapse animation so that can happen.
136 this.currentAnimation_.finish();
76 }, 137 },
77 138
78 /** 139 /**
79 * Animates the card in |section|, expanding it to fill the page. 140 * Animates the card in |section|, expanding it to fill the page.
80 * @param {!SettingsSectionElement} section 141 * @param {!SettingsSectionElement} section
142 * @return {!Promise} Resolved when the transition is finished or canceled.
81 * @private 143 * @private
82 */ 144 */
83 expandSection_: function(section) { 145 expandSection_: function(section) {
84 // If another section's card is expanding, cancel that animation first. 146 assert(this.scroller);
85 var expanding = this.$$('.expanding'); 147 assert(section.canAnimateExpand());
86 if (expanding) {
87 if (expanding == section)
88 return;
89 148
90 if (this.animations['section']) { 149 // Save the scroller position before freezing it.
91 // Cancel the animation, then call startExpandSection_. 150 this.origScrollTop_ = this.scroller.scrollTop;
92 this.cancelAnimation('section', function() { 151 this.toggleScrolling_(false);
93 this.startExpandSection_(section);
94 }.bind(this));
95 } else {
96 // The animation must have finished but its promise hasn't resolved yet.
97 // When it resolves, collapse that section's card before expanding
98 // this one.
99 setTimeout(function() {
100 this.collapseSection_(
101 /** @type {!SettingsSectionElement} */(expanding));
102 this.finishAnimation('section', function() {
103 this.startExpandSection_(section);
104 }.bind(this));
105 }.bind(this));
106 }
107 152
108 return; 153 // Freeze the section's height so its card can be removed from the flow.
109 }
110
111 if (this.$$('.collapsing') && this.animations['section']) {
112 // Finish the collapse animation before expanding.
113 this.finishAnimation('section', function() {
114 this.startExpandSection_(section);
115 }.bind(this));
116 return;
117 }
118
119 this.startExpandSection_(section);
120 },
121
122 /**
123 * Helper function to set up and start the expand animation.
124 * @param {!SettingsSectionElement} section
125 */
126 startExpandSection_: function(section) {
127 if (!section.canAnimateExpand())
128 return;
129
130 // Freeze the scroller and save its position.
131 this.listScrollTop_ = this.scroller.scrollTop;
132
133 var scrollerWidth = this.scroller.clientWidth;
134 this.scroller.style.overflow = 'hidden';
135 // Adjust width to compensate for scroller.
136 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth;
137 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)';
138
139 // Freezes the section's height so its card can be removed from the flow.
140 section.setFrozen(true); 154 section.setFrozen(true);
141 155
142 // Expand the section's card to fill the parent. 156 this.currentAnimation_ = section.animateExpand(this.scroller);
143 var animationPromise = this.playExpandSection_(section); 157 var promise = this.currentAnimation_ ?
158 this.currentAnimation_.finished : Promise.resolve();
144 159
145 animationPromise.then(function() { 160 var finished;
161 return promise.then(function() {
146 this.scroller.scrollTop = 0; 162 this.scroller.scrollTop = 0;
147 this.toggleOtherSectionsHidden_(section.section, true); 163 this.toggleOtherSectionsHidden_(section.section, true);
164 finished = true;
148 }.bind(this), function() { 165 }.bind(this), function() {
149 // Animation was canceled; restore the section. 166 // The animation was canceled; restore the section.
150 section.setFrozen(false); 167 section.setFrozen(false);
151 }.bind(this)).then(function() {
152 this.scroller.style.overflow = '';
153 this.scroller.style.width = '';
154 }.bind(this));
155 },
156
157 /**
158 * Expands the card in |section| to fill the page.
159 * @param {!SettingsSectionElement} section
160 * @return {!Promise}
161 * @private
162 */
163 playExpandSection_: function(section) {
164 // We must be attached.
165 assert(this.scroller);
166
167 var promise;
168 var animationConfig = section.animateExpand(this.scroller);
169 if (animationConfig) {
170 promise = this.animateElement('section', animationConfig.card,
171 animationConfig.keyframes, animationConfig.options);
172 } else {
173 promise = Promise.resolve();
174 }
175
176 var finished;
177 promise.then(function() {
178 finished = true;
179 this.style.margin = 'auto';
180 }.bind(this), function() {
181 // The animation was canceled; catch the error and continue.
182 finished = false; 168 finished = false;
183 }).then(function() { 169 }).then(function() {
184 section.cleanUpAnimateExpand(finished); 170 section.cleanUpAnimateExpand(finished);
185 }); 171 this.toggleScrolling_(true);
186 172 this.currentAnimation_ = null;
187 return promise; 173 }.bind(this));
188 }, 174 },
189 175
190 /** 176 /**
191 * Animates the card in |section|, collapsing it back into its section. 177 * Animates the card in |section|, collapsing it back into its section.
192 * @param {!SettingsSectionElement} section 178 * @param {!SettingsSectionElement} section
179 * @return {!Promise} Resolved when the transition is finished or canceled.
193 * @private 180 * @private
194 */ 181 */
195 collapseSection_: function(section) { 182 collapseSection_: function(section) {
196 // If the section's card is still expanding, cancel the expand animation. 183 assert(this.scroller);
197 if (section.classList.contains('expanding')) { 184 assert(section.canAnimateCollapse());
198 if (this.animations['section']) {
199 this.cancelAnimation('section');
200 } else {
201 // The animation must have finished but its promise hasn't finished
202 // resolving; try again asynchronously.
203 this.async(function() {
204 this.collapseSection_(section);
205 });
206 }
207 return;
208 }
209
210 if (!section.canAnimateCollapse())
211 return;
212 185
213 this.toggleOtherSectionsHidden_(section.section, false); 186 this.toggleOtherSectionsHidden_(section.section, false);
187 this.toggleScrolling_(false);
214 188
215 var scrollerWidth = this.scroller.clientWidth; 189 this.currentAnimation_ =
216 this.scroller.style.overflow = 'hidden'; 190 section.animateCollapse(this.scroller, this.origScrollTop_);
217 // Adjust width to compensate for scroller. 191 var promise = this.currentAnimation_ ?
218 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; 192 this.currentAnimation_.finished : Promise.resolve();
219 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)';
220 193
221 this.playCollapseSection_(section).then(function() { 194 return promise.then(function() {
195 section.cleanUpAnimateCollapse(true);
196 }, function() {
197 section.cleanUpAnimateCollapse(false);
198 }).then(function() {
222 section.setFrozen(false); 199 section.setFrozen(false);
223 this.scroller.style.overflow = '';
224 this.scroller.style.width = '';
225 section.classList.remove('collapsing'); 200 section.classList.remove('collapsing');
201 this.toggleScrolling_(true);
202 this.currentAnimation_ = null;
226 }.bind(this)); 203 }.bind(this));
227 }, 204 },
228 205
229 /** 206 /**
230 * Collapses the card in |section| back to its normal position. 207 * Hides or unhides the sections not being expanded.
231 * @param {!SettingsSectionElement} section 208 * @param {string} sectionName The section to keep visible.
232 * @return {!Promise} 209 * @param {boolean} hidden Whether the sections should be hidden.
233 * @private 210 * @private
234 */ 211 */
235 playCollapseSection_: function(section) { 212 toggleOtherSectionsHidden_: function(sectionName, hidden) {
236 // We must be attached. 213 var sections = Polymer.dom(this.root).querySelectorAll(
237 assert(this.scroller); 214 'settings-section');
238 215 for (var section of sections)
239 this.style.margin = ''; 216 section.hidden = hidden && (section.section != sectionName);
240
241 var promise;
242 var animationConfig =
243 section.animateCollapse(this.scroller, this.listScrollTop_);
244 if (animationConfig) {
245 promise = this.animateElement('section', animationConfig.card,
246 animationConfig.keyframes, animationConfig.options);
247 } else {
248 promise = Promise.resolve();
249 }
250
251 promise.then(function() {
252 section.cleanUpAnimateCollapse(true);
253 }, function() {
254 section.cleanUpAnimateCollapse(false);
255 });
256 return promise;
257 }, 217 },
258 218
259 /** @private */ 219 /** @private */
260 scrollToSection_: function() { 220 scrollToSection_: function() {
261 doWhenReady( 221 doWhenReady(
262 function() { 222 function() {
263 return this.scrollHeight > 0; 223 return this.scrollHeight > 0;
264 }.bind(this), 224 }.bind(this),
265 function() { 225 function() {
266 // If the current section changes while we are waiting for the page to 226 // If the current section changes while we are waiting for the page to
267 // be ready, scroll to the newest requested section. 227 // be ready, scroll to the newest requested section.
268 this.getSection_(settings.getCurrentRoute().section).scrollIntoView(); 228 var section = this.getSection_(settings.getCurrentRoute().section);
229 if (section)
230 section.scrollIntoView();
269 }.bind(this)); 231 }.bind(this));
270 }, 232 },
271 233
272 /** 234 /**
273 * Hides or unhides the sections not being expanded.
274 * @param {string} sectionName The section to keep visible.
275 * @param {boolean} hidden Whether the sections should be hidden.
276 * @private
277 */
278 toggleOtherSectionsHidden_: function(sectionName, hidden) {
279 var sections = Polymer.dom(this.root).querySelectorAll(
280 'settings-section');
281 for (var section of sections)
282 section.hidden = hidden && (section.section != sectionName);
283 },
284
285 /**
286 * Helper function to get a section from the local DOM. 235 * Helper function to get a section from the local DOM.
287 * @param {string} section Section name of the element to get. 236 * @param {string} section Section name of the element to get.
288 * @return {?SettingsSectionElement} 237 * @return {?SettingsSectionElement}
289 * @private 238 * @private
290 */ 239 */
291 getSection_: function(section) { 240 getSection_: function(section) {
241 if (!section)
242 return null;
292 return /** @type {?SettingsSectionElement} */( 243 return /** @type {?SettingsSectionElement} */(
293 this.$$('[section=' + section + ']')); 244 this.$$('[section=' + section + ']'));
294 }, 245 },
246
247 /**
248 * Enables or disables user scrolling, via overscroll: hidden. Room for the
249 * hidden scrollbar is added to prevent the page width from changing back and
250 * forth.
251 * @param {boolean} enabled
252 * @private
253 */
254 toggleScrolling_: function(enabled) {
255 if (enabled) {
256 this.scroller.style.overflow = '';
257 this.scroller.style.width = '';
258 } else {
259 var scrollerWidth = this.scroller.clientWidth;
260 this.scroller.style.overflow = 'hidden';
261 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth;
262 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)';
263 }
264 }
295 }; 265 };
296 266
297 /** @polymerBehavior */ 267 /** @polymerBehavior */
298 var MainPageBehavior = [ 268 var MainPageBehavior = [
299 settings.RouteObserverBehavior, 269 settings.RouteObserverBehavior,
300 TransitionBehavior,
301 MainPageBehaviorImpl, 270 MainPageBehaviorImpl,
302 ]; 271 ];
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698