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

Side by Side Diff: chrome/browser/resources/options/sync_setup_overlay.js

Issue 2939273002: DO NOT SUBMIT: what chrome/browser/resources/ could eventually look like with clang-format (Closed)
Patch Set: Created 3 years, 6 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 (c) 2012 The Chromium Authors. All rights reserved. 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 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 cr.exportPath('options'); 5 cr.exportPath('options');
6 6
7 /** @typedef {{appsEnforced: boolean, 7 /** @typedef {{appsEnforced: boolean,
8 * appsRegistered: boolean, 8 * appsRegistered: boolean,
9 * appsSynced: boolean, 9 * appsSynced: boolean,
10 * autofillEnforced: boolean, 10 * autofillEnforced: boolean,
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
57 cr.define('options', function() { 57 cr.define('options', function() {
58 /** @const */ var Page = cr.ui.pageManager.Page; 58 /** @const */ var Page = cr.ui.pageManager.Page;
59 /** @const */ var PageManager = cr.ui.pageManager.PageManager; 59 /** @const */ var PageManager = cr.ui.pageManager.PageManager;
60 60
61 /** 61 /**
62 * SyncSetupOverlay class 62 * SyncSetupOverlay class
63 * Encapsulated handling of the 'Sync Setup' overlay page. 63 * Encapsulated handling of the 'Sync Setup' overlay page.
64 * @class 64 * @class
65 */ 65 */
66 function SyncSetupOverlay() { 66 function SyncSetupOverlay() {
67 Page.call(this, 'syncSetup', 67 Page.call(
68 loadTimeData.getString('syncSetupOverlayTabTitle'), 68 this, 'syncSetup', loadTimeData.getString('syncSetupOverlayTabTitle'),
69 'sync-setup-overlay'); 69 'sync-setup-overlay');
70 } 70 }
71 71
72 cr.addSingletonGetter(SyncSetupOverlay); 72 cr.addSingletonGetter(SyncSetupOverlay);
73 73
74 SyncSetupOverlay.prototype = { 74 SyncSetupOverlay.prototype = {
75 __proto__: Page.prototype, 75 __proto__: Page.prototype,
76 76
77 /** 77 /**
78 * True if the synced account uses a custom passphrase. 78 * True if the synced account uses a custom passphrase.
79 * @private {boolean} 79 * @private {boolean}
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
125 125
126 // If 'profilesInfo' doesn't exist, it's forbidden to delete profile. 126 // If 'profilesInfo' doesn't exist, it's forbidden to delete profile.
127 // So don't display the delete-profile checkbox. 127 // So don't display the delete-profile checkbox.
128 if (!loadTimeData.valueExists('profilesInfo') && 128 if (!loadTimeData.valueExists('profilesInfo') &&
129 $('sync-setup-delete-profile')) { 129 $('sync-setup-delete-profile')) {
130 $('sync-setup-delete-profile').hidden = true; 130 $('sync-setup-delete-profile').hidden = true;
131 } 131 }
132 132
133 $('basic-encryption-option').onchange = 133 $('basic-encryption-option').onchange =
134 $('full-encryption-option').onchange = function() { 134 $('full-encryption-option').onchange = function() {
135 self.onEncryptionRadioChanged_(); 135 self.onEncryptionRadioChanged_();
136 }; 136 };
137 $('choose-datatypes-cancel').onclick = 137 $('choose-datatypes-cancel').onclick =
138 $('confirm-everything-cancel').onclick = 138 $('confirm-everything-cancel').onclick =
139 $('stop-syncing-cancel').onclick = 139 $('stop-syncing-cancel').onclick =
140 $('sync-spinner-cancel').onclick = function() { 140 $('sync-spinner-cancel').onclick = function() {
141 self.closeOverlay_(); 141 self.closeOverlay_();
142 }; 142 };
143 $('activity-controls').onclick = function() { 143 $('activity-controls').onclick = function() {
144 chrome.metricsPrivate.recordUserAction( 144 chrome.metricsPrivate.recordUserAction(
145 'Signin_AccountSettings_GoogleActivityControlsClicked'); 145 'Signin_AccountSettings_GoogleActivityControlsClicked');
146 }; 146 };
147 $('confirm-everything-ok').onclick = function() { 147 $('confirm-everything-ok').onclick = function() {
148 self.sendConfiguration_(); 148 self.sendConfiguration_();
149 }; 149 };
150 $('timeout-ok').onclick = function() { 150 $('timeout-ok').onclick = function() {
151 chrome.send('CloseTimeout'); 151 chrome.send('CloseTimeout');
152 self.closeOverlay_(); 152 self.closeOverlay_();
153 }; 153 };
154 $('stop-syncing-ok').onclick = function() { 154 $('stop-syncing-ok').onclick = function() {
155 var deleteProfile = $('delete-profile') != undefined && 155 var deleteProfile =
156 $('delete-profile').checked; 156 $('delete-profile') != undefined && $('delete-profile').checked;
157 chrome.send('SyncSetupStopSyncing', [deleteProfile]); 157 chrome.send('SyncSetupStopSyncing', [deleteProfile]);
158 self.closeOverlay_(); 158 self.closeOverlay_();
159 }; 159 };
160 $('use-default-link').onclick = function() { 160 $('use-default-link').onclick = function() {
161 self.showSyncEverythingPage_(); 161 self.showSyncEverythingPage_();
162 }; 162 };
163 $('autofill-checkbox').onclick = function() { 163 $('autofill-checkbox').onclick = function() {
164 var autofillSyncEnabled = $('autofill-checkbox').checked; 164 var autofillSyncEnabled = $('autofill-checkbox').checked;
165 $('payments-integration-checkbox').checked = autofillSyncEnabled; 165 $('payments-integration-checkbox').checked = autofillSyncEnabled;
166 $('payments-integration-checkbox').disabled = !autofillSyncEnabled; 166 $('payments-integration-checkbox').disabled = !autofillSyncEnabled;
(...skipping 24 matching lines...) Expand all
191 /** @override */ 191 /** @override */
192 didClosePage: function() { 192 didClosePage: function() {
193 chrome.send('SyncSetupDidClosePage'); 193 chrome.send('SyncSetupDidClosePage');
194 }, 194 },
195 195
196 /** @private */ 196 /** @private */
197 onEncryptionRadioChanged_: function() { 197 onEncryptionRadioChanged_: function() {
198 var visible = $('full-encryption-option').checked; 198 var visible = $('full-encryption-option').checked;
199 // TODO(dbeam): should sync-custom-passphrase-container be hidden instead? 199 // TODO(dbeam): should sync-custom-passphrase-container be hidden instead?
200 $('sync-custom-passphrase').hidden = !visible; 200 $('sync-custom-passphrase').hidden = !visible;
201 chrome.send('coreOptionsUserMetricsAction', 201 chrome.send(
202 ['Options_SyncSetEncryption']); 202 'coreOptionsUserMetricsAction', ['Options_SyncSetEncryption']);
203 }, 203 },
204 204
205 /** 205 /**
206 * Sets the checked state of the individual sync data type checkboxes in the 206 * Sets the checked state of the individual sync data type checkboxes in the
207 * advanced sync settings dialog. 207 * advanced sync settings dialog.
208 * @param {boolean} value True for checked, false for unchecked. 208 * @param {boolean} value True for checked, false for unchecked.
209 * @private 209 * @private
210 */ 210 */
211 checkAllDataTypeCheckboxes_: function(value) { 211 checkAllDataTypeCheckboxes_: function(value) {
212 // Only check / uncheck the visible ones (since there's no way to uncheck 212 // Only check / uncheck the visible ones (since there's no way to uncheck
213 // / check the invisible ones). 213 // / check the invisible ones).
214 var checkboxes = $('choose-data-types-body').querySelectorAll( 214 var checkboxes =
215 '.sync-type-checkbox:not([hidden]) input'); 215 $('choose-data-types-body')
216 .querySelectorAll('.sync-type-checkbox:not([hidden]) input');
216 for (var i = 0; i < checkboxes.length; i++) { 217 for (var i = 0; i < checkboxes.length; i++) {
217 checkboxes[i].checked = value; 218 checkboxes[i].checked = value;
218 } 219 }
219 $('payments-integration-checkbox').checked = value; 220 $('payments-integration-checkbox').checked = value;
220 }, 221 },
221 222
222 /** 223 /**
223 * Restores the checked states of the sync data type checkboxes in the 224 * Restores the checked states of the sync data type checkboxes in the
224 * advanced sync settings dialog. Called when "Choose what to sync" is 225 * advanced sync settings dialog. Called when "Choose what to sync" is
225 * selected. Required because all the checkboxes are checked when 226 * selected. Required because all the checkboxes are checked when
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
269 }, 270 },
270 271
271 /** @private */ 272 /** @private */
272 checkPassphraseMatch_: function() { 273 checkPassphraseMatch_: function() {
273 var emptyError = $('empty-error'); 274 var emptyError = $('empty-error');
274 var mismatchError = $('mismatch-error'); 275 var mismatchError = $('mismatch-error');
275 emptyError.hidden = true; 276 emptyError.hidden = true;
276 mismatchError.hidden = true; 277 mismatchError.hidden = true;
277 278
278 if (!$('full-encryption-option').checked || 279 if (!$('full-encryption-option').checked ||
279 $('basic-encryption-option').disabled) { 280 $('basic-encryption-option').disabled) {
280 return true; 281 return true;
281 } 282 }
282 283
283 var customPassphrase = $('custom-passphrase'); 284 var customPassphrase = $('custom-passphrase');
284 if (customPassphrase.value.length == 0) { 285 if (customPassphrase.value.length == 0) {
285 emptyError.hidden = false; 286 emptyError.hidden = false;
286 return false; 287 return false;
287 } 288 }
288 289
289 var confirmPassphrase = $('confirm-passphrase'); 290 var confirmPassphrase = $('confirm-passphrase');
(...skipping 17 matching lines...) Expand all
307 customPassphrase = getRequiredElement('passphrase').value; 308 customPassphrase = getRequiredElement('passphrase').value;
308 usePassphrase = true; 309 usePassphrase = true;
309 // If we were displaying the 'enter your old google password' prompt, 310 // If we were displaying the 'enter your old google password' prompt,
310 // then that means this is the user's google password. 311 // then that means this is the user's google password.
311 googlePassphrase = !$('google-passphrase-needed-body').hidden; 312 googlePassphrase = !$('google-passphrase-needed-body').hidden;
312 // We allow an empty passphrase, in case the user has disabled 313 // We allow an empty passphrase, in case the user has disabled
313 // all their encrypted datatypes. In that case, the PSS will accept 314 // all their encrypted datatypes. In that case, the PSS will accept
314 // the passphrase and finish configuration. If the user has enabled 315 // the passphrase and finish configuration. If the user has enabled
315 // encrypted datatypes, the PSS will prompt again specifying that the 316 // encrypted datatypes, the PSS will prompt again specifying that the
316 // passphrase failed. 317 // passphrase failed.
317 } else if (!$('basic-encryption-option').disabled && 318 } else if (
318 $('full-encryption-option').checked) { 319 !$('basic-encryption-option').disabled &&
320 $('full-encryption-option').checked) {
319 // The user is setting a custom passphrase for the first time. 321 // The user is setting a custom passphrase for the first time.
320 if (!this.checkPassphraseMatch_()) 322 if (!this.checkPassphraseMatch_())
321 return; 323 return;
322 customPassphrase = $('custom-passphrase').value; 324 customPassphrase = $('custom-passphrase').value;
323 usePassphrase = true; 325 usePassphrase = true;
324 } else { 326 } else {
325 // The user is not setting a custom passphrase. 327 // The user is not setting a custom passphrase.
326 usePassphrase = false; 328 usePassphrase = false;
327 } 329 }
328 330
329 // Don't allow the user to tweak the settings once we send the 331 // Don't allow the user to tweak the settings once we send the
330 // configuration to the backend. 332 // configuration to the backend.
331 this.setInputElementsDisabledState_(true); 333 this.setInputElementsDisabledState_(true);
332 $('use-default-link').hidden = true; 334 $('use-default-link').hidden = true;
333 335
334 // These values need to be kept in sync with where they are read in 336 // These values need to be kept in sync with where they are read in
335 // sync_setup_handler.cc:GetConfiguration(). 337 // sync_setup_handler.cc:GetConfiguration().
336 var syncAll = $('sync-select-datatypes').selectedIndex == 338 var syncAll = $('sync-select-datatypes').selectedIndex ==
337 options.DataTypeSelection.SYNC_EVERYTHING; 339 options.DataTypeSelection.SYNC_EVERYTHING;
338 var autofillSynced = syncAll || $('autofill-checkbox').checked; 340 var autofillSynced = syncAll || $('autofill-checkbox').checked;
339 var result = JSON.stringify({ 341 var result = JSON.stringify({
340 'syncAllDataTypes': syncAll, 342 'syncAllDataTypes': syncAll,
341 'bookmarksSynced': syncAll || $('bookmarks-checkbox').checked, 343 'bookmarksSynced': syncAll || $('bookmarks-checkbox').checked,
342 'preferencesSynced': syncAll || $('preferences-checkbox').checked, 344 'preferencesSynced': syncAll || $('preferences-checkbox').checked,
343 'themesSynced': syncAll || $('themes-checkbox').checked, 345 'themesSynced': syncAll || $('themes-checkbox').checked,
344 'passwordsSynced': syncAll || $('passwords-checkbox').checked, 346 'passwordsSynced': syncAll || $('passwords-checkbox').checked,
345 'autofillSynced': autofillSynced, 347 'autofillSynced': autofillSynced,
346 'extensionsSynced': syncAll || $('extensions-checkbox').checked, 348 'extensionsSynced': syncAll || $('extensions-checkbox').checked,
347 'typedUrlsSynced': syncAll || $('typed-urls-checkbox').checked, 349 'typedUrlsSynced': syncAll || $('typed-urls-checkbox').checked,
(...skipping 22 matching lines...) Expand all
370 var configureElements = 372 var configureElements =
371 $('customize-sync-preferences').querySelectorAll('input'); 373 $('customize-sync-preferences').querySelectorAll('input');
372 for (var i = 0; i < configureElements.length; i++) 374 for (var i = 0; i < configureElements.length; i++)
373 configureElements[i].disabled = disabled; 375 configureElements[i].disabled = disabled;
374 $('sync-select-datatypes').disabled = disabled; 376 $('sync-select-datatypes').disabled = disabled;
375 $('payments-integration-checkbox').disabled = disabled; 377 $('payments-integration-checkbox').disabled = disabled;
376 378
377 $('customize-link').hidden = disabled; 379 $('customize-link').hidden = disabled;
378 $('customize-link').disabled = disabled; 380 $('customize-link').disabled = disabled;
379 $('customize-link').onclick = disabled ? null : function() { 381 $('customize-link').onclick = disabled ? null : function() {
380 SyncSetupOverlay.showCustomizePage(self.syncConfigureArgs_, 382 SyncSetupOverlay.showCustomizePage(
381 options.DataTypeSelection.SYNC_EVERYTHING); 383 self.syncConfigureArgs_, options.DataTypeSelection.SYNC_EVERYTHING);
382 return false; 384 return false;
383 }; 385 };
384 }, 386 },
385 387
386 /** 388 /**
387 * Shows or hides the sync data type checkboxes in the advanced sync 389 * Shows or hides the sync data type checkboxes in the advanced sync
388 * settings dialog. Also initializes |this.dataTypeBoxesChecked_| and 390 * settings dialog. Also initializes |this.dataTypeBoxesChecked_| and
389 * |this.dataTypeBoxedDisabled_| with their values, and makes their onclick 391 * |this.dataTypeBoxedDisabled_| with their values, and makes their onclick
390 * handlers update |this.dataTypeBoxesChecked_|. 392 * handlers update |this.dataTypeBoxesChecked_|.
391 * @param {SyncConfig} args The configuration data used to show/hide UI. 393 * @param {SyncConfig} args The configuration data used to show/hide UI.
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
483 * Updates the cached values of the sync data type checkboxes stored in 485 * Updates the cached values of the sync data type checkboxes stored in
484 * |this.dataTypeBoxesChecked_|. Used as an onclick handler for each data 486 * |this.dataTypeBoxesChecked_|. Used as an onclick handler for each data
485 * type checkbox. 487 * type checkbox.
486 * @param {Event} e The change event. 488 * @param {Event} e The change event.
487 * @private 489 * @private
488 */ 490 */
489 handleDataTypeChange_: function(e) { 491 handleDataTypeChange_: function(e) {
490 var input = assertInstanceof(e.target, HTMLInputElement); 492 var input = assertInstanceof(e.target, HTMLInputElement);
491 assert(input.type == 'checkbox'); 493 assert(input.type == 'checkbox');
492 this.dataTypeBoxesChecked_[input.id] = input.checked; 494 this.dataTypeBoxesChecked_[input.id] = input.checked;
493 chrome.send('coreOptionsUserMetricsAction', 495 chrome.send(
494 ['Options_SyncToggleDataType']); 496 'coreOptionsUserMetricsAction', ['Options_SyncToggleDataType']);
495 }, 497 },
496 498
497 /** 499 /**
498 * @param {SyncConfig} args 500 * @param {SyncConfig} args
499 * @private 501 * @private
500 */ 502 */
501 setEncryptionRadios_: function(args) { 503 setEncryptionRadios_: function(args) {
502 if (!args.encryptAllData && !args.usePassphrase) { 504 if (!args.encryptAllData && !args.usePassphrase) {
503 $('basic-encryption-option').checked = true; 505 $('basic-encryption-option').checked = true;
504 } else { 506 } else {
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
559 if (args) { 561 if (args) {
560 this.setCheckboxesAndErrors_(args); 562 this.setCheckboxesAndErrors_(args);
561 563
562 this.useEncryptEverything_ = args.encryptAllData; 564 this.useEncryptEverything_ = args.encryptAllData;
563 565
564 // Determine whether to display the 'OK, sync everything' confirmation 566 // Determine whether to display the 'OK, sync everything' confirmation
565 // dialog or the advanced sync settings dialog, and assign focus to the 567 // dialog or the advanced sync settings dialog, and assign focus to the
566 // OK button, or to the passphrase field if a passphrase is required. 568 // OK button, or to the passphrase field if a passphrase is required.
567 this.usePassphrase_ = args.usePassphrase; 569 this.usePassphrase_ = args.usePassphrase;
568 var index = args.syncAllDataTypes ? 570 var index = args.syncAllDataTypes ?
569 options.DataTypeSelection.SYNC_EVERYTHING : 571 options.DataTypeSelection.SYNC_EVERYTHING :
570 options.DataTypeSelection.CHOOSE_WHAT_TO_SYNC; 572 options.DataTypeSelection.CHOOSE_WHAT_TO_SYNC;
571 this.showCustomizePage_(args, index); 573 this.showCustomizePage_(args, index);
572 } 574 }
573 }, 575 },
574 576
575 /** @private */ 577 /** @private */
576 showSpinner_: function() { 578 showSpinner_: function() {
577 this.resetPage_('sync-setup-spinner'); 579 this.resetPage_('sync-setup-spinner');
578 $('sync-setup-spinner').hidden = false; 580 $('sync-setup-spinner').hidden = false;
579 }, 581 },
580 582
581 /** @private */ 583 /** @private */
582 showTimeoutPage_: function() { 584 showTimeoutPage_: function() {
583 this.resetPage_('sync-setup-timeout'); 585 this.resetPage_('sync-setup-timeout');
584 $('sync-setup-timeout').hidden = false; 586 $('sync-setup-timeout').hidden = false;
585 }, 587 },
586 588
587 /** @private */ 589 /** @private */
588 showSyncEverythingPage_: function() { 590 showSyncEverythingPage_: function() {
589 chrome.send('coreOptionsUserMetricsAction', 591 chrome.send('coreOptionsUserMetricsAction', ['Options_SyncSetDefault']);
590 ['Options_SyncSetDefault']);
591 592
592 $('confirm-sync-preferences').hidden = false; 593 $('confirm-sync-preferences').hidden = false;
593 $('customize-sync-preferences').hidden = true; 594 $('customize-sync-preferences').hidden = true;
594 595
595 // Reset the selection to 'Sync everything'. 596 // Reset the selection to 'Sync everything'.
596 $('sync-select-datatypes').selectedIndex = 0; 597 $('sync-select-datatypes').selectedIndex = 0;
597 598
598 // The default state is to sync everything. 599 // The default state is to sync everything.
599 this.setDataTypeCheckboxes_(options.DataTypeSelection.SYNC_EVERYTHING); 600 this.setDataTypeCheckboxes_(options.DataTypeSelection.SYNC_EVERYTHING);
600 601
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after
748 */ 749 */
749 resetPage_: function(pageElementId) { 750 resetPage_: function(pageElementId) {
750 var page = $(pageElementId); 751 var page = $(pageElementId);
751 var forEach = function(arr, fn) { 752 var forEach = function(arr, fn) {
752 var length = arr.length; 753 var length = arr.length;
753 for (var i = 0; i < length; i++) { 754 for (var i = 0; i < length; i++) {
754 fn(arr[i]); 755 fn(arr[i]);
755 } 756 }
756 }; 757 };
757 758
758 forEach(page.getElementsByClassName('reset-hidden'), 759 forEach(page.getElementsByClassName('reset-hidden'), function(elt) {
759 function(elt) { elt.hidden = true; }); 760 elt.hidden = true;
760 forEach(page.getElementsByClassName('reset-shown'), 761 });
761 function(elt) { elt.hidden = false; }); 762 forEach(page.getElementsByClassName('reset-shown'), function(elt) {
762 forEach(page.getElementsByClassName('reset-disabled'), 763 elt.hidden = false;
763 function(elt) { elt.disabled = true; }); 764 });
764 forEach(page.getElementsByClassName('reset-enabled'), 765 forEach(page.getElementsByClassName('reset-disabled'), function(elt) {
765 function(elt) { elt.disabled = false; }); 766 elt.disabled = true;
766 forEach(page.getElementsByClassName('reset-value'), 767 });
767 function(elt) { elt.value = ''; }); 768 forEach(page.getElementsByClassName('reset-enabled'), function(elt) {
768 forEach(page.getElementsByClassName('reset-opaque'), 769 elt.disabled = false;
769 function(elt) { elt.classList.remove('transparent'); }); 770 });
771 forEach(page.getElementsByClassName('reset-value'), function(elt) {
772 elt.value = '';
773 });
774 forEach(page.getElementsByClassName('reset-opaque'), function(elt) {
775 elt.classList.remove('transparent');
776 });
770 }, 777 },
771 778
772 /** 779 /**
773 * Displays the stop syncing dialog. 780 * Displays the stop syncing dialog.
774 * @private 781 * @private
775 */ 782 */
776 showStopSyncingUI_: function() { 783 showStopSyncingUI_: function() {
777 // Hide any visible children of the overlay. 784 // Hide any visible children of the overlay.
778 var overlay = $('sync-setup-overlay'); 785 var overlay = $('sync-setup-overlay');
779 for (var i = 0; i < overlay.children.length; i++) 786 for (var i = 0; i < overlay.children.length; i++)
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
820 'closeOverlay', 827 'closeOverlay',
821 'showSetupUI', 828 'showSetupUI',
822 'startSignIn', 829 'startSignIn',
823 'doSignOutOnAuthError', 830 'doSignOutOnAuthError',
824 'showSyncSetupPage', 831 'showSyncSetupPage',
825 'showCustomizePage', 832 'showCustomizePage',
826 'showStopSyncingUI', 833 'showStopSyncingUI',
827 ]); 834 ]);
828 835
829 // Export 836 // Export
830 return { 837 return {SyncSetupOverlay: SyncSetupOverlay};
831 SyncSetupOverlay: SyncSetupOverlay
832 };
833 }); 838 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698