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

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

Issue 2106013002: Move settings-section animations into setting-section, make better (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@Transitions
Patch Set: Refactor 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 // Fast out, slow in.
6 var EASING_FUNCTION = 'cubic-bezier(0.4, 0, 0.2, 1)';
7 var EXPAND_DURATION = 350;
8
9 /** 5 /**
10 * Calls |readyTest| repeatedly until it returns true, then calls 6 * Calls |readyTest| repeatedly until it returns true, then calls
11 * |readyCallback|. 7 * |readyCallback|.
12 * @param {function():boolean} readyTest 8 * @param {function():boolean} readyTest
13 * @param {!Function} readyCallback 9 * @param {!Function} readyCallback
14 */ 10 */
15 function doWhenReady(readyTest, readyCallback) { 11 function doWhenReady(readyTest, readyCallback) {
16 // TODO(dschuyler): Determine whether this hack can be removed. 12 // TODO(dschuyler): Determine whether this hack can be removed.
17 // See also: https://github.com/Polymer/polymer/issues/3629 13 // See also: https://github.com/Polymer/polymer/issues/3629
18 var intervalId = setInterval(function() { 14 var intervalId = setInterval(function() {
19 if (readyTest()) { 15 if (readyTest()) {
20 clearInterval(intervalId); 16 clearInterval(intervalId);
21 readyCallback(); 17 readyCallback();
22 } 18 }
23 }, 10); 19 }, 10);
24 } 20 }
25 21
26 /** 22 /**
27 * Provides animations to expand and collapse individual sections in a page. 23 * Responds to route changes by expanding, collapsing, and scrolling to
28 * Expanded sections take up the full height of the container. At most one 24 * sections. Expanded sections take up the full height of the container. At
29 * section should be expanded at any given time. 25 * most one section should be expanded at any given time.
26 * TODO(michaelpg): Combine with RoutableBehaviorImpl.
30 * @polymerBehavior Polymer.MainPageBehavior 27 * @polymerBehavior Polymer.MainPageBehavior
31 */ 28 */
32 var MainPageBehaviorImpl = { 29 var MainPageBehaviorImpl = {
33 /** @type {?Element} The scrolling container. */
34 scroller: null,
35
36 /** @override */ 30 /** @override */
37 attached: function() { 31 attached: function() {
38 if (this.domHost && this.domHost.parentNode.tagName == 'PAPER-HEADER-PANEL') 32 /** @type {!HTMLElement} */
Dan Beam 2016/08/05 00:18:05 indent off
39 this.scroller = this.domHost.parentNode.$.mainContainer; 33 this.scroller = (this.domHost && this.domHost.parentNode.tagName ==
Dan Beam 2016/08/05 00:18:05 what's the functional difference here? why are yo
40 else 34 'PAPER-HEADER-PANEL') ?
41 this.scroller = document.body; // Used in unit tests. 35 this.domHost.parentNode.$.mainContainer :
Dan Beam 2016/08/05 00:18:05 yeppppppp this'll trigger that presubmit
36 document.body; // Used in unit tests.
42 }, 37 },
43 38
44 /** 39 /**
45 * Hides or unhides the sections not being expanded. 40 * Hides or unhides the sections not being expanded.
46 * @param {string} sectionName The section to keep visible. 41 * @param {string} sectionName The section to keep visible.
47 * @param {boolean} hidden Whether the sections should be hidden. 42 * @param {boolean} hidden Whether the sections should be hidden.
48 * @private 43 * @private
49 */ 44 */
50 toggleOtherSectionsHidden_: function(sectionName, hidden) { 45 toggleOtherSectionsHidden_: function(sectionName, hidden) {
51 var sections = Polymer.dom(this.root).querySelectorAll( 46 var sections = Polymer.dom(this.root).querySelectorAll(
52 'settings-section'); 47 'settings-section');
53 for (var section of sections) 48 for (var section of sections)
54 section.hidden = hidden && (section.section != sectionName); 49 section.hidden = hidden && (section.section != sectionName);
55 }, 50 },
56 51
57 /** 52 /**
53 * Transistions to the current route if the |deferredTransition_| flag is set.
Dan Beam 2016/08/05 00:47:07 Transitions
54 * @private
55 */
56 runDeferredTransition_: function() {
Dan Beam 2016/08/05 00:47:06 this makes it seem as if "deferredTransition_" is
57 // TODO(michaelpg): Manage transition lifetime better. crbug.com/624145
58 if (!this.deferredTransition_)
59 return;
60 this.deferredTransition_ = false;
61 this.transitionToRoute_();
62 },
63
64 /**
65 * Transitions to the current route based on the state of the current page
66 * (i.e., expands and collapses sections as necessary). Must only be called
67 * when no other transition is running or pending.
68 * @private
69 */
70 transitionToRoute_: function() {
71 assert(!this.currentSectionTransition_);
72
73 var expandedSection = /** @type {?SettingsSectionElement} */(
74 this.$$('settings-section.expanded'));
75 if (expandedSection) {
76 if (expandedSection.section == this.currentRoute.section &&
77 this.currentRoute.subpage.length > 0) {
78 // settings-animated-pages controls visibility of subpages in sections.
Dan Beam 2016/08/05 00:47:06 can you describe this in a different, more high-le
79 return;
80 }
81
82 // Collapse this section (and check for a deferred transition when
83 // finished).
84 this.collapseSection(expandedSection).then(
85 this.runDeferredTransition_.bind(this));
86
87 // If the new route is a sub-page, we'll have to expand that next.
88 this.deferredTransition_ = this.currentRoute.subpage.length > 0;
89 return;
90 }
91
92 if (this.currentRoute.section.length == 0)
Dan Beam 2016/08/05 00:47:06 nit: can you just use truthiness for section or ot
93 return;
94
95 var section = this.getSection_(this.currentRoute.section);
96 if (!section)
97 return;
98
99 // Expand the section if the route is a subpage (and check for a deferred
100 // transition when finished).
101 if (this.currentRoute.subpage.length > 0) {
102 this.expandSection(section).then(this.runDeferredTransition_.bind(this));
103 return;
104 }
105
Dan Beam 2016/08/05 00:47:06 can you put a comment saying which cases actually
106 this.scrollToSection_();
107 },
108
109 /**
58 * Animates the card in |section|, expanding it to fill the page. 110 * Animates the card in |section|, expanding it to fill the page.
59 * @param {!SettingsSectionElement} section 111 * @param {!SettingsSectionElement} section
112 * @return {!Promise} Promise when the transition finishes or cancels.
60 */ 113 */
61 expandSection: function(section) { 114 expandSection: function(section) {
62 // If another section's card is expanding, cancel that animation first. 115 assert(!this.currentSectionTransition_);
63 var expanding = this.$$('.expanding');
64 if (expanding) {
65 if (expanding == section)
66 return;
67 116
68 if (this.animations['section']) { 117 // The overscroll must not shrink during the transition.
69 // Cancel the animation, then call startExpandSection_. 118 this.fire('overscroll-suppress');
70 this.cancelAnimation('section', function() {
71 this.startExpandSection_(section);
72 }.bind(this));
73 } else {
74 // The animation must have finished but its promise hasn't resolved yet.
75 // When it resolves, collapse that section's card before expanding
76 // this one.
77 setTimeout(function() {
78 this.collapseSection(
79 /** @type {!SettingsSectionElement} */(expanding));
80 this.finishAnimation('section', function() {
81 this.startExpandSection_(section);
82 }.bind(this));
83 }.bind(this));
84 }
85 119
86 return; 120 // Prevent additional scrolling and keep the expanding section clipped.
87 } 121 this.scroller.style.overflow = '';
122 var scrollerWidth = this.scroller.clientWidth;
123 this.mainScrollTop_ = this.scroller.scrollTop;
124 this.scroller.style.overflow = 'hidden';
88 125
89 if (this.$$('.collapsing') && this.animations['section']) { 126 // Set the width to account for the missing scrollbar.
90 // Finish the collapse animation before expanding.
91 this.finishAnimation('section', function() {
92 this.startExpandSection_(section);
93 }.bind(this));
94 return;
95 }
96
97 this.startExpandSection_(section);
98 },
99
100 /**
101 * Helper function to set up and start the expand animation.
102 * @param {!SettingsSectionElement} section
103 */
104 startExpandSection_: function(section) {
105 if (section.classList.contains('expanded'))
106 return;
107
108 // Freeze the scroller and save its position.
109 this.listScrollTop_ = this.scroller.scrollTop;
110
111 var scrollerWidth = this.scroller.clientWidth;
112 this.scroller.style.overflow = 'hidden';
113 // Adjust width to compensate for scroller.
114 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; 127 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth;
115 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; 128 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)';
116 129
117 // Freezes the section's height so its card can be removed from the flow. 130 this.currentSectionTransition_ =
118 this.freezeSection_(section); 131 new settings.OpenSectionTransition(section, this.scroller);
119 132
120 // Expand the section's card to fill the parent. 133 return this.currentSectionTransition_.play().then(function(success) {
121 var animationPromise = this.playExpandSection_(section); 134 this.currentSectionTransition_ = null;
135 if (success) {
136 // Hide other sections and scroll to the top of the subpage.
137 this.classList.add('showing-subpage');
138 this.toggleOtherSectionsHidden_(section.section, true);
139 this.scroller.scrollTop = 0;
140 // Notify that the page is fully expanded.
141 this.fire('subpage-expand');
142 } else {
143 // The open transition cancelled, so scroll back to our old location.
144 this.scroller.scrollTop = this.mainScrollTop_;
145 }
122 146
123 animationPromise.then(function() { 147 // Overscroll can now be recalculated.
124 this.scroller.scrollTop = 0; 148 this.fire('overscroll-recalc');
125 this.toggleOtherSectionsHidden_(section.section, true); 149
126 }.bind(this), function() { 150 // Restore the scrollbar.
127 // Animation was canceled; restore the section. 151 this.scroller.style.width = '';
128 this.unfreezeSection_(section);
129 }.bind(this)).then(function() {
130 this.scroller.style.overflow = ''; 152 this.scroller.style.overflow = '';
131 this.scroller.style.width = '';
132 }.bind(this)); 153 }.bind(this));
133 }, 154 },
134 155
135 /** 156 /**
136 * Animates the card in |section|, collapsing it back into its section. 157 * Animates the card in |section|, collapsing it back into its section.
137 * @param {!SettingsSectionElement} section 158 * @param {!SettingsSectionElement} section
159 * @return {!Promise} Promise when the transition finishes or cancels.
138 */ 160 */
139 collapseSection: function(section) { 161 collapseSection: function(section) {
140 // If the section's card is still expanding, cancel the expand animation. 162 assert(!this.currentSectionTransition_);
141 if (section.classList.contains('expanding')) { 163 assert(section.classList.contains('expanded'));
142 if (this.animations['section']) {
143 this.cancelAnimation('section');
144 } else {
145 // The animation must have finished but its promise hasn't finished
146 // resolving; try again asynchronously.
147 this.async(function() {
148 this.collapseSection(section);
149 });
150 }
151 return;
152 }
153 164
154 if (!section.classList.contains('expanded')) 165 // TODO(michaelpg): Minimize horizontal motion when scrollbar changes for
155 return; 166 // the common cases.
167 this.scroller.style.overflow = 'hidden';
168
169 // Set up the close transition first, which takes the section out of the
170 // flow, before showing everything.
171 this.currentSectionTransition_ =
172 new settings.CloseSectionTransition(section, this.scroller);
173 this.currentSectionTransition_.setUp();
156 174
157 this.toggleOtherSectionsHidden_(section.section, false); 175 this.toggleOtherSectionsHidden_(section.section, false);
176 this.classList.remove('showing-subpage');
177 this.fire('overscroll-recalc');
158 178
159 var scrollerWidth = this.scroller.clientWidth; 179 return new Promise(function(resolve, reject) {
160 this.scroller.style.overflow = 'hidden'; 180 // Wait for the other sections to show up so we can scroll properly.
161 // Adjust width to compensate for scroller. 181 setTimeout(function() {
162 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; 182 // Allow overscroll adjustment so it changes now rather than after the
163 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; 183 // transition has begun.
164 184
165 this.playCollapseSection_(section).then(function() { 185 var newSection = this.currentRoute.section &&
166 this.unfreezeSection_(section); 186 this.getSection_(this.currentRoute.section);
167 this.scroller.style.overflow = ''; 187
168 this.scroller.style.width = ''; 188 // Scroll to the section if indicated by the route. TODO(dschuyler): Is
169 section.classList.remove('collapsing'); 189 // this the right behavior, or should we return to the previous scroll
190 // position?
191 if (newSection)
192 newSection.scrollIntoView();
193 else
194 this.scroller.scrollTop = this.mainScrollTop_;
195
196 this.currentSectionTransition_.play().then(function(success) {
197 this.currentSectionTransition_ = null;
198 if (!success) {
199 // The collapse was canceled, so ensure the page is still set up for
200 // a full-height section.
201 this.fire('subpage-expand');
202 }
203
204 // Restore the scrollbar.
205 this.scroller.style.overflow = '';
206 this.scroller.style.width = '';
Dan Beam 2016/08/05 00:47:06 marginally useful: make a lockScrolling_() and unl
207 resolve();
208 }.bind(this));
209 }.bind(this));
170 }.bind(this)); 210 }.bind(this));
171 }, 211 },
172
173 /**
174 * Freezes a section's height so its card can be removed from the flow without
175 * affecting the layout of the surrounding sections.
176 * @param {!SettingsSectionElement} section
177 * @private
178 */
179 freezeSection_: function(section) {
180 var card = section.$.card;
181 section.style.height = section.clientHeight + 'px';
182
183 var cardHeight = card.offsetHeight;
184 var cardWidth = card.offsetWidth;
185 // If the section is not displayed yet (e.g., navigated directly to a
186 // sub-page), cardHeight and cardWidth are 0, so do not set the height or
187 // width explicitly.
188 // TODO(michaelpg): Improve this logic when refactoring
189 // settings-animated-pages.
190 if (cardHeight && cardWidth) {
191 // TODO(michaelpg): Temporary hack to store the height the section should
192 // collapse to when it closes.
193 card.origHeight_ = cardHeight;
194
195 card.style.height = cardHeight + 'px';
196 card.style.width = cardWidth + 'px';
197 } else {
198 // Set an invalid value so we don't try to use it later.
199 card.origHeight_ = NaN;
200 }
201
202 // Place the section's card at its current position but removed from the
203 // flow.
204 card.style.top = card.getBoundingClientRect().top + 'px';
205 section.classList.add('frozen');
206 },
207
208 /**
209 * After freezeSection_, restores the section to its normal height.
210 * @param {!SettingsSectionElement} section
211 * @private
212 */
213 unfreezeSection_: function(section) {
214 if (!section.classList.contains('frozen'))
215 return;
216 var card = section.$.card;
217 section.classList.remove('frozen');
218 card.style.top = '';
219 card.style.height = '';
220 card.style.width = '';
221 section.style.height = '';
222 },
223
224 /**
225 * Expands the card in |section| to fill the page.
226 * @param {!SettingsSectionElement} section
227 * @return {!Promise}
228 * @private
229 */
230 playExpandSection_: function(section) {
231 var card = section.$.card;
232
233 // The card should start at the top of the page.
234 var targetTop = this.scroller.getBoundingClientRect().top;
235
236 section.classList.add('expanding');
237
238 // Expand the card, using minHeight. (The card must span the container's
239 // client height, so it must be at least 100% in case the card is too short.
240 // If the card is already taller than the container's client height, we
241 // don't want to shrink the card to 100% or the content will overflow, so
242 // we can't use height, and animating height wouldn't look right anyway.)
243 var keyframes = [{
244 top: card.style.top,
245 minHeight: card.style.height,
246 easing: EASING_FUNCTION,
247 }, {
248 top: targetTop + 'px',
249 minHeight: 'calc(100% - ' + targetTop + 'px)',
250 }];
251 var options = /** @type {!KeyframeEffectOptions} */({
252 duration: EXPAND_DURATION
253 });
254 // TODO(michaelpg): Change elevation of sections.
255 var promise;
256 if (keyframes[0].top && keyframes[0].minHeight)
257 promise = this.animateElement('section', card, keyframes, options);
258 else
259 promise = Promise.resolve();
260
261 promise.then(function() {
262 section.classList.add('expanded');
263 card.style.top = '';
264 this.style.margin = 'auto';
265 section.$.header.hidden = true;
266 section.style.height = '';
267 }.bind(this), function() {
268 // The animation was canceled; catch the error and continue.
269 }).then(function() {
270 // Whether finished or canceled, clean up the animation.
271 section.classList.remove('expanding');
272 card.style.height = '';
273 card.style.width = '';
274 });
275
276 return promise;
277 },
278
279 /**
280 * Collapses the card in |section| back to its normal position.
281 * @param {!SettingsSectionElement} section
282 * @return {!Promise}
283 * @private
284 */
285 playCollapseSection_: function(section) {
286 var card = section.$.card;
287
288 this.style.margin = '';
289 section.$.header.hidden = false;
290
291 var startingTop = this.scroller.getBoundingClientRect().top;
292
293 var cardHeightStart = card.clientHeight;
294 var cardWidthStart = card.clientWidth;
295
296 section.classList.add('collapsing');
297 section.classList.remove('expanding', 'expanded');
298
299 // If we navigated here directly, we don't know the original height of the
300 // section, so we skip the animation.
301 // TODO(michaelpg): remove this condition once sliding is implemented.
302 if (isNaN(card.origHeight_))
303 return Promise.resolve();
304
305 // Restore the section to its proper height to make room for the card.
306 section.style.height = section.clientHeight + card.origHeight_ + 'px';
307
308 // TODO(michaelpg): this should be in collapseSection(), but we need to wait
309 // until the full page height is available (setting the section height).
310 this.scroller.scrollTop = this.listScrollTop_;
311
312 // The card is unpositioned, so use its position as the ending state,
313 // but account for scroll.
314 var targetTop = card.getBoundingClientRect().top - this.scroller.scrollTop;
315
316 // Account for the section header.
317 var headerStyle = getComputedStyle(section.$.header);
318 targetTop += section.$.header.offsetHeight +
319 parseInt(headerStyle.marginBottom, 10) +
320 parseInt(headerStyle.marginTop, 10);
321
322 var keyframes = [{
323 top: startingTop + 'px',
324 minHeight: cardHeightStart + 'px',
325 easing: EASING_FUNCTION,
326 }, {
327 top: targetTop + 'px',
328 minHeight: card.origHeight_ + 'px',
329 }];
330 var options = /** @type {!KeyframeEffectOptions} */({
331 duration: EXPAND_DURATION
332 });
333
334 card.style.width = cardWidthStart + 'px';
335 var promise = this.animateElement('section', card, keyframes, options);
336 promise.then(function() {
337 card.style.width = '';
338 });
339 return promise;
340 },
341 }; 212 };
342 213
343 214
344 /** @polymerBehavior */ 215 /** @polymerBehavior */
345 var MainPageBehavior = [ 216 var MainPageBehavior = [
346 TransitionBehavior,
347 MainPageBehaviorImpl 217 MainPageBehaviorImpl
348 ]; 218 ];
349 219
350 220
351 /** 221 /**
352 * TODO(michaelpg): integrate slide animations. 222 * TODO(michaelpg): integrate slide animations.
353 * @polymerBehavior RoutableBehavior 223 * @polymerBehavior RoutableBehavior
354 */ 224 */
355 var RoutableBehaviorImpl = { 225 var RoutableBehaviorImpl = {
356 properties: { 226 properties: {
357 /** Contains the current route. */ 227 /** Contains the current route. */
358 currentRoute: { 228 currentRoute: {
359 type: Object, 229 type: Object,
360 notify: true, 230 notify: true,
361 observer: 'currentRouteChanged_', 231 observer: 'currentRouteChanged_',
362 }, 232 },
363 }, 233 },
364 234
365 /** @private */ 235 /** @private */
366 scrollToSection_: function() { 236 scrollToSection_: function() {
367 doWhenReady( 237 doWhenReady(
368 function() { 238 function() {
369 return this.scrollHeight > 0; 239 return this.scrollHeight > 0;
370 }.bind(this), 240 }.bind(this),
371 function() { 241 function() {
372 // If the current section changes while we are waiting for the page to 242 // If the current section changes while we are waiting for the page to
373 // be ready, scroll to the newest requested section. 243 // be ready, scroll to the newest requested section.
374 this.getSection_(this.currentRoute.section).scrollIntoView(); 244 var section = this.getSection_(this.currentRoute.section);
245 if (section)
246 section.scrollIntoView();
375 }.bind(this)); 247 }.bind(this));
376 }, 248 },
377 249
378 /** @private */ 250 /**
251 * @param {?settings.Route} newRoute
252 * @param {?settings.Route} oldRoute
253 * @private
254 */
379 currentRouteChanged_: function(newRoute, oldRoute) { 255 currentRouteChanged_: function(newRoute, oldRoute) {
380 var newRouteIsSubpage = newRoute && newRoute.subpage.length; 256 if (!oldRoute && newRoute && newRoute.subpage.length > 0) {
381 var oldRouteIsSubpage = oldRoute && oldRoute.subpage.length;
382
383 if (!oldRoute && newRouteIsSubpage) {
384 // Allow the page to load before expanding the section. TODO(michaelpg): 257 // Allow the page to load before expanding the section. TODO(michaelpg):
385 // Time this better when refactoring settings-animated-pages. 258 // Time this better when refactoring settings-animated-pages.
386 setTimeout(function() { 259 this.deferredTransition_ = true;
387 var section = this.getSection_(newRoute.section); 260 setTimeout(this.runDeferredTransition_.bind(this));
388 if (section)
389 this.expandSection(section);
390 }.bind(this));
391 return; 261 return;
392 } 262 }
393 263
394 if (newRouteIsSubpage) { 264 if (this.currentSectionTransition_) {
395 if (!oldRouteIsSubpage || newRoute.section != oldRoute.section) { 265 // Transition to the current route when the current one finishes.
396 var section = this.getSection_(newRoute.section); 266 this.deferredTransition_ = true;
397 if (section) 267
398 this.expandSection(section); 268 // If the current transition is opening a section we want to close, cancel
269 // that transition immediately.
270 if (this.currentSectionTransition_ instanceof
271 settings.OpenSectionTransition &&
272 (this.currentSectionTransition_.section != newRoute.section ||
273 newRoute.subpage.length == 0)) {
Dan Beam 2016/08/05 00:47:07 can you use some temporary variables for this?
274 this.currentSectionTransition_.cancel();
399 } 275 }
400 } else { 276 return;
401 if (oldRouteIsSubpage) { 277 }
402 var section = this.getSection_(oldRoute.section);
403 if (section)
404 this.collapseSection(section);
405 }
406 278
407 // Scrolls to the section if this main page contains the route's section. 279 this.transitionToRoute_();
408 if (newRoute && newRoute.section && this.getSection_(newRoute.section))
409 this.scrollToSection_();
410 }
411 }, 280 },
412 281
413 /** 282 /**
414 * Helper function to get a section from the local DOM. 283 * Helper function to get a section from the local DOM.
415 * @param {string} section Section name of the element to get. 284 * @param {string} section Section name of the element to get.
416 * @return {?SettingsSectionElement} 285 * @return {?SettingsSectionElement}
417 * @private 286 * @private
418 */ 287 */
419 getSection_: function(section) { 288 getSection_: function(section) {
420 return /** @type {?SettingsSectionElement} */( 289 return /** @type {?SettingsSectionElement} */(
421 this.$$('[section=' + section + ']')); 290 this.$$('[section=' + section + ']'));
422 }, 291 },
423 }; 292 };
424 293
425 294
426 /** @polymerBehavior */ 295 /** @polymerBehavior */
427 var RoutableBehavior = [ 296 var RoutableBehavior = [
428 MainPageBehavior, 297 MainPageBehavior,
429 RoutableBehaviorImpl 298 RoutableBehaviorImpl
430 ]; 299 ];
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698