This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2 in repository tor-browser.
commit d3f26be63d2106c48f47dabd51ff6416d97a9e64 Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Thu Apr 7 12:23:55 2022 +0200
fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser
Changes introduced by !275 --- browser/components/torconnect/TorConnectParent.jsm | 53 +- .../torconnect/content/aboutTorConnect.css | 178 ++++-- .../torconnect/content/aboutTorConnect.js | 675 +++++++++++++-------- .../torconnect/content/aboutTorConnect.xhtml | 24 +- browser/components/torconnect/content/globe.svg | 4 - browser/components/torconnect/jar.mn | 1 - toolkit/modules/RemotePageAccessManager.jsm | 7 +- 7 files changed, 596 insertions(+), 346 deletions(-)
diff --git a/browser/components/torconnect/TorConnectParent.jsm b/browser/components/torconnect/TorConnectParent.jsm index dd3d1b2410f95..3c2a56934c145 100644 --- a/browser/components/torconnect/TorConnectParent.jsm +++ b/browser/components/torconnect/TorConnectParent.jsm @@ -4,13 +4,18 @@ var EXPORTED_SYMBOLS = ["TorConnectParent"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); -const { TorConnect, TorConnectTopics, TorConnectState, TorCensorshipLevel } = ChromeUtils.import( - "resource:///modules/TorConnect.jsm" -); +const { + InternetStatus, + TorConnect, + TorConnectTopics, + TorConnectState, +} = ChromeUtils.import("resource:///modules/TorConnect.jsm"); const { TorSettings, TorSettingsTopics, TorSettingsData } = ChromeUtils.import( "resource:///modules/TorSettings.jsm" );
+const BroadcastTopic = "about-torconnect:broadcast"; + /* This object is basically a marshalling interface between the TorConnect module and a particular about:torconnect page @@ -24,15 +29,16 @@ class TorConnectParent extends JSWindowActorParent {
this.state = { State: TorConnect.state, - DetectedCensorshiplevel: TorConnect.detectedCensorshiplevel, StateChanged: false, + PreviousState: TorConnectState.Initial, ErrorMessage: TorConnect.errorMessage, ErrorDetails: TorConnect.errorDetails, BootstrapProgress: TorConnect.bootstrapProgress, BootstrapStatus: TorConnect.bootstrapStatus, + InternetStatus: TorConnect.internetStatus, ShowViewLog: TorConnect.logHasWarningOrError, QuickStartEnabled: TorSettings.quickstart.enabled, - CountryCodes: TorConnect.countryCodes, + UIState: TorConnect.uiState, };
// JSWindowActiveParent derived objects cannot observe directly, so create a member @@ -50,6 +56,7 @@ class TorConnectParent extends JSWindowActorParent { self.state.StateChanged = false; switch (aTopic) { case TorConnectTopics.StateChange: { + self.state.PreviousState = self.state.State; self.state.State = obj.state; self.state.StateChanged = true;
@@ -73,13 +80,7 @@ class TorConnectParent extends JSWindowActorParent { case TorConnectTopics.BootstrapError: { self.state.ErrorMessage = obj.message; self.state.ErrorDetails = obj.details; - self.state.DetectedCensorshiplevel = obj.censorshipLevel; - - // With severe censorshp, we offer user list of countries to try - if (self.state.DetectedCensorshiplevel == TorCensorshipLevel.Severe) { - self.state.CountryCodes = TorConnect.countryCodes; - } - + self.state.InternetStatus = TorConnect.internetStatus; self.state.ShowViewLog = true; break; } @@ -114,6 +115,17 @@ class TorConnectParent extends JSWindowActorParent { this.torConnectObserver, TorSettingsTopics.SettingChanged ); + + this.userActionObserver = { + observe(aSubject, aTopic, aData) { + let obj = aSubject?.wrappedJSObject; + if (obj) { + obj.connState = self.state; + self.sendAsyncMessage("torconnect:user-action", obj); + } + }, + }; + Services.obs.addObserver(this.userActionObserver, BroadcastTopic); }
willDestroy() { @@ -126,6 +138,7 @@ class TorConnectParent extends JSWindowActorParent { this.torConnectObserver, TorSettingsTopics.SettingChanged ); + Services.obs.removeObserver(this.userActionObserver, BroadcastTopic); }
async receiveMessage(message) { @@ -137,9 +150,6 @@ class TorConnectParent extends JSWindowActorParent { case "torconnect:open-tor-preferences": TorConnect.openTorPreferences(); break; - case "torconnect:view-tor-logs": - TorConnect.viewTorLogs(); - break; case "torconnect:cancel-bootstrap": TorConnect.cancelBootstrap(); break; @@ -149,21 +159,32 @@ class TorConnectParent extends JSWindowActorParent { case "torconnect:begin-autobootstrap": TorConnect.beginAutoBootstrap(message.data); break; + case "torconnect:view-tor-logs": + TorConnect.viewTorLogs(); + break; case "torconnect:restart": Services.startup.quit( Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit ); break; + case "torconnect:set-ui-state": + TorConnect.uiState = message.data; + this.state.UIState = TorConnect.uiState; + break; + case "torconnect:broadcast-user-action": + Services.obs.notifyObservers(message.data, BroadcastTopic); + break; case "torconnect:get-init-args": // called on AboutTorConnect.init(), pass down all state data it needs to init
// pretend this is a state transition on init // so we always get fresh UI this.state.StateChanged = true; + this.state.UIState = TorConnect.uiState; return { TorStrings, TorConnectState, - TorCensorshipLevel, + InternetStatus, Direction: Services.locale.isAppLocaleRTL ? "rtl" : "ltr", State: this.state, CountryNames: TorConnect.countryNames, diff --git a/browser/components/torconnect/content/aboutTorConnect.css b/browser/components/torconnect/content/aboutTorConnect.css index 0a3dc9fbd75fd..ae6a76db9fe75 100644 --- a/browser/components/torconnect/content/aboutTorConnect.css +++ b/browser/components/torconnect/content/aboutTorConnect.css @@ -10,22 +10,22 @@ --onion-radius: 75px; }
-/* override firefox's default blue focus coloring */ -:focus { +input[type="checkbox"]:focus, select:focus { outline: none!important; box-shadow: 0 0 0 3px var(--purple-30) !important; border: 1px var(--purple-80) solid !important; }
-@media (prefers-color-scheme: dark) +@media (-moz-toolbar-prefers-color-scheme: dark) { - :focus { + input[type="checkbox"]:focus, select:focus { box-shadow: 0 0 0 3px var(--purple-50)!important; } }
#breadcrumbs { display: flex; + align-items: center; margin: 0 0 24px 0; color: var(--grey-40); } @@ -38,15 +38,24 @@ display: flex; margin: 0; margin-inline-start: 20px; + padding: 8px; }
.breadcrumb-item { + align-items: center; cursor: pointer; color: var(--in-content-text-color); + border-radius: 4px; }
.breadcrumb-item:hover { - color: var(--blue-60); + color: var(--in-content-accent-color); + background-color: var(--in-content-button-background-hover); +} + +.breadcrumb-item:active { + color: var(--in-content-accent-color-active); + background-color: var(--in-content-button-background-active); }
.breadcrumb-separator { @@ -60,30 +69,44 @@
.breadcrumb-icon { display: inline list-item; + height: 16px; list-style-position: inside; fill: currentColor; -moz-context-properties: fill; }
-.breadcrumb-label { - margin-top: -1px; +.breadcrumb-item.active { + color: var(--in-content-accent-color); }
-#breadcrumbs .active { - color: var(--blue-60); +.breadcrumb-item.disabled, .breadcrumb-item.disabled:hover, .breadcrumb-item.disabled:active { + color: var(--in-content-text-color); + opacity: 0.4; + cursor: default; }
-#breadcrumbs .disabled, #breadcrumbs .disabled:hover { - color: var(--green-90-a40); - cursor: default; +.breadcrumb-item.error { + color: var(--in-content-danger-button-background); +} + +.breadcrumb-item.error:hover { + color: var(--in-content-danger-button-background-hover); +} + +.breadcrumb-item.error:active { + color: var(--in-content-danger-button-background-active); }
-#breadcrumbs .error { - color: var(--red-60); +.breadcrumb-item.hidden, .breadcrumb-separator.hidden { + display: none; }
-#connection-assist { - margin-left: 0; +#connect-to-tor { + margin-inline-start: 0; +} + +#connect-to-tor-icon { + list-style-image: url("chrome://browser/content/torconnect/onion.svg"); }
#connection-assist-icon { @@ -102,30 +125,28 @@ list-style-image: url("chrome://browser/content/torconnect/bridge.svg"); }
-button.primary { - background-color: var(--purple-60)!important; - color: white!important; - fill: white!important; -} - -button.primary:hover { - background-color: var(--purple-70)!important; - color: white!important; - fill: white!important; -} - -button.primary:active { - background-color: var(--purple-80)!important; - color: white!important; - fill: white!important; +button { + --purple-button-text-color: rgb(251,251,254); + --in-content-primary-button-text-color: var(--purple-button-text-color); + --in-content-primary-button-background: var(--purple-60); + --in-content-primary-button-text-color-hover: var(--purple-button-text-color); + --in-content-primary-button-background-hover: var(--purple-70); + --in-content-primary-button-text-color-active: var(--purple-button-text-color); + --in-content-primary-button-background-active: var(--purple-80); + --in-content-focus-outline-color: var(--purple-60); + fill: white; }
-div#locationDropdownLabel { +#locationDropdownLabel { margin-block: auto; margin-inline: 4px; }
-/* these two follow similar css in error-pages.css for buttons */ +#locationDropdownLabel.error { + color: var(--in-content-danger-button-background) +} + +/* this follows similar css in error-pages.css for buttons */ @media only screen and (min-width: 480px) { form#locationDropdown { margin-inline: 4px; @@ -136,14 +157,13 @@ div#locationDropdownLabel { }
@media only screen and (max-width: 480px) { - form#locationDropdown, - div#locationDropdownLabel { - margin: 0.66em 0 0; + #tryAgainButton { + margin-top: 4px; } +}
- form#locationDropdown { - width: 100%; - } +form#locationDropdown { + width: 240px; }
form#locationDropdown select { @@ -182,21 +202,79 @@ input[type="checkbox"]:not(:disabled):active:checked { background-color: var(--purple-80)!important; }
-#progressBackground { - position:fixed; - padding:0; - margin:0; - top:0; - left:0; +:root { + --progressbar-shadow-start: rgba(255, 255, 255, 0.7); + --progressbar-gradient: linear-gradient(90deg, #FC00FF 0%, #00DBDE 50%, #FC00FF 100%); +} + +@media (-moz-toolbar-prefers-color-scheme: dark) { + :root { + --progressbar-shadow-start: rgba(28, 27, 34, 0.7); + } +} + +#progressBar { + position: fixed; + top: 0; + inset-inline-start: 0; width: 0%; + padding: 0; + margin: 0; + animation: progressAnimation 5s ease infinite; +} + +#progressBackground { + height: 66px; + margin-top: -26px; + background-image: + linear-gradient(var(--progressbar-shadow-start), var(--in-content-page-background) 100%), + var(--progressbar-gradient); + background-position: inherit; + filter: blur(5px); + border-end-end-radius: 33px; +} + +#progressSolid { + position: absolute; + top: 0; + width: 100%; height: 7px; - background-image: linear-gradient(90deg, rgb(20, 218, 221) 0%, rgb(128, 109, 236) 100%); - border-radius: 0; + background-image: var(--progressbar-gradient); + background-position: inherit; +} + +#progressBackground, #progressSolid { + background-size: 200% 100%; +} + +@keyframes progressAnimation { + 0% { + background-position: 200%; + } + 50% { + background-position: 100%; + } + 100% { + background-position: 0%; + } +} + +@keyframes progressAnimation { + 0% { + background-position: 200%; + } + 50% { + background-position: 100%; + } + 100% { + background-position: 0%; + } }
#connectPageContainer { margin-top: 10vh; - width: 50%; + width: 100%; + max-width: 45em; }
#quickstartCheckbox, #quickstartCheckboxLabel { @@ -238,7 +316,7 @@ body { fill: var(--onion-color); }
-.title.error { +.title.offline, .title.assist, .title.final { background-image: url("chrome://browser/content/torconnect/connection-failure.svg"); }
diff --git a/browser/components/torconnect/content/aboutTorConnect.js b/browser/components/torconnect/content/aboutTorConnect.js index 8710eef95a49e..8b5f7216e9ef8 100644 --- a/browser/components/torconnect/content/aboutTorConnect.js +++ b/browser/components/torconnect/content/aboutTorConnect.js @@ -5,13 +5,23 @@ // populated in AboutTorConnect.init() let TorStrings = {}; let TorConnectState = {}; -let TorCensorshipLevel = {}; +let InternetStatus = {}; + +const UIStates = Object.freeze({ + ConnectToTor: "ConnectToTor", + Offline: "Offline", + ConnectionAssist: "ConnectionAssist", + CouldNotLocate: "CouldNotLocate", + LocationConfirm: "LocationConfirm", + FinalError: "FinalError", +});
const BreadcrumbStatus = Object.freeze({ - Disabled: -1, - Default: 0, - Active: 1, - Error: 2, + Hidden: "hidden", + Disabled: "disabled", + Default: "default", + Active: "active", + Error: "error", });
class AboutTorConnect { @@ -23,24 +33,27 @@ class AboutTorConnect { }, progress: { description: "p#connectShortDescText", - meter: "div#progressBackground", + meter: "div#progressBar", }, breadcrumbs: { container: "#breadcrumbs", + connectToTor: { + link: "#connect-to-tor", + label: "#connect-to-tor .breadcrumb-label", + }, connectionAssist: { + separator: "#connection-assist-separator", link: "#connection-assist", label: "#connection-assist .breadcrumb-label", }, - locationSettings: { - link: "#location-settings", - label: "#location-settings .breadcrumb-label", - }, tryBridge: { + separator: "#try-bridge-separator", link: "#try-bridge", label: "#try-bridge .breadcrumb-label", }, }, viewLog: { + container: "#viewLogContainer", link: "span#viewLogLink", }, quickstart: { @@ -54,10 +67,9 @@ class AboutTorConnect { cancel: "button#cancelButton", connect: "button#connectButton", tryBridge: "button#tryBridgeButton", - locationDropdownLabel: "div#locationDropdownLabel", + locationDropdownLabel: "#locationDropdownLabel", locationDropdown: "form#locationDropdown", locationDropdownSelect: "form#locationDropdown select", - tryAgain: "button#tryAgainButton", }, });
@@ -74,17 +86,23 @@ class AboutTorConnect { breadcrumbContainer: document.querySelector( this.selectors.breadcrumbs.container ), + connectToTorLink: document.querySelector( + this.selectors.breadcrumbs.connectToTor.link + ), + connectToTorLabel: document.querySelector( + this.selectors.breadcrumbs.connectToTor.label + ), + connectionAssistSeparator: document.querySelector( + this.selectors.breadcrumbs.connectionAssist.separator + ), connectionAssistLink: document.querySelector( this.selectors.breadcrumbs.connectionAssist.link ), connectionAssistLabel: document.querySelector( this.selectors.breadcrumbs.connectionAssist.label ), - locationSettingsLink: document.querySelector( - this.selectors.breadcrumbs.locationSettings.link - ), - locationSettingsLabel: document.querySelector( - this.selectors.breadcrumbs.locationSettings.label + tryBridgeSeparator: document.querySelector( + this.selectors.breadcrumbs.tryBridge.separator ), tryBridgeLink: document.querySelector( this.selectors.breadcrumbs.tryBridge.link @@ -92,6 +110,7 @@ class AboutTorConnect { tryBridgeLabel: document.querySelector( this.selectors.breadcrumbs.tryBridge.label ), + viewLogContainer: document.querySelector(this.selectors.viewLog.container), viewLogLink: document.querySelector(this.selectors.viewLog.link), quickstartContainer: document.querySelector( this.selectors.quickstart.container @@ -104,7 +123,6 @@ class AboutTorConnect { configureButton: document.querySelector(this.selectors.buttons.configure), cancelButton: document.querySelector(this.selectors.buttons.cancel), connectButton: document.querySelector(this.selectors.buttons.connect), - tryBridgeButton: document.querySelector(this.selectors.buttons.tryBridge), locationDropdownLabel: document.querySelector( this.selectors.buttons.locationDropdownLabel ), @@ -114,27 +132,44 @@ class AboutTorConnect { locationDropdownSelect: document.querySelector( this.selectors.buttons.locationDropdownSelect ), - tryAgainButton: document.querySelector(this.selectors.buttons.tryAgain), + tryBridgeButton: document.querySelector(this.selectors.buttons.tryBridge), });
// a redirect url can be passed as a query parameter for the page to // forward us to once bootstrap completes (otherwise the window will just close) redirect = null;
+ uiState = { + currentState: UIStates.ConnectToTor, + connectIsTryAgain: false, + allowAutomaticLocation: true, + selectedLocation: "automatic", + bootstrapCause: UIStates.ConnectToTor, + }; + locations = {};
+ constructor() { + this.uiStates = Object.freeze( + Object.fromEntries([ + [UIStates.ConnectToTor, this.showConnectToTor.bind(this)], + [UIStates.Offline, this.showOffline.bind(this)], + [UIStates.ConnectionAssist, this.showConnectionAssistant.bind(this)], + [UIStates.CouldNotLocate, this.showCouldNotLocate.bind(this)], + [UIStates.LocationConfirm, this.showLocationConfirmation.bind(this)], + [UIStates.FinalError, this.showFinalError.bind(this)], + ]) + ); + } + beginBootstrap() { - this.hide(this.elements.connectButton); - this.hide(this.elements.quickstartContainer); - this.show(this.elements.cancelButton); - this.elements.cancelButton.focus(); RPMSendAsyncMessage("torconnect:begin-bootstrap"); }
beginAutoBootstrap(countryCode) { - this.hide(this.elements.tryBridgeButton); - this.show(this.elements.cancelButton); - this.elements.cancelButton.focus(); + if (countryCode === "automatic") { + countryCode = ""; + } RPMSendAsyncMessage("torconnect:begin-autobootstrap", countryCode); }
@@ -142,16 +177,24 @@ class AboutTorConnect { RPMSendAsyncMessage("torconnect:cancel-bootstrap"); }
+ transitionUIState(nextState, connState) { + if (nextState !== this.uiState.currentState) { + this.uiState.currentState = nextState; + this.saveUIState(); + } + this.uiStates[nextState](connState); + } + + saveUIState() { + RPMSendAsyncMessage("torconnect:set-ui-state", this.uiState); + } + /* Element helper methods */
show(element, primary) { - if (primary) { - element.classList.add("primary"); - } else { - element.classList.remove("primary"); - } + element.classList.toggle("primary", primary !== undefined && primary); element.removeAttribute("hidden"); }
@@ -160,14 +203,14 @@ class AboutTorConnect { }
hideButtons() { + this.hide(this.elements.quickstartContainer); this.hide(this.elements.restartButton); this.hide(this.elements.configureButton); this.hide(this.elements.cancelButton); this.hide(this.elements.connectButton); - this.hide(this.elements.tryBridgeButton); this.hide(this.elements.locationDropdownLabel); this.hide(this.elements.locationDropdown); - this.hide(this.elements.tryAgainButton); + this.hide(this.elements.tryBridgeButton); }
populateLocations() { @@ -187,31 +230,29 @@ class AboutTorConnect { locationNodes.sort((left, right) => left.textContent.localeCompare(right.textContent) ); - this.elements.locationDropdownSelect.append( selectCountryRegion, ...locationNodes ); }
- populateSpecialLocations(specialLocations) { - this.removeSpecialLocations(); - if (!specialLocations || !specialLocations.length) { + populateFrequentLocations(locations) { + this.removeFrequentLocations(); + if (!locations || !locations.length) { return; }
const locationNodes = []; - for (const code of specialLocations) { + for (const code of locations) { const option = document.createElement("option"); option.value = code; - + option.className = "frequent-location"; // codes (partially) come from rdsys service, so make sure we have a // string defined for it let name = this.locations[code]; if (!name) { name = code; } - option.textContent = name; locationNodes.push(option); } @@ -220,28 +261,27 @@ class AboutTorConnect { left.textContent.localeCompare(right.textContent) );
- const disabledDividerNode = document.createElement("option"); - disabledDividerNode.setAttribute("disabled", true); - disabledDividerNode.className = "divider"; + const frequentGroup = document.createElement("optgroup"); + frequentGroup.setAttribute( + "label", + TorStrings.torConnect.frequentLocations + ); + frequentGroup.className = "frequent-location"; + const locationGroup = document.createElement("optgroup"); + locationGroup.setAttribute("label", TorStrings.torConnect.otherLocations); + locationGroup.className = "frequent-location"; + // options[0] is either "Select Country or Region" or "Automatic" this.elements.locationDropdownSelect.options[0].after( + frequentGroup, ...locationNodes, - disabledDividerNode + locationGroup ); }
- removeSpecialLocations() { + removeFrequentLocations() { const select = this.elements.locationDropdownSelect; - if (select.querySelector(".divider") === null) { - return; - } - - while (select.options.length > 1) { - // Skip the "select country/region" option - const opt = select.options[1]; - opt.remove(); - if (opt.className === "divider") { - break; - } + for (const option of select.querySelectorAll(".frequent-location")) { + option.remove(); } }
@@ -251,20 +291,15 @@ class AboutTorConnect { selectedIndex ]; if (!selectedOption.value) { - this.elements.tryAgainButton.setAttribute("disabled", "disabled"); + this.elements.tryBridgeButton.setAttribute("disabled", "disabled"); } else { - this.elements.tryAgainButton.removeAttribute("disabled"); + this.elements.tryBridgeButton.removeAttribute("disabled"); } }
setTitle(title, className) { this.elements.titleText.textContent = title; - if (className !== "error") { - this.elements.title.classList.remove("error"); - } - if (className !== "location") { - this.elements.title.classList.remove("location"); - } + this.elements.title.className = "title"; if (className) { this.elements.title.classList.add(className); } @@ -286,28 +321,30 @@ class AboutTorConnect { } }
- setBreadcrumbsStatus(connectionAssist, locationSettings, tryBridge) { + setBreadcrumbsStatus(connectToTor, connectionAssist, tryBridge) { this.elements.breadcrumbContainer.classList.remove("hidden"); - let elems = [ - [this.elements.connectionAssistLink, connectionAssist], - [this.elements.locationSettingsLink, locationSettings], - [this.elements.tryBridgeLink, tryBridge], + const elems = [ + [this.elements.connectToTorLink, connectToTor, null], + [ + this.elements.connectionAssistLink, + connectionAssist, + this.elements.connectionAssistSeparator, + ], + [ + this.elements.tryBridgeLink, + tryBridge, + this.elements.tryBridgeSeparator, + ], ]; - elems.forEach(([elem, status]) => { - elem.classList.remove("disabled"); - elem.classList.remove("active"); - elem.classList.remove("error"); - switch (status) { - case BreadcrumbStatus.Disabled: - elem.classList.add("disabled"); - break; - case BreadcrumbStatus.Active: - elem.classList.add("active"); - break; - case BreadcrumbStatus.Error: - elem.classList.add("error"); - break; + elems.forEach(([elem, status, separator]) => { + elem.classList.remove(BreadcrumbStatus.Hidden); + elem.classList.remove(BreadcrumbStatus.Disabled); + elem.classList.remove(BreadcrumbStatus.Active); + elem.classList.remove(BreadcrumbStatus.Error); + if (status !== "") { + elem.classList.add(status); } + separator?.classList.toggle("hidden", status === BreadcrumbStatus.Hidden); }); }
@@ -328,124 +365,59 @@ class AboutTorConnect { /* Per-state updates */
update_Initial(state) { - const hasError = false; - const showProgressbar = false; - - this.setTitle(TorStrings.torConnect.torConnect, hasError ? "error" : ""); - this.setProgress( - TorStrings.settings.torPreferencesDescription, - showProgressbar - ); - this.hide(this.elements.quickstartContainer); - this.hide(this.elements.viewLogLink); - this.hideButtons(); + this.showConnectToTor(state); }
update_Configuring(state) { - const hasError = state.ErrorMessage != null; - const showProgressbar = false; - - this.hide(this.elements.quickstartContainer); - this.hide(this.elements.viewLogLink); - this.hideButtons(); - - if (hasError) { - switch (state.DetectedCensorshiplevel) { - case TorCensorshipLevel.None: - // we shouldn't be able to get here - break; - case TorCensorshipLevel.Moderate: - // bootstrap failed once, offer auto bootstrap - this.showConnectionAssistant(state.ErrorDetails); - if (state.StateChanged) { - this.elements.tryBridgeButton.focus(); - } - break; - case TorCensorshipLevel.Severe: - // autobootstrap failed, verify correct location - this.showLocationSettings(state.CountryCodes, state.ErrorMessage); - if (state.StateChanged) { - this.elements.tryAgainButton.focus(); - } - break; - case TorCensorshipLevel.Extreme: - // finally offer to restart tor-browser or go to configure options - this.showFinalError(state); - break; - } - } else { - this.setTitle(TorStrings.torConnect.torConnect, ""); - this.setLongText(TorStrings.settings.torPreferencesDescription); - this.setProgress("", showProgressbar); - this.show(this.elements.quickstartContainer); - this.show(this.elements.configureButton); - this.show(this.elements.connectButton, true); - if (state.StateChanged) { - this.elements.connectButton.focus(); - } - this.elements.connectButton.textContent = - TorStrings.torConnect.torConnectButton; + if ( + state.StateChanged && + (state.PreviousState === TorConnectState.Bootstrapping || + state.PreviousState === TorConnectState.AutoBootstrapping) + ) { + // The bootstrap has been cancelled + this.transitionUIState(this.uiState.bootstrapCause, state); } }
update_AutoBootstrapping(state) { - const showProgressbar = true; - - if (state.DetectedCensorshiplevel >= TorCensorshipLevel.Severe) { - this.setTitle(TorStrings.torConnect.tryingBridgeAgain, ""); - } else { - this.setTitle(TorStrings.torConnect.tryingBridge, ""); - } - this.showConfigureConnectionLink(TorStrings.torConnect.assistDescription); - this.setProgress( - state.BootstrapStatus, - showProgressbar, - state.BootstrapProgress - ); - this.setBreadcrumbsStatus( - BreadcrumbStatus.Disabled, - BreadcrumbStatus.Disabled, - BreadcrumbStatus.Active - ); - if (state.ShowViewLog) { - this.show(this.elements.viewLogLink); - } else { - this.hide(this.elements.viewLogLink); - } - this.hideButtons(); - this.show(this.elements.cancelButton, true); - if (state.StateChanged) { - this.elements.cancelButton.focus(); - } + this.showBootstrapping(state); }
update_Bootstrapping(state) { - const showProgressbar = true; - - this.setTitle(TorStrings.torConnect.torConnecting, ""); - this.setLongText(TorStrings.settings.torPreferencesDescription); - this.setProgress("", showProgressbar, state.BootstrapProgress); - this.hideBreadcrumbs(); - if (state.ShowViewLog) { - this.show(this.elements.viewLogLink); - } else { - this.hide(this.elements.viewLogLink); - } - this.hideButtons(); - this.show(this.elements.cancelButton, true); - if (state.StateChanged) { - this.elements.cancelButton.focus(); - } + this.showBootstrapping(state); }
update_Error(state) { - const showProgressbar = false; - - this.setTitle(state.ErrorMessage, "error"); - this.setLongText(""); - this.setProgress(state.ErrorDetails, showProgressbar); - this.hideButtons(); - this.show(this.elements.viewLogLink); + if (!this.uiState.connectIsTryAgain) { + // TorConnect.hasBootstrapEverFailed remains false in case of Internet + // offline + this.uiState.connectIsTryAgain = true; + this.saveUIState(); + } + if (!state.StateChanged) { + return; + } + if (state.InternetStatus === InternetStatus.Offline) { + this.transitionUIState(UIStates.Offline, state); + } else if (state.PreviousState === TorConnectState.Bootstrapping) { + this.transitionUIState(UIStates.ConnectionAssist, state); + } else if (state.PreviousState === TorConnectState.AutoBootstrapping) { + if (this.uiState.bootstrapCause === UIStates.ConnectionAssist) { + this.transitionUIState( + this.getLocation() === "automatic" + ? UIStates.CouldNotLocate + : UIStates.LocationConfirm, + state + ); + } else { + this.transitionUIState(UIStates.FinalError, state); + } + } else { + console.error( + "We received an error starting from an unexpected state", + state + ); + } }
update_Bootstrapped(state) { @@ -467,23 +439,165 @@ class AboutTorConnect { // it isn't in use (eg using tor-launcher or system tor) }
- showConnectionAssistant(error) { - const hasError = !!error; - this.setTitle( - TorStrings.torConnect.couldNotConnect, - hasError ? "error" : "" + showConnectToTor(state) { + this.setTitle(TorStrings.torConnect.torConnect, ""); + this.setLongText(TorStrings.settings.torPreferencesDescription); + this.setProgress("", false); + this.hide(this.elements.viewLogContainer); + this.hideButtons(); + this.show(this.elements.quickstartContainer); + this.show(this.elements.configureButton); + this.show(this.elements.connectButton, true); + if (state?.StateChanged) { + this.elements.connectButton.focus(); + } + if (this.uiState.connectIsTryAgain) { + this.setBreadcrumbsStatus( + BreadcrumbStatus.Active, + BreadcrumbStatus.Default, + BreadcrumbStatus.Disabled + ); + this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain; + } + this.uiState.bootstrapCause = UIStates.ConnectToTor; + this.saveUIState(); + } + + showBootstrapping(state) { + const showProgressbar = true; + let title = ""; + let description = ""; + const breadcrumbs = [ + BreadcrumbStatus.Disabled, + BreadcrumbStatus.Disabled, + BreadcrumbStatus.Disabled, + ]; + switch (this.uiState.bootstrapCause) { + case UIStates.ConnectToTor: + breadcrumbs[0] = BreadcrumbStatus.Active; + title = this.uiState.connectIsTryAgain + ? TorStrings.torConnect.tryAgain + : TorStrings.torConnect.torConnecting; + description = TorStrings.settings.torPreferencesDescription; + break; + case UIStates.ConnectionAssist: + breadcrumbs[2] = BreadcrumbStatus.Active; + title = TorStrings.torConnect.tryingBridge; + description = TorStrings.torConnect.assistDescription; + break; + case UIStates.CouldNotLocate: + breadcrumbs[2] = BreadcrumbStatus.Active; + title = TorStrings.torConnect.tryingBridgeAgain; + description = TorStrings.torConnect.errorLocationDescription; + break; + case UIStates.LocationConfirm: + breadcrumbs[2] = BreadcrumbStatus.Active; + title = TorStrings.torConnect.tryingBridgeAgain; + description = TorStrings.torConnect.isLocationCorrectDescription; + break; + } + this.setTitle(title, ""); + this.showConfigureConnectionLink(description); + this.setProgress("", showProgressbar, state.BootstrapProgress); + this.setBreadcrumbsStatus(...breadcrumbs); + this.hideButtons(); + if (state.ShowViewLog) { + this.show(this.elements.viewLogContainer); + } else { + this.hide(this.elements.viewLogContainer); + } + this.show(this.elements.cancelButton, true); + if (state.StateChanged) { + this.elements.cancelButton.focus(); + } + } + + showOffline(error) { + this.setTitle(TorStrings.torConnect.noInternet, "offline"); + this.setLongText(TorStrings.torConnect.noInternetDescription); + this.setProgress(error, false); + this.setBreadcrumbsStatus( + BreadcrumbStatus.Default, + BreadcrumbStatus.Active, + BreadcrumbStatus.Hidden ); + this.show(this.elements.viewLogContainer); + this.hideButtons(); + this.show(this.elements.configureButton); + this.show(this.elements.connectButton, true); + this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain; + } + + showConnectionAssistant(state) { + this.setTitle(TorStrings.torConnect.couldNotConnect, "assist"); this.showConfigureConnectionLink(TorStrings.torConnect.assistDescription); - this.setProgress(error, false); + this.setProgress(state?.ErrorDetails, false); this.setBreadcrumbsStatus( + BreadcrumbStatus.Default, BreadcrumbStatus.Active, + BreadcrumbStatus.Disabled + ); + this.showLocationForm(false, TorStrings.torConnect.tryBridge); + if (state?.StateChanged) { + this.elements.tryBridgeButton.focus(); + } + this.uiState.bootstrapCause = UIStates.ConnectionAssist; + this.saveUIState(); + } + + showCouldNotLocate(state) { + this.uiState.allowAutomaticLocation = false; + this.setTitle(TorStrings.torConnect.errorLocation, "location"); + this.showConfigureConnectionLink( + TorStrings.torConnect.errorLocationDescription + ); + this.setProgress(state.ErrorMessage, false); + this.setBreadcrumbsStatus( BreadcrumbStatus.Default, + BreadcrumbStatus.Active, BreadcrumbStatus.Disabled ); + this.show(this.elements.viewLogContainer); + this.showLocationForm(true, TorStrings.torConnect.tryBridge); + if (state.StateChanged) { + this.elements.tryBridgeButton.focus(); + } + this.uiState.bootstrapCause = UIStates.CouldNotLocate; + this.saveUIState(); + } + + showLocationConfirmation(state) { + this.setTitle(TorStrings.torConnect.isLocationCorrect, "location"); + this.showConfigureConnectionLink( + TorStrings.torConnect.isLocationCorrectDescription + ); + this.setProgress(state.ErrorMessage, false); + this.setBreadcrumbsStatus( + BreadcrumbStatus.Default, + BreadcrumbStatus.Default, + BreadcrumbStatus.Active + ); + this.show(this.elements.viewLogContainer); + this.showLocationForm(true, TorStrings.torConnect.tryAgain); + if (state.StateChanged) { + this.elements.tryBridgeButton.focus(); + } + this.uiState.bootstrapCause = UIStates.LocationConfirm; + this.saveUIState(); + } + + showFinalError(state) { + this.setTitle(TorStrings.torConnect.finalError, "final"); + this.setLongText(TorStrings.torConnect.finalErrorDescription); + this.setProgress(state ? state.ErrorDetails : "", false); + this.setBreadcrumbsStatus( + BreadcrumbStatus.Default, + BreadcrumbStatus.Default, + BreadcrumbStatus.Active + ); this.hideButtons(); - this.show(this.elements.configureButton); - this.show(this.elements.connectButton); - this.show(this.elements.tryBridgeButton, true); + this.show(this.elements.restartButton); + this.show(this.elements.configureButton, true); }
showConfigureConnectionLink(text) { @@ -495,80 +609,100 @@ class AboutTorConnect { e.preventDefault(); RPMSendAsyncMessage("torconnect:open-tor-preferences"); }); - this.setLongText(pieces[0], link, pieces[1]); - } - - showLocationSettings(locations, error) { - const hasError = !!error; - if (hasError) { - this.setTitle(TorStrings.torConnect.errorLocation, "location"); - this.setLongText(TorStrings.torConnect.errorLocationDescription); - this.setBreadcrumbsStatus( - BreadcrumbStatus.Disabled, - BreadcrumbStatus.Error, - BreadcrumbStatus.Disabled - ); - this.elements.tryAgainButton.textContent = TorStrings.torConnect.tryAgain; + if (pieces.length > 1) { + const first = pieces.shift(); + this.setLongText(first, link, ...pieces); } else { - this.setTitle(TorStrings.torConnect.addLocation, "location"); - this.showConfigureConnectionLink( - TorStrings.torConnect.addLocationDescription - ); - this.setBreadcrumbsStatus( - BreadcrumbStatus.Default, - BreadcrumbStatus.Active, - BreadcrumbStatus.Disabled - ); - this.elements.tryAgainButton.textContent = - TorStrings.torConnect.tryBridge; + this.setLongText(text); } - this.setProgress(error, false); + } + + showLocationForm(isError, buttonLabel) { this.hideButtons(); - if (!locations || !locations.length) { - RPMSendQuery("torconnect:get-country-codes").then(codes => { - if (codes && codes.length) { - this.populateSpecialLocations(codes); - } - }); + RPMSendQuery("torconnect:get-country-codes").then(codes => { + if (codes && codes.length) { + this.populateFrequentLocations(codes); + this.setLocationFromState(); + } + }); + let firstOpt = this.elements.locationDropdownSelect.options[0]; + if (this.uiState.allowAutomaticLocation) { + firstOpt.value = "automatic"; + firstOpt.textContent = TorStrings.torConnect.automatic; } else { - this.populateSpecialLocations(locations); + firstOpt.value = ""; + firstOpt.textContent = TorStrings.torConnect.selectCountryRegion; } + this.setLocationFromState(); this.validateLocation(); this.show(this.elements.locationDropdownLabel); this.show(this.elements.locationDropdown); - this.show(this.elements.tryAgainButton, true); + this.elements.locationDropdownLabel.classList.toggle("error", isError); + this.show(this.elements.tryBridgeButton, true); + this.elements.tryBridgeButton.classList.toggle("danger-button", isError); + if (buttonLabel !== undefined) { + this.elements.tryBridgeButton.textContent = buttonLabel; + } }
- showFinalError(state) { - this.setTitle(TorStrings.torConnect.finalError, "error"); - this.setLongText(TorStrings.torConnect.finalErrorDescription); - this.setProgress(state ? state.ErrorDetails : "", false); - this.hideButtons(); - this.show(this.elements.restartButton); - this.show(this.elements.configureButton); - this.show(this.elements.connectButton, true); + getLocation() { + const selectedIndex = this.elements.locationDropdownSelect.selectedIndex; + return this.elements.locationDropdownSelect.options[selectedIndex].value; + } + + setLocationFromState() { + if (this.getLocation() === this.uiState.selectedLocation) { + return; + } + const options = this.elements.locationDropdownSelect.options; + // We need to do this way, because we have repeated values that break + // the .value way to select (which would however require the label, + // rather than the code)... + for (let i = 0; i < options.length; i++) { + if (options[i].value === this.uiState.selectedLocation) { + this.elements.locationDropdownSelect.selectedIndex = i; + break; + } + } + this.validateLocation(); }
initElements(direction) { document.documentElement.setAttribute("dir", direction);
+ this.elements.connectToTorLink.addEventListener("click", event => { + if (this.uiState.currentState === UIStates.ConnectToTor) { + return; + } + this.transitionUIState(UIStates.ConnectToTor, null); + RPMSendAsyncMessage("torconnect:broadcast-user-action", { + uiState: UIStates.ConnectToTor, + }); + }); + this.elements.connectToTorLabel.textContent = + TorStrings.torConnect.torConnect; this.elements.connectionAssistLink.addEventListener("click", event => { - if (!this.elements.connectionAssistLink.classList.contains("disabled")) { - this.showConnectionAssistant(); + if ( + this.elements.connectionAssistLink.classList.contains( + BreadcrumbStatus.Active + ) || + this.elements.connectionAssistLink.classList.contains( + BreadcrumbStatus.Disabled + ) + ) { + return; } + this.transitionUIState(UIStates.ConnectionAssist, null); + RPMSendAsyncMessage("torconnect:broadcast-user-action", { + uiState: UIStates.ConnectionAssist, + }); }); this.elements.connectionAssistLabel.textContent = TorStrings.torConnect.breadcrumbAssist; - this.elements.locationSettingsLink.addEventListener("click", event => { - if (!this.elements.connectionAssistLink.classList.contains("disabled")) { - this.showLocationSettings(); - } - }); - this.elements.locationSettingsLabel.textContent = - TorStrings.torConnect.breadcrumbLocation; this.elements.tryBridgeLabel.textContent = TorStrings.torConnect.breadcrumbTryBridge;
+ this.hide(this.elements.viewLogContainer); this.elements.viewLogLink.textContent = TorStrings.torConnect.viewLog; this.elements.viewLogLink.addEventListener("click", event => { RPMSendAsyncMessage("torconnect:view-tor-logs"); @@ -606,26 +740,25 @@ class AboutTorConnect {
this.populateLocations(); this.elements.locationDropdownSelect.addEventListener("change", () => { + this.uiState.selectedLocation = this.getLocation(); + this.saveUIState(); this.validateLocation(); - }); - - this.elements.tryBridgeButton.textContent = TorStrings.torConnect.tryBridge; - this.elements.tryBridgeButton.addEventListener("click", () => { - this.beginAutoBootstrap(); + RPMSendAsyncMessage("torconnect:broadcast-user-action", { + location: this.uiState.selectedLocation, + }); });
this.elements.locationDropdownLabel.textContent = TorStrings.torConnect.yourLocation;
- this.elements.tryAgainButton.textContent = TorStrings.torConnect.tryAgain; - this.elements.tryAgainButton.setAttribute("disabled", "disabled"); - this.elements.tryAgainButton.addEventListener("click", () => { - let selectedIndex = this.elements.locationDropdownSelect.selectedIndex; - let selectedOption = this.elements.locationDropdownSelect.options[ - selectedIndex - ]; - - this.beginAutoBootstrap(selectedOption.value); + this.elements.tryBridgeButton.textContent = TorStrings.torConnect.tryBridge; + this.elements.tryBridgeButton.addEventListener("click", () => { + const value = this.getLocation(); + if (value === "automatic") { + this.beginAutoBootstrap(); + } else { + this.beginAutoBootstrap(value); + } }); }
@@ -634,6 +767,15 @@ class AboutTorConnect { RPMAddMessageListener("torconnect:state-change", ({ data }) => { this.updateUI(data); }); + RPMAddMessageListener("torconnect:user-action", ({ data }) => { + if (data.location) { + this.uiState.selectedLocation = data.location; + this.setLocationFromState(); + } + if (data.uiState !== undefined) { + this.transitionUIState(data.uiState, data.connState); + } + }); }
initKeyboardShortcuts() { @@ -664,13 +806,20 @@ class AboutTorConnect { // various constants TorStrings = Object.freeze(args.TorStrings); TorConnectState = Object.freeze(args.TorConnectState); - TorCensorshipLevel = Object.freeze(args.TorCensorshipLevel); + InternetStatus = Object.freeze(args.InternetStatus); this.locations = args.CountryNames;
this.initElements(args.Direction); this.initObservers(); this.initKeyboardShortcuts();
+ if (Object.keys(args.State.UIState).length) { + this.uiState = args.State.UIState; + } else { + args.State.UIState = this.uiState; + this.saveUIState(); + } + this.uiStates[this.uiState.currentState](args.State); // populate UI based on current state this.updateUI(args.State); } diff --git a/browser/components/torconnect/content/aboutTorConnect.xhtml b/browser/components/torconnect/content/aboutTorConnect.xhtml index a98af43e2d53f..77d2e68895708 100644 --- a/browser/components/torconnect/content/aboutTorConnect.xhtml +++ b/browser/components/torconnect/content/aboutTorConnect.xhtml @@ -7,19 +7,22 @@ <link rel="stylesheet" href="chrome://browser/content/torconnect/aboutTorConnect.css" type="text/css" media="all" /> </head> <body> - <div id="progressBackground"></div> + <div id="progressBar"> + <div id="progressBackground" /> + <div id="progressSolid" /> + </div> <div id="connectPageContainer" class="container"> <div id="breadcrumbs" class="hidden"> - <span id="connection-assist" class="breadcrumb-item"> - <span id="connection-assist-icon" class="breadcrumb-icon" /> + <span id="connect-to-tor" class="breadcrumb-item"> + <span id="connect-to-tor-icon" class="breadcrumb-icon" /> <span class="breadcrumb-label"/> </span> - <span class="breadcrumb-separator breadcrumb-icon" /> - <span id="location-settings" class="breadcrumb-item"> - <span id="location-settings-icon" class="breadcrumb-icon" /> + <span id="connection-assist-separator" class="breadcrumb-separator breadcrumb-icon" /> + <span id="connection-assist" class="breadcrumb-item"> + <span id="connection-assist-icon" class="breadcrumb-icon" /> <span class="breadcrumb-label"/> </span> - <span class="breadcrumb-separator breadcrumb-icon" /> + <span id="try-bridge-separator" class="breadcrumb-separator breadcrumb-icon" /> <span id="try-bridge" class="breadcrumb-item"> <span id="try-bridge-icon" class="breadcrumb-icon" /> <span class="breadcrumb-label"/> @@ -37,7 +40,7 @@ </div>
<div id="viewLogContainer"> - <span id="viewLogLink" hidden="true"></span> + <span id="viewLogLink"></span> </div>
<div id="quickstartContainer"> @@ -50,13 +53,12 @@ <button id="configureButton" hidden="true"></button> <button id="cancelButton" hidden="true"></button> <button id="connectButton" class="primary" hidden="true"></button> - <button id="tryBridgeButton" class="primary" hidden="true"></button> - <div id="locationDropdownLabel"/> + <label id="locationDropdownLabel" for="countries"/> <form id="locationDropdown" hidden="true"> <select id="countries"> </select> </form> - <button id="tryAgainButton" class="primary" hidden="true"></button> + <button id="tryBridgeButton" class="primary" hidden="true"></button> </div> </div> </div> diff --git a/browser/components/torconnect/content/globe.svg b/browser/components/torconnect/content/globe.svg deleted file mode 100644 index f4d1f19b43ce8..0000000000000 --- a/browser/components/torconnect/content/globe.svg +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> - <path d="M8 0.5C6.01088 0.5 4.10322 1.29018 2.6967 2.6967C1.29018 4.10322 0.5 6.01088 0.5 8C0.5 9.98912 1.29018 11.8968 2.6967 13.3033C4.10322 14.7098 6.01088 15.5 8 15.5C9.98912 15.5 11.8968 14.7098 13.3033 13.3033C14.7098 11.8968 15.5 9.98912 15.5 8C15.5 6.01088 14.7098 4.10322 13.3033 2.6967C11.8968 1.29018 9.98912 0.5 8 0.5ZM10.447 2.25C11.4738 2.69088 12.3637 3.39877 13.0242 4.30006C13.6848 5.20135 14.0918 6.26313 14.203 7.375H11.974C11.8509 5.51288 11.1778 3.72922 10.04 2.25H10 [...] -</svg> diff --git a/browser/components/torconnect/jar.mn b/browser/components/torconnect/jar.mn index 8ca0b0651523e..043bd1435cda5 100644 --- a/browser/components/torconnect/jar.mn +++ b/browser/components/torconnect/jar.mn @@ -5,7 +5,6 @@ browser.jar: content/browser/torconnect/aboutTorConnect.js (content/aboutTorConnect.js) content/browser/torconnect/arrow-right.svg (content/arrow-right.svg) content/browser/torconnect/bridge.svg (content/bridge.svg) - content/browser/torconnect/globe.svg (content/globe.svg) content/browser/torconnect/connection-failure.svg (content/connection-failure.svg) content/browser/torconnect/connection-location.svg (content/connection-location.svg) content/browser/torconnect/onion.svg (content/onion.svg) diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm index 225429d95b667..73c48281276b9 100644 --- a/toolkit/modules/RemotePageAccessManager.jsm +++ b/toolkit/modules/RemotePageAccessManager.jsm @@ -219,7 +219,10 @@ let RemotePageAccessManager = { RPMSendQuery: ["FetchUpdateData"], }, "about:torconnect": { - RPMAddMessageListener: ["torconnect:state-change"], + RPMAddMessageListener: [ + "torconnect:state-change", + "torconnect:user-action", + ], RPMSendAsyncMessage: [ "torconnect:open-tor-preferences", "torconnect:begin-bootstrap", @@ -228,6 +231,8 @@ let RemotePageAccessManager = { "torconnect:set-quickstart", "torconnect:view-tor-logs", "torconnect:restart", + "torconnect:set-ui-state", + "torconnect:broadcast-user-action", ], RPMSendQuery: [ "torconnect:get-init-args",