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

Side by Side Diff: chrome/browser/resources/settings/route.js

Issue 2957153003: MD Settings: remove unsupported routes from guest-mode. (Closed)
Patch Set: safeguard all settings.routes.[ROUTE]. dereferencing Created 3 years, 5 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 /**
6 * Specifies available routes in settings.
dpapad 2017/07/06 02:07:06 Nit: Can we clarify this comment a bit? s/availabl
scottchen 2017/07/07 20:58:45 Done.
7 *
8 * @typedef {{
9 * ABOUT: (undefined|!settings.Route),
10 * ABOUT_ABOUT: (undefined|!settings.Route),
11 * ACCESSIBILITY: (undefined|!settings.Route),
12 * ACCOUNTS: (undefined|!settings.Route),
13 * ADVANCED: (undefined|!settings.Route),
14 * ANDROID_APPS: (undefined|!settings.Route),
15 * ANDROID_APPS_DETAILS: (undefined|!settings.Route),
16 * APPEARANCE: (undefined|!settings.Route),
17 * AUTOFILL: (undefined|!settings.Route),
18 * BASIC: (undefined|!settings.Route),
19 * BLUETOOTH: (undefined|!settings.Route),
20 * BLUETOOTH_DEVICES: (undefined|!settings.Route),
21 * CERTIFICATES: (undefined|!settings.Route),
22 * CHANGE_PICTURE: (undefined|!settings.Route),
23 * CLEAR_BROWSER_DATA: (undefined|!settings.Route),
24 * CLOUD_PRINTERS: (undefined|!settings.Route),
25 * CUPS_PRINTER_DETAIL: (undefined|!settings.Route),
26 * CUPS_PRINTERS: (undefined|!settings.Route),
27 * DATETIME: (undefined|!settings.Route),
28 * DEFAULT_BROWSER: (undefined|!settings.Route),
29 * DETAILED_BUILD_INFO: (undefined|!settings.Route),
30 * DEVICE: (undefined|!settings.Route),
31 * DISPLAY: (undefined|!settings.Route),
32 * DOWNLOADS: (undefined|!settings.Route),
33 * EDIT_DICTIONARY: (undefined|!settings.Route),
34 * FINGERPRINT: (undefined|!settings.Route),
35 * FONTS: (undefined|!settings.Route),
36 * IMPORT_DATA: (undefined|!settings.Route),
37 * INPUT_METHODS: (undefined|!settings.Route),
38 * INTERNET: (undefined|!settings.Route),
39 * INTERNET_NETWORKS: (undefined|!settings.Route),
40 * KEYBOARD: (undefined|!settings.Route),
41 * KNOWN_NETWORKS: (undefined|!settings.Route),
42 * LANGUAGES: (undefined|!settings.Route),
43 * LOCK_SCREEN: (undefined|!settings.Route),
44 * MANAGE_ACCESSIBILITY: (undefined|!settings.Route),
45 * MANAGE_PASSWORDS: (undefined|!settings.Route),
46 * MANAGE_PROFILE: (undefined|!settings.Route),
47 * NETWORK_CONFIG: (undefined|!settings.Route),
48 * NETWORK_DETAIL: (undefined|!settings.Route),
49 * ON_STARTUP: (undefined|!settings.Route),
50 * PASSWORDS: (undefined|!settings.Route),
51 * PEOPLE: (undefined|!settings.Route),
52 * POINTERS: (undefined|!settings.Route),
53 * POWER: (undefined|!settings.Route),
54 * PRINTING: (undefined|!settings.Route),
55 * PRIVACY: (undefined|!settings.Route),
56 * RESET: (undefined|!settings.Route),
57 * RESET_DIALOG: (undefined|!settings.Route),
58 * SEARCH: (undefined|!settings.Route),
59 * SEARCH_ENGINES: (undefined|!settings.Route),
60 * SIGN_OUT: (undefined|!settings.Route),
61 * SITE_SETTINGS: (undefined|!settings.Route),
62 * SITE_SETTINGS_ADS: (undefined|!settings.Route),
63 * SITE_SETTINGS_ALL: (undefined|!settings.Route),
64 * SITE_SETTINGS_AUTOMATIC_DOWNLOADS: (undefined|!settings.Route),
65 * SITE_SETTINGS_BACKGROUND_SYNC: (undefined|!settings.Route),
66 * SITE_SETTINGS_CAMERA: (undefined|!settings.Route),
67 * SITE_SETTINGS_COOKIES: (undefined|!settings.Route),
68 * SITE_SETTINGS_DATA_DETAILS: (undefined|!settings.Route),
69 * SITE_SETTINGS_FLASH: (undefined|!settings.Route),
70 * SITE_SETTINGS_HANDLERS: (undefined|!settings.Route),
71 * SITE_SETTINGS_IMAGES: (undefined|!settings.Route),
72 * SITE_SETTINGS_JAVASCRIPT: (undefined|!settings.Route),
73 * SITE_SETTINGS_LOCATION: (undefined|!settings.Route),
74 * SITE_SETTINGS_MICROPHONE: (undefined|!settings.Route),
75 * SITE_SETTINGS_MIDI_DEVICES: (undefined|!settings.Route),
76 * SITE_SETTINGS_NOTIFICATIONS: (undefined|!settings.Route),
77 * SITE_SETTINGS_PDF_DOCUMENTS: (undefined|!settings.Route),
78 * SITE_SETTINGS_POPUPS: (undefined|!settings.Route),
79 * SITE_SETTINGS_PROTECTED_CONTENT: (undefined|!settings.Route),
80 * SITE_SETTINGS_SITE_DETAILS: (undefined|!settings.Route),
81 * SITE_SETTINGS_UNSANDBOXED_PLUGINS: (undefined|!settings.Route),
82 * SITE_SETTINGS_USB_DEVICES: (undefined|!settings.Route),
83 * SITE_SETTINGS_ZOOM_LEVELS: (undefined|!settings.Route),
84 * STORAGE: (undefined|!settings.Route),
85 * STYLUS: (undefined|!settings.Route),
86 * SYNC: (undefined|!settings.Route),
87 * SYSTEM: (undefined|!settings.Route),
88 * TRIGGERED_RESET_DIALOG: (undefined|!settings.Route),
89 * }}
90 */
91 var SettingsRoutes;
92
5 cr.define('settings', function() { 93 cr.define('settings', function() {
94
6 /** 95 /**
7 * Class for navigable routes. May only be instantiated within this file. 96 * Class for navigable routes. May only be instantiated within this file.
8 * @constructor
9 * @param {string} path
10 * @private
11 */ 97 */
12 var Route = function(path) { 98 class Route {
13 this.path = path; 99 /**
100 * @param {string} path
dpapad 2017/07/06 02:07:06 Nit: Maybe shorten as follows: /** @param {string
scottchen 2017/07/07 20:58:44 Done.
101 */
102 constructor(path) {
103 /** @type {string} */
104 this.path = path;
14 105
15 /** @type {?settings.Route} */ 106 /** @type {?settings.Route} */
16 this.parent = null; 107 this.parent = null;
17 108
18 /** @type {number} */ 109 /** @type {number} */
19 this.depth = 0; 110 this.depth = 0;
20 111
21 /** 112 /**
22 * @type {boolean} Whether this route corresponds to a navigable 113 * @type {boolean} Whether this route corresponds to a navigable
23 * dialog. Those routes don't belong to a "section". 114 * dialog. Those routes don't belong to a "section".
24 */ 115 */
25 this.isNavigableDialog = false; 116 this.isNavigableDialog = false;
26 117
27 // Below are all legacy properties to provide compatibility with the old 118 // Below are all legacy properties to provide compatibility with the old
28 // routing system. 119 // routing system.
29 120
30 /** @type {string} */ 121 /** @type {string} */
31 this.section = ''; 122 this.section = '';
32 }; 123 }
33 124
34 Route.prototype = {
35 /** 125 /**
36 * Returns a new Route instance that's a child of this route. 126 * Returns a new Route instance that's a child of this route.
37 * @param {string} path Extends this route's path if it doesn't contain a 127 * @param {string} path Extends this route's path if it doesn't contain a
38 * leading slash. 128 * leading slash.
39 * @return {!settings.Route} 129 * @return {!settings.Route}
40 * @private 130 * @private
41 */ 131 */
42 createChild: function(path) { 132 createChild(path) {
43 assert(path); 133 assert(path);
44 134
45 // |path| extends this route's path if it doesn't have a leading slash. 135 // |path| extends this route's path if it doesn't have a leading slash.
46 // If it does have a leading slash, it's just set as the new route's URL. 136 // If it does have a leading slash, it's just set as the new route's URL.
47 var newUrl = path[0] == '/' ? path : this.path + '/' + path; 137 var newUrl = path[0] == '/' ? path : this.path + '/' + path;
48 138
49 var route = new Route(newUrl); 139 var route = new Route(newUrl);
50 route.parent = this; 140 route.parent = this;
51 route.section = this.section; 141 route.section = this.section;
52 route.depth = this.depth + 1; 142 route.depth = this.depth + 1;
53 143
54 return route; 144 return route;
55 }, 145 }
56 146
57 /** 147 /**
58 * Returns a new Route instance that's a child section of this route. 148 * Returns a new Route instance that's a child section of this route.
59 * TODO(tommycli): Remove once we've obsoleted the concept of sections. 149 * TODO(tommycli): Remove once we've obsoleted the concept of sections.
60 * @param {string} path 150 * @param {string} path
61 * @param {string} section 151 * @param {string} section
62 * @return {!settings.Route} 152 * @return {!settings.Route}
63 * @private 153 * @private
64 */ 154 */
65 createSection: function(path, section) { 155 createSection(path, section) {
66 var route = this.createChild(path); 156 var route = this.createChild(path);
67 route.section = section; 157 route.section = section;
68 return route; 158 return route;
69 }, 159 }
70 160
71 /** 161 /**
72 * Returns true if this route matches or is an ancestor of the parameter. 162 * Returns true if this route matches or is an ancestor of the parameter.
73 * @param {!settings.Route} route 163 * @param {!settings.Route} route
74 * @return {boolean} 164 * @return {boolean}
75 */ 165 */
76 contains: function(route) { 166 contains(route) {
77 for (var r = route; r != null; r = r.parent) { 167 for (var r = route; r != null; r = r.parent) {
78 if (this == r) 168 if (this == r)
79 return true; 169 return true;
80 } 170 }
81 return false; 171 return false;
82 }, 172 }
83 173
84 /** 174 /**
85 * Returns true if this route is a subpage of a section. 175 * Returns true if this route is a subpage of a section.
86 * @return {boolean} 176 * @return {boolean}
87 */ 177 */
88 isSubpage: function() { 178 isSubpage() {
89 return !!this.parent && !!this.section && 179 return !!this.parent && !!this.section &&
90 this.parent.section == this.section; 180 this.parent.section == this.section;
91 }, 181 }
182 }
183
184 class Router {
185 constructor() {
186 /** @private {!SettingsRoutes} */
dpapad 2017/07/06 02:07:06 Let's add a comment to differentiate between route
scottchen 2017/07/07 20:58:44 Done.
187 this.routes_ = {};
dpapad 2017/07/06 02:07:06 Not sure if this is worth doing, but mentioning it
scottchen 2017/07/07 20:58:44 I was going to use Router_.initializeRoutes() in t
dpapad 2017/07/07 21:19:30 SG. Consider this optional.
188 this.initializeRoutes();
189
190 /**
191 * The current active route. This updated is only by settings.navigateTo
192 * or settings.initializeRouteFromUrl.
193 * @type {!settings.Route}
194 */
195 this.currentRoute = this.routes_.BASIC;
196
197 /**
198 * The current query parameters. This is updated only by
199 * settings.navigateTo or settings.initializeRouteFromUrl.
200 * @private {!URLSearchParams}
201 */
202 this.currentQueryParameters_ = new URLSearchParams();
203
204 /** @private {boolean} */
205 this.wasLastRouteChangePopstate_ = false;
206
207 /** @private {boolean}*/
208 this.initializeRouteFromUrlCalled_ = false;
209 }
210
211 initializeRoutes() {
dpapad 2017/07/06 02:07:06 Can this be private?
scottchen 2017/07/07 20:58:44 Moving this method out of the class definition.
212 var pageVisibility = settings.pageVisibility || {};
213
214 // Abbreviated variable for easier definitions.
215 var r = this.routes_;
216
217 // Root pages.
218 r.BASIC = new Route('/');
219 r.ABOUT = new Route('/help');
220
221 // Navigable dialogs. These are the only non-section children of root
222 // pages. These are disfavored. If we add anymore, we should add explicit
223 // support.
224 r.IMPORT_DATA = r.BASIC.createChild('/importData');
225 r.IMPORT_DATA.isNavigableDialog = true;
226 r.SIGN_OUT = r.BASIC.createChild('/signOut');
227 r.SIGN_OUT.isNavigableDialog = true;
228
229 // <if expr="chromeos">
230 r.INTERNET = r.BASIC.createSection('/internet', 'internet');
231 r.INTERNET_NETWORKS = r.INTERNET.createChild('/networks');
232 r.NETWORK_CONFIG = r.INTERNET.createChild('/networkConfig');
233 r.NETWORK_DETAIL = r.INTERNET.createChild('/networkDetail');
234 r.KNOWN_NETWORKS = r.INTERNET.createChild('/knownNetworks');
235 r.BLUETOOTH = r.BASIC.createSection('/bluetooth', 'bluetooth');
236 r.BLUETOOTH_DEVICES = r.BLUETOOTH.createChild('/bluetoothDevices');
237 // </if>
238
239 if (pageVisibility.appearance !== false) {
240 r.APPEARANCE = r.BASIC.createSection('/appearance', 'appearance');
241 r.FONTS = r.APPEARANCE.createChild('/fonts');
242 }
243
244 if (pageVisibility.defaultBrowser !== false) {
245 r.DEFAULT_BROWSER =
246 r.BASIC.createSection('/defaultBrowser', 'defaultBrowser');
247 }
248
249 r.SEARCH = r.BASIC.createSection('/search', 'search');
250 r.SEARCH_ENGINES = r.SEARCH.createChild('/searchEngines');
251
252 // <if expr="chromeos">
253 r.ANDROID_APPS = r.BASIC.createSection('/androidApps', 'androidApps');
254 r.ANDROID_APPS_DETAILS =
255 r.ANDROID_APPS.createChild('/androidApps/details');
256 // </if>
257
258 if (pageVisibility.onStartup !== false) {
259 r.ON_STARTUP = r.BASIC.createSection('/onStartup', 'onStartup');
260 }
261
262 if (pageVisibility.people !== false) {
263 r.PEOPLE = r.BASIC.createSection('/people', 'people');
264 r.SYNC = r.PEOPLE.createChild('/syncSetup');
265 // <if expr="not chromeos">
266 r.MANAGE_PROFILE = r.PEOPLE.createChild('/manageProfile');
267 // </if>
268 // <if expr="chromeos">
269 r.CHANGE_PICTURE = r.PEOPLE.createChild('/changePicture');
270 r.ACCOUNTS = r.PEOPLE.createChild('/accounts');
271 r.LOCK_SCREEN = r.PEOPLE.createChild('/lockScreen');
272 r.FINGERPRINT = r.LOCK_SCREEN.createChild('/lockScreen/fingerprint');
273 // </if>
274 }
275
276 // <if expr="chromeos">
277 r.DEVICE = r.BASIC.createSection('/device', 'device');
278 r.POINTERS = r.DEVICE.createChild('/pointer-overlay');
279 r.KEYBOARD = r.DEVICE.createChild('/keyboard-overlay');
280 r.STYLUS = r.DEVICE.createChild('/stylus');
281 r.DISPLAY = r.DEVICE.createChild('/display');
282 r.STORAGE = r.DEVICE.createChild('/storage');
283 r.POWER = r.DEVICE.createChild('/power');
284 // </if>
285
286 // Advacned Routes
287 if (pageVisibility.advancedSettings !== false) {
288 r.ADVANCED = new Route('/advanced');
289
290 r.CLEAR_BROWSER_DATA = r.ADVANCED.createChild('/clearBrowserData');
291 r.CLEAR_BROWSER_DATA.isNavigableDialog = true;
292
293 if (pageVisibility.privacy !== false) {
294 r.PRIVACY = r.ADVANCED.createSection('/privacy', 'privacy');
295 r.CERTIFICATES = r.PRIVACY.createChild('/certificates');
296 r.SITE_SETTINGS = r.PRIVACY.createChild('/content');
297 }
298
299 if (loadTimeData.getBoolean('enableSiteSettings')) {
300 r.SITE_SETTINGS_ALL = r.SITE_SETTINGS.createChild('all');
301 r.SITE_SETTINGS_SITE_DETAILS =
302 r.SITE_SETTINGS_ALL.createChild('/content/siteDetails');
303 } else if (loadTimeData.getBoolean('enableSiteDetails')) {
304 // When there is no "All Sites", pressing 'back' from "Site Details"
305 // should return to "Content Settings". This should only occur when
306 // |kSiteSettings| is off and |kSiteDetails| is on.
307 r.SITE_SETTINGS_SITE_DETAILS =
308 r.SITE_SETTINGS.createChild('/content/siteDetails');
309 }
310
311 r.SITE_SETTINGS_HANDLERS = r.SITE_SETTINGS.createChild('/handlers');
312
313 // TODO(tommycli): Find a way to refactor these repetitive category
314 // routes.
315 r.SITE_SETTINGS_ADS = r.SITE_SETTINGS.createChild('ads');
316 r.SITE_SETTINGS_AUTOMATIC_DOWNLOADS =
317 r.SITE_SETTINGS.createChild('automaticDownloads');
318 r.SITE_SETTINGS_BACKGROUND_SYNC =
319 r.SITE_SETTINGS.createChild('backgroundSync');
320 r.SITE_SETTINGS_CAMERA = r.SITE_SETTINGS.createChild('camera');
321 r.SITE_SETTINGS_COOKIES = r.SITE_SETTINGS.createChild('cookies');
322 r.SITE_SETTINGS_DATA_DETAILS =
323 r.SITE_SETTINGS_COOKIES.createChild('/cookies/detail');
324 r.SITE_SETTINGS_IMAGES = r.SITE_SETTINGS.createChild('images');
325 r.SITE_SETTINGS_JAVASCRIPT = r.SITE_SETTINGS.createChild('javascript');
326 r.SITE_SETTINGS_LOCATION = r.SITE_SETTINGS.createChild('location');
327 r.SITE_SETTINGS_MICROPHONE = r.SITE_SETTINGS.createChild('microphone');
328 r.SITE_SETTINGS_NOTIFICATIONS =
329 r.SITE_SETTINGS.createChild('notifications');
330 r.SITE_SETTINGS_FLASH = r.SITE_SETTINGS.createChild('flash');
331 r.SITE_SETTINGS_POPUPS = r.SITE_SETTINGS.createChild('popups');
332 r.SITE_SETTINGS_UNSANDBOXED_PLUGINS =
333 r.SITE_SETTINGS.createChild('unsandboxedPlugins');
334 r.SITE_SETTINGS_MIDI_DEVICES =
335 r.SITE_SETTINGS.createChild('midiDevices');
336 r.SITE_SETTINGS_USB_DEVICES = r.SITE_SETTINGS.createChild('usbDevices');
337 r.SITE_SETTINGS_ZOOM_LEVELS = r.SITE_SETTINGS.createChild('zoomLevels');
338 r.SITE_SETTINGS_PDF_DOCUMENTS =
339 r.SITE_SETTINGS.createChild('pdfDocuments');
340 r.SITE_SETTINGS_PROTECTED_CONTENT =
341 r.SITE_SETTINGS.createChild('protectedContent');
342
343 // <if expr="chromeos">
344 if (pageVisibility.dateTime !== false) {
345 r.DATETIME = r.ADVANCED.createSection('/dateTime', 'dateTime');
346 }
347 // </if>
348
349 if (pageVisibility.passwordsAndForms !== false) {
350 r.PASSWORDS = r.ADVANCED.createSection(
351 '/passwordsAndForms', 'passwordsAndForms');
352 r.AUTOFILL = r.PASSWORDS.createChild('/autofill');
353 r.MANAGE_PASSWORDS = r.PASSWORDS.createChild('/passwords');
354 }
355
356 r.LANGUAGES = r.ADVANCED.createSection('/languages', 'languages');
357 // <if expr="chromeos">
358 r.INPUT_METHODS = r.LANGUAGES.createChild('/inputMethods');
359 // </if>
360 // <if expr="not is_macosx">
361 r.EDIT_DICTIONARY = r.LANGUAGES.createChild('/editDictionary');
362 // </if>
363
364 if (pageVisibility.downloads !== false) {
365 r.DOWNLOADS = r.ADVANCED.createSection('/downloads', 'downloads');
366 }
367
368 r.PRINTING = r.ADVANCED.createSection('/printing', 'printing');
369 r.CLOUD_PRINTERS = r.PRINTING.createChild('/cloudPrinters');
370 // <if expr="chromeos">
371 r.CUPS_PRINTERS = r.PRINTING.createChild('/cupsPrinters');
372 r.CUPS_PRINTER_DETAIL =
373 r.CUPS_PRINTERS.createChild('/cupsPrinterDetails');
374 // </if>
375
376 r.ACCESSIBILITY = r.ADVANCED.createSection('/accessibility', 'a11y');
377 // <if expr="chromeos">
378 r.MANAGE_ACCESSIBILITY =
379 r.ACCESSIBILITY.createChild('/manageAccessibility');
380 // </if>
381
382 r.SYSTEM = r.ADVANCED.createSection('/system', 'system');
383
384 if (pageVisibility.reset !== false) {
385 r.RESET = r.ADVANCED.createSection('/reset', 'reset');
386 r.RESET_DIALOG = r.ADVANCED.createChild('/resetProfileSettings');
387 r.RESET_DIALOG.isNavigableDialog = true;
388 r.TRIGGERED_RESET_DIALOG =
389 r.ADVANCED.createChild('/triggeredResetProfileSettings');
390 r.TRIGGERED_RESET_DIALOG.isNavigableDialog = true;
391 }
392 }
393
394 // <if expr="chromeos">
395 // "About" is the only section in About, but we still need to create the
396 // route in order to show the subpage on Chrome OS.
397 r.ABOUT_ABOUT = r.ABOUT.createSection('/help/about', 'about');
398 r.DETAILED_BUILD_INFO = r.ABOUT_ABOUT.createChild('/help/details');
399 // </if>
400 }
401
402 /** @return {settings.Route} */
403 getRoute(routeName) {
404 return this.routes_[routeName];
405 }
406
407 /** @return {!SettingsRoutes} */
408 getRoutes() {
409 return this.routes_;
410 }
411
412 /**
413 * Helper function to set the current route and notify all observers.
414 * @param {!settings.Route} route
415 * @param {!URLSearchParams} queryParameters
416 * @param {boolean} isPopstate
417 */
418 setCurrentRoute(route, queryParameters, isPopstate) {
419 var oldRoute = this.currentRoute;
420 this.currentRoute = route;
421 this.currentQueryParameters_ = queryParameters;
422 this.wasLastRouteChangePopstate_ = isPopstate;
423 routeObservers.forEach(function(observer) {
424 observer.currentRouteChanged(this.currentRoute, oldRoute);
425 }.bind(this));
426 }
427
428 /** @return {!settings.Route} */
429 getCurrentRoute() {
430 return this.currentRoute;
431 }
432
433 /** @return {!URLSearchParams} */
434 getQueryParameters() {
435 return new URLSearchParams(
436 this.currentQueryParameters_); // Defensive copy.
437 }
438
439 /** @return {boolean} */
440 lastRouteChangeWasPopstate() {
441 return this.wasLastRouteChangePopstate_;
442 }
443
444 /**
445 * @param {string} path
446 * @return {?settings.Route} The matching canonical route, or null if none
447 * matches.
448 */
449 getRouteForPath(path) {
450 // Allow trailing slash in paths.
451 var canonicalPath = path.replace(CANONICAL_PATH_REGEX, '$1$2');
452
453 // TODO(tommycli): Use Object.values once Closure compilation supports it.
454 var matchingKey = Object.keys(this.routes_).find(function(key) {
455 return this.routes_[key].path == canonicalPath;
456 }.bind(this));
457
458 return !!matchingKey ? this.routes_[matchingKey] : null;
459 }
460
461 /**
462 * Navigates to a canonical route and pushes a new history entry.
463 * @param {!settings.Route} route
464 * @param {URLSearchParams=} opt_dynamicParameters Navigations to the same
465 * URL parameters in a different order will still push to history.
466 * @param {boolean=} opt_removeSearch Whether to strip the 'search' URL
467 * parameter during navigation. Defaults to false.
468 */
469 navigateTo(route, opt_dynamicParameters, opt_removeSearch) {
470 // The ADVANCED route only serves as a parent of subpages, and should not
471 // be possible to navigate to it directly.
472 if (route == this.routes_.ADVANCED)
473 route = /** @type {!settings.Route} */ (this.routes_.BASIC);
474
475 var params = opt_dynamicParameters || new URLSearchParams();
476 var removeSearch = !!opt_removeSearch;
477
478 var oldSearchParam = this.getQueryParameters().get('search') || '';
479 var newSearchParam = params.get('search') || '';
480
481 if (!removeSearch && oldSearchParam && !newSearchParam)
482 params.append('search', oldSearchParam);
483
484 var url = route.path;
485 var queryString = params.toString();
486 if (queryString)
487 url += '?' + queryString;
488
489 // History serializes the state, so we don't push the actual route object.
490 window.history.pushState(this.currentRoute.path, '', url);
491 this.setCurrentRoute(route, params, false);
492 }
493
494 /**
495 * Navigates to the previous route if it has an equal or lesser depth.
496 * If there is no previous route in history meeting those requirements,
497 * this navigates to the immediate parent. This will never exit Settings.
498 */
499 navigateToPreviousRoute() {
500 var previousRoute = window.history.state &&
501 assert(this.getRouteForPath(
502 /** @type {string} */ (window.history.state)));
503
504 if (previousRoute && previousRoute.depth <= this.currentRoute.depth)
505 window.history.back();
506 else
507 this.navigateTo(
508 this.currentRoute.parent ||
509 /** @type {!settings.Route} */ (this.routes_.BASIC));
510 }
511
512 /**
513 * Initialize the route and query params from the URL.
514 */
515 initializeRouteFromUrl() {
516 assert(!this.initializeRouteFromUrlCalled_);
517 this.initializeRouteFromUrlCalled_ = true;
518
519 var route = this.getRouteForPath(window.location.pathname);
520 // Never allow direct navigation to ADVANCED.
521 if (route && route != this.routes_.ADVANCED) {
522 this.currentRoute = route;
523 this.currentQueryParameters_ =
524 new URLSearchParams(window.location.search);
525 } else {
526 window.history.replaceState(undefined, '', this.routes_.BASIC.path);
527 }
528 }
529
530 resetRouteForTesting() {
531 this.initializeRouteFromUrlCalled_ = false;
532 this.wasLastRouteChangePopstate_ = false;
533 this.currentRoute = /** @type {!settings.Route} */ (this.routes_.BASIC);
534 this.currentQueryParameters_ = new URLSearchParams();
535 }
536 }
537
538 var Router_ = new Router();
dpapad 2017/07/06 02:07:06 Typically only class names start with a capital le
scottchen 2017/07/07 20:58:45 Done. Changed to routerInstance because apparently
539
540 var getRouter = function() {
dpapad 2017/07/06 02:07:06 Is this used anywhere?
scottchen 2017/07/07 20:58:45 not anymore, removing.
541 return Router_;
92 }; 542 };
93 543
94 // Abbreviated variable for easier definitions.
95 var r = Route;
96
97 // Root pages.
98 r.BASIC = new Route('/');
99 r.ADVANCED = new Route('/advanced');
100 r.ABOUT = new Route('/help');
101
102 // Navigable dialogs. These are the only non-section children of root pages.
103 // These are disfavored. If we add anymore, we should add explicit support.
104 r.IMPORT_DATA = r.BASIC.createChild('/importData');
105 r.IMPORT_DATA.isNavigableDialog = true;
106 r.SIGN_OUT = r.BASIC.createChild('/signOut');
107 r.SIGN_OUT.isNavigableDialog = true;
108 r.CLEAR_BROWSER_DATA = r.ADVANCED.createChild('/clearBrowserData');
109 r.CLEAR_BROWSER_DATA.isNavigableDialog = true;
110 r.RESET_DIALOG = r.ADVANCED.createChild('/resetProfileSettings');
111 r.RESET_DIALOG.isNavigableDialog = true;
112 r.TRIGGERED_RESET_DIALOG =
113 r.ADVANCED.createChild('/triggeredResetProfileSettings');
114 r.TRIGGERED_RESET_DIALOG.isNavigableDialog = true;
115
116 // <if expr="chromeos">
117 r.INTERNET = r.BASIC.createSection('/internet', 'internet');
118 r.INTERNET_NETWORKS = r.INTERNET.createChild('/networks');
119 r.NETWORK_CONFIG = r.INTERNET.createChild('/networkConfig');
120 r.NETWORK_DETAIL = r.INTERNET.createChild('/networkDetail');
121 r.KNOWN_NETWORKS = r.INTERNET.createChild('/knownNetworks');
122 r.BLUETOOTH = r.BASIC.createSection('/bluetooth', 'bluetooth');
123 r.BLUETOOTH_DEVICES = r.BLUETOOTH.createChild('/bluetoothDevices');
124 // </if>
125
126 r.APPEARANCE = r.BASIC.createSection('/appearance', 'appearance');
127 r.FONTS = r.APPEARANCE.createChild('/fonts');
128
129 r.DEFAULT_BROWSER =
130 r.BASIC.createSection('/defaultBrowser', 'defaultBrowser');
131
132 r.SEARCH = r.BASIC.createSection('/search', 'search');
133 r.SEARCH_ENGINES = r.SEARCH.createChild('/searchEngines');
134
135 // <if expr="chromeos">
136 r.ANDROID_APPS = r.BASIC.createSection('/androidApps', 'androidApps');
137 r.ANDROID_APPS_DETAILS = r.ANDROID_APPS.createChild('/androidApps/details');
138 // </if>
139
140 r.ON_STARTUP = r.BASIC.createSection('/onStartup', 'onStartup');
141
142 r.PEOPLE = r.BASIC.createSection('/people', 'people');
143 r.SYNC = r.PEOPLE.createChild('/syncSetup');
144 // <if expr="not chromeos">
145 r.MANAGE_PROFILE = r.PEOPLE.createChild('/manageProfile');
146 // </if>
147 // <if expr="chromeos">
148 r.CHANGE_PICTURE = r.PEOPLE.createChild('/changePicture');
149 r.ACCOUNTS = r.PEOPLE.createChild('/accounts');
150 r.LOCK_SCREEN = r.PEOPLE.createChild('/lockScreen');
151 r.FINGERPRINT = r.LOCK_SCREEN.createChild('/lockScreen/fingerprint');
152
153 r.DEVICE = r.BASIC.createSection('/device', 'device');
154 r.POINTERS = r.DEVICE.createChild('/pointer-overlay');
155 r.KEYBOARD = r.DEVICE.createChild('/keyboard-overlay');
156 r.STYLUS = r.DEVICE.createChild('/stylus');
157 r.DISPLAY = r.DEVICE.createChild('/display');
158 r.STORAGE = r.DEVICE.createChild('/storage');
159 r.POWER = r.DEVICE.createChild('/power');
160 // </if>
161
162 r.PRIVACY = r.ADVANCED.createSection('/privacy', 'privacy');
163 r.CERTIFICATES = r.PRIVACY.createChild('/certificates');
164
165 r.SITE_SETTINGS = r.PRIVACY.createChild('/content');
166
167 if (loadTimeData.getBoolean('enableSiteSettings')) {
168 r.SITE_SETTINGS_ALL = r.SITE_SETTINGS.createChild('all');
169 r.SITE_SETTINGS_SITE_DETAILS =
170 r.SITE_SETTINGS_ALL.createChild('/content/siteDetails');
171 } else if (loadTimeData.getBoolean('enableSiteDetails')) {
172 // When there is no "All Sites", pressing 'back' from "Site Details" should
173 // return to "Content Settings". This should only occur when |kSiteSettings|
174 // is off and |kSiteDetails| is on.
175 r.SITE_SETTINGS_SITE_DETAILS =
176 r.SITE_SETTINGS.createChild('/content/siteDetails');
177 }
178
179 r.SITE_SETTINGS_HANDLERS = r.SITE_SETTINGS.createChild('/handlers');
180
181 // TODO(tommycli): Find a way to refactor these repetitive category routes.
182 r.SITE_SETTINGS_ADS = r.SITE_SETTINGS.createChild('ads');
183 r.SITE_SETTINGS_AUTOMATIC_DOWNLOADS =
184 r.SITE_SETTINGS.createChild('automaticDownloads');
185 r.SITE_SETTINGS_BACKGROUND_SYNC =
186 r.SITE_SETTINGS.createChild('backgroundSync');
187 r.SITE_SETTINGS_CAMERA = r.SITE_SETTINGS.createChild('camera');
188 r.SITE_SETTINGS_COOKIES = r.SITE_SETTINGS.createChild('cookies');
189 r.SITE_SETTINGS_DATA_DETAILS =
190 r.SITE_SETTINGS_COOKIES.createChild('/cookies/detail');
191 r.SITE_SETTINGS_IMAGES = r.SITE_SETTINGS.createChild('images');
192 r.SITE_SETTINGS_JAVASCRIPT = r.SITE_SETTINGS.createChild('javascript');
193 r.SITE_SETTINGS_LOCATION = r.SITE_SETTINGS.createChild('location');
194 r.SITE_SETTINGS_MICROPHONE = r.SITE_SETTINGS.createChild('microphone');
195 r.SITE_SETTINGS_NOTIFICATIONS = r.SITE_SETTINGS.createChild('notifications');
196 r.SITE_SETTINGS_FLASH = r.SITE_SETTINGS.createChild('flash');
197 r.SITE_SETTINGS_POPUPS = r.SITE_SETTINGS.createChild('popups');
198 r.SITE_SETTINGS_UNSANDBOXED_PLUGINS =
199 r.SITE_SETTINGS.createChild('unsandboxedPlugins');
200 r.SITE_SETTINGS_MIDI_DEVICES = r.SITE_SETTINGS.createChild('midiDevices');
201 r.SITE_SETTINGS_USB_DEVICES = r.SITE_SETTINGS.createChild('usbDevices');
202 r.SITE_SETTINGS_ZOOM_LEVELS = r.SITE_SETTINGS.createChild('zoomLevels');
203 r.SITE_SETTINGS_PDF_DOCUMENTS = r.SITE_SETTINGS.createChild('pdfDocuments');
204 r.SITE_SETTINGS_PROTECTED_CONTENT =
205 r.SITE_SETTINGS.createChild('protectedContent');
206
207 // <if expr="chromeos">
208 r.DATETIME = r.ADVANCED.createSection('/dateTime', 'dateTime');
209 // </if>
210
211 r.PASSWORDS =
212 r.ADVANCED.createSection('/passwordsAndForms', 'passwordsAndForms');
213 r.AUTOFILL = r.PASSWORDS.createChild('/autofill');
214 r.MANAGE_PASSWORDS = r.PASSWORDS.createChild('/passwords');
215
216 r.LANGUAGES = r.ADVANCED.createSection('/languages', 'languages');
217 // <if expr="chromeos">
218 r.INPUT_METHODS = r.LANGUAGES.createChild('/inputMethods');
219 // </if>
220 // <if expr="not is_macosx">
221 r.EDIT_DICTIONARY = r.LANGUAGES.createChild('/editDictionary');
222 // </if>
223
224 r.DOWNLOADS = r.ADVANCED.createSection('/downloads', 'downloads');
225
226 r.PRINTING = r.ADVANCED.createSection('/printing', 'printing');
227 r.CLOUD_PRINTERS = r.PRINTING.createChild('/cloudPrinters');
228 // <if expr="chromeos">
229 r.CUPS_PRINTERS = r.PRINTING.createChild('/cupsPrinters');
230 r.CUPS_PRINTER_DETAIL = r.CUPS_PRINTERS.createChild('/cupsPrinterDetails');
231 // </if>
232
233 r.ACCESSIBILITY = r.ADVANCED.createSection('/accessibility', 'a11y');
234 // <if expr="chromeos">
235 r.MANAGE_ACCESSIBILITY = r.ACCESSIBILITY.createChild('/manageAccessibility');
236 // </if>
237
238 r.SYSTEM = r.ADVANCED.createSection('/system', 'system');
239 r.RESET = r.ADVANCED.createSection('/reset', 'reset');
240
241 // <if expr="chromeos">
242 // "About" is the only section in About, but we still need to create the route
243 // in order to show the subpage on Chrome OS.
244 r.ABOUT_ABOUT = r.ABOUT.createSection('/help/about', 'about');
245 r.DETAILED_BUILD_INFO = r.ABOUT_ABOUT.createChild('/help/details');
246 // </if>
247
248 var routeObservers = new Set(); 544 var routeObservers = new Set();
249 545
250 /** @polymerBehavior */ 546 /** @polymerBehavior */
251 var RouteObserverBehavior = { 547 var RouteObserverBehavior = {
252 /** @override */ 548 /** @override */
253 attached: function() { 549 attached: function() {
254 assert(!routeObservers.has(this)); 550 assert(!routeObservers.has(this));
255 routeObservers.add(this); 551 routeObservers.add(this);
256 552
257 // Emulating Polymer data bindings, the observer is called when the 553 // Emulating Polymer data bindings, the observer is called when the
258 // element starts observing the route. 554 // element starts observing the route.
259 this.currentRouteChanged(currentRoute, undefined); 555 this.currentRouteChanged(Router_.currentRoute, undefined);
260 }, 556 },
261 557
262 /** @override */ 558 /** @override */
263 detached: function() { 559 detached: function() {
264 assert(routeObservers.delete(this)); 560 assert(routeObservers.delete(this));
265 }, 561 },
266 562
267 /** 563 /**
268 * @param {!settings.Route|undefined} opt_newRoute 564 * @param {!settings.Route|undefined} opt_newRoute
269 * @param {!settings.Route|undefined} opt_oldRoute 565 * @param {!settings.Route|undefined} opt_oldRoute
270 * @abstract 566 * @abstract
271 */ 567 */
272 currentRouteChanged: function(opt_newRoute, opt_oldRoute) { 568 currentRouteChanged: function(opt_newRoute, opt_oldRoute) {
273 assertNotReached(); 569 assertNotReached();
274 }, 570 },
275 }; 571 };
276 572
277 /** 573 /**
278 * Regular expression that captures the leading slash, the content and the 574 * Regular expression that captures the leading slash, the content and the
279 * trailing slash in three different groups. 575 * trailing slash in three different groups.
280 * @const {!RegExp} 576 * @const {!RegExp}
281 */ 577 */
282 var CANONICAL_PATH_REGEX = /(^\/)([\/-\w]+)(\/$)/; 578 var CANONICAL_PATH_REGEX = /(^\/)([\/-\w]+)(\/$)/;
283 579
284 /**
285 * @param {string} path
286 * @return {?settings.Route} The matching canonical route, or null if none
287 * matches.
288 */
289 var getRouteForPath = function(path) {
290 // Allow trailing slash in paths.
291 var canonicalPath = path.replace(CANONICAL_PATH_REGEX, '$1$2');
292
293 // TODO(tommycli): Use Object.values once Closure compilation supports it.
294 var matchingKey = Object.keys(Route).find(function(key) {
295 return Route[key].path == canonicalPath;
296 });
297
298 return !!matchingKey ? Route[matchingKey] : null;
299 };
300
301 /**
302 * The current active route. This updated is only by settings.navigateTo or
303 * settings.initializeRouteFromUrl.
304 * @private {!settings.Route}
305 */
306 var currentRoute = Route.BASIC;
307
308 /**
309 * The current query parameters. This is updated only by settings.navigateTo
310 * or settings.initializeRouteFromUrl.
311 * @private {!URLSearchParams}
312 */
313 var currentQueryParameters = new URLSearchParams();
314
315 /** @private {boolean} */
316 var wasLastRouteChangePopstate = false;
317
318 /** @private */
319 var initializeRouteFromUrlCalled = false;
320
321 /**
322 * Initialize the route and query params from the URL.
323 */
324 var initializeRouteFromUrl = function() {
325 assert(!initializeRouteFromUrlCalled);
326 initializeRouteFromUrlCalled = true;
327
328 var route = getRouteForPath(window.location.pathname);
329 // Never allow direct navigation to ADVANCED.
330 if (route && route != Route.ADVANCED) {
331 currentRoute = route;
332 currentQueryParameters = new URLSearchParams(window.location.search);
333 } else {
334 window.history.replaceState(undefined, '', Route.BASIC.path);
335 }
336 };
337
338 function resetRouteForTesting() {
339 initializeRouteFromUrlCalled = false;
340 wasLastRouteChangePopstate = false;
341 currentRoute = Route.BASIC;
342 currentQueryParameters = new URLSearchParams();
343 }
344
345 /**
346 * Helper function to set the current route and notify all observers.
347 * @param {!settings.Route} route
348 * @param {!URLSearchParams} queryParameters
349 * @param {boolean} isPopstate
350 */
351 var setCurrentRoute = function(route, queryParameters, isPopstate) {
352 var oldRoute = currentRoute;
353 currentRoute = route;
354 currentQueryParameters = queryParameters;
355 wasLastRouteChangePopstate = isPopstate;
356 routeObservers.forEach(function(observer) {
357 observer.currentRouteChanged(currentRoute, oldRoute);
358 });
359 };
360
361 /** @return {!settings.Route} */
362 var getCurrentRoute = function() {
363 return currentRoute;
364 };
365
366 /** @return {!URLSearchParams} */
367 var getQueryParameters = function() {
368 return new URLSearchParams(currentQueryParameters); // Defensive copy.
369 };
370
371 /** @return {boolean} */
372 var lastRouteChangeWasPopstate = function() {
373 return wasLastRouteChangePopstate;
374 };
375
376 /**
377 * Navigates to a canonical route and pushes a new history entry.
378 * @param {!settings.Route} route
379 * @param {URLSearchParams=} opt_dynamicParameters Navigations to the same
380 * URL parameters in a different order will still push to history.
381 * @param {boolean=} opt_removeSearch Whether to strip the 'search' URL
382 * parameter during navigation. Defaults to false.
383 */
384 var navigateTo = function(route, opt_dynamicParameters, opt_removeSearch) {
385 // The ADVANCED route only serves as a parent of subpages, and should not
386 // be possible to navigate to it directly.
387 if (route == settings.Route.ADVANCED)
388 route = settings.Route.BASIC;
389
390 var params = opt_dynamicParameters || new URLSearchParams();
391 var removeSearch = !!opt_removeSearch;
392
393 var oldSearchParam = getQueryParameters().get('search') || '';
394 var newSearchParam = params.get('search') || '';
395
396 if (!removeSearch && oldSearchParam && !newSearchParam)
397 params.append('search', oldSearchParam);
398
399 var url = route.path;
400 var queryString = params.toString();
401 if (queryString)
402 url += '?' + queryString;
403
404 // History serializes the state, so we don't push the actual route object.
405 window.history.pushState(currentRoute.path, '', url);
406 setCurrentRoute(route, params, false);
407 };
408
409 /**
410 * Navigates to the previous route if it has an equal or lesser depth.
411 * If there is no previous route in history meeting those requirements,
412 * this navigates to the immediate parent. This will never exit Settings.
413 */
414 var navigateToPreviousRoute = function() {
415 var previousRoute = window.history.state &&
416 assert(getRouteForPath(/** @type {string} */ (window.history.state)));
417
418 if (previousRoute && previousRoute.depth <= currentRoute.depth)
419 window.history.back();
420 else
421 navigateTo(currentRoute.parent || Route.BASIC);
422 };
423
424 window.addEventListener('popstate', function(event) { 580 window.addEventListener('popstate', function(event) {
425 // On pop state, do not push the state onto the window.history again. 581 // On pop state, do not push the state onto the window.history again.
426 setCurrentRoute( 582 Router_.setCurrentRoute(
427 getRouteForPath(window.location.pathname) || Route.BASIC, 583 /** @type {!settings.Route} */ (
584 Router_.getRouteForPath(window.location.pathname) ||
585 Router_.getRoutes().BASIC),
428 new URLSearchParams(window.location.search), true); 586 new URLSearchParams(window.location.search), true);
429 }); 587 });
430 588
589 // TODO(scottchen): Change to 'get routes() {}' in export when we figure
590 // out why doing so causes a closure compiler error.
dpapad 2017/07/06 02:07:06 Maybe update the comment to mention our newest fin
scottchen 2017/07/07 20:58:45 Done.
591 var routes = Router_.getRoutes();
592
dpapad 2017/07/06 02:07:06 Maybe add a TODO as follows? "Stop exposing all t
scottchen 2017/07/07 20:58:45 Done.
593 var getCurrentRoute = Router_.getCurrentRoute.bind(Router_);
594 var getRouteForPath = Router_.getRouteForPath.bind(Router_);
595 var initializeRouteFromUrl = Router_.initializeRouteFromUrl.bind(Router_);
596 var resetRouteForTesting = Router_.resetRouteForTesting.bind(Router_);
597 var getQueryParameters = Router_.getQueryParameters.bind(Router_);
598 var lastRouteChangeWasPopstate =
599 Router_.lastRouteChangeWasPopstate.bind(Router_);
600 var navigateTo = Router_.navigateTo.bind(Router_);
601 var navigateToPreviousRoute = Router_.navigateToPreviousRoute.bind(Router_);
602
431 return { 603 return {
432 Route: Route, 604 Route: Route, // The class definition.
605 Router: Router, // The class definition.
606 router: Router_, // the singleton.
607 routes: routes,
433 RouteObserverBehavior: RouteObserverBehavior, 608 RouteObserverBehavior: RouteObserverBehavior,
434 getRouteForPath: getRouteForPath, 609 getRouteForPath: getRouteForPath,
435 initializeRouteFromUrl: initializeRouteFromUrl, 610 initializeRouteFromUrl: initializeRouteFromUrl,
436 resetRouteForTesting: resetRouteForTesting, 611 resetRouteForTesting: resetRouteForTesting,
437 getCurrentRoute: getCurrentRoute, 612 getCurrentRoute: getCurrentRoute,
438 getQueryParameters: getQueryParameters, 613 getQueryParameters: getQueryParameters,
439 lastRouteChangeWasPopstate: lastRouteChangeWasPopstate, 614 lastRouteChangeWasPopstate: lastRouteChangeWasPopstate,
440 navigateTo: navigateTo, 615 navigateTo: navigateTo,
441 navigateToPreviousRoute: navigateToPreviousRoute, 616 navigateToPreviousRoute: navigateToPreviousRoute,
442 }; 617 };
443 }); 618 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698