diff --git a/.eslintignore b/.eslintignore index b767c50355d7b0d27fe77fb59ffacb7e65c6ea57..bbe2292bb8b7ac3953b6fb4d03f11fb5dbf3a162 100644 --- a/.eslintignore +++ b/.eslintignore @@ -57,7 +57,11 @@ xulrunner/** # browser/ exclusions browser/app/** -browser/base/** +browser/base/content/browser-social.js +browser/base/content/nsContextMenu.js +browser/base/content/sanitizeDialog.js +browser/base/content/test/** +browser/base/content/newtab/** browser/components/customizableui/** browser/components/downloads/** browser/components/feeds/** diff --git a/browser/.eslintrc b/browser/.eslintrc index 3b1e5f049f645a27665a1b937ccad1c89b9b9df6..e7e9d8ce5928dd7732ba6fbbcd525e9f1d92c963 100644 --- a/browser/.eslintrc +++ b/browser/.eslintrc @@ -1,6 +1,5 @@ { - // When adding items to this file please check for effects on sub-directories. - "rules": { - "eol-last": 2, - } + "extends": [ + "../toolkit/.eslintrc" + ] } diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index e229f15ee138ab58a989fb0f17903b383da6a777..293c20a9d69a6589ba2fa8c48fd4b983dacd1c5d 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1471,6 +1471,11 @@ pref("identity.fxaccounts.profile_image.enabled", true); // Token server used by the FxA Sync identity. pref("identity.sync.tokenserver.uri", "https://token.services.mozilla.com/1.0/sync/1.5"); +// URLs for promo links to mobile browsers. Note that consumers are expected to +// append a value for utm_campaign. +pref("identity.mobilepromo.android", "https://www.mozilla.org/firefox/android/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign="); +pref("identity.mobilepromo.ios", "https://www.mozilla.org/firefox/ios/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign="); + // Migrate any existing Firefox Account data from the default profile to the // Developer Edition profile. #ifdef MOZ_DEV_EDITION diff --git a/browser/base/content/aboutTabCrashed.js b/browser/base/content/aboutTabCrashed.js index 2886fffa20e3b1f754561be581c36676a4e874cb..bb11730f4150bd29342b3eb90368b6bc5b4327e3 100644 --- a/browser/base/content/aboutTabCrashed.js +++ b/browser/base/content/aboutTabCrashed.js @@ -255,4 +255,4 @@ var AboutTabCrashed = { }, }; -AboutTabCrashed.init(); \ No newline at end of file +AboutTabCrashed.init(); diff --git a/browser/base/content/browser-gestureSupport.js b/browser/base/content/browser-gestureSupport.js index 236f0ad0ee028dfffc1b1d26ab8609555620b1f4..f14cb4350c4f8d287b7215b845bbfb62d552ba28 100644 --- a/browser/base/content/browser-gestureSupport.js +++ b/browser/base/content/browser-gestureSupport.js @@ -255,7 +255,7 @@ var gGestureSupport = { * Source array containing any number of elements * @yield Array that is a subset of the input array from full set to empty */ - _power: function GS__power(aArray) { + _power: function* GS__power(aArray) { // Create a bitmask based on the length of the array let num = 1 << aArray.length; while (--num >= 0) { diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc index 142386d8ea36959759031f4b48c093e6001f085b..383f841e6f63237fbf756a9cb3b573b71ee552eb 100644 --- a/browser/base/content/browser-menubar.inc +++ b/browser/base/content/browser-menubar.inc @@ -367,9 +367,9 @@ <menuseparator id="sanitizeSeparator"/> <menuitem id="sync-tabs-menuitem" class="syncTabsMenuItem" - label="&syncTabsMenu2.label;" + label="&syncTabsMenu3.label;" oncommand="BrowserOpenSyncTabs();" - disabled="true"/> + hidden="true"/> <menuitem id="historyRestoreLastSession" label="&historyRestoreLastSession.label;" command="Browser:RestoreLastSession"/> diff --git a/browser/base/content/browser-sets.inc b/browser/base/content/browser-sets.inc index 0b5b1030b0fea26ec87d70043cdd7c01ebd0d7ed..d65de7c73db9769ee467e6b5f8b900364f79319d 100644 --- a/browser/base/content/browser-sets.inc +++ b/browser/base/content/browser-sets.inc @@ -177,6 +177,14 @@ <broadcaster id="isFrameImage"/> <broadcaster id="singleFeedMenuitemState" disabled="true"/> <broadcaster id="multipleFeedsMenuState" hidden="true"/> + + <!-- Sync broadcasters --> + <!-- A broadcaster of a number of attributes suitable for "sync now" UI - + A 'syncstatus' attribute is set while actively syncing, and the label + attribute which changes from "sync now" to "syncing" etc. --> + <broadcaster id="sync-status"/> + <!-- broadcasters of the "hidden" attribute to reflect setup state for + menus --> <broadcaster id="sync-setup-state"/> <broadcaster id="sync-syncnow-state" hidden="true"/> <broadcaster id="sync-reauth-state" hidden="true"/> diff --git a/browser/base/content/browser-syncui.js b/browser/base/content/browser-syncui.js index e122c5f05174e9e2063432c994069e87c877913e..1e42fb774885100901cee9411494a148ed426553 100644 --- a/browser/base/content/browser-syncui.js +++ b/browser/base/content/browser-syncui.js @@ -77,6 +77,10 @@ var gSyncUI = { Services.obs.addObserver(this, topic, true); }, this); + // initial label for the sync buttons. + let broadcaster = document.getElementById("sync-status"); + broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncnow.label")); + this.updateUI(); }, @@ -176,14 +180,10 @@ var gSyncUI = { this.log.debug("onActivityStart with numActive", this._numActiveSyncTasks); if (++this._numActiveSyncTasks == 1) { - let button = document.getElementById("sync-button"); - if (button) { - button.setAttribute("status", "active"); - } - let container = document.getElementById("PanelUI-footer-fxa"); - if (container) { - container.setAttribute("syncstatus", "active"); - } + let broadcaster = document.getElementById("sync-status"); + broadcaster.setAttribute("syncstatus", "active"); + broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncing.label")); + broadcaster.setAttribute("disabled", "true"); } this.updateUI(); }, @@ -203,14 +203,10 @@ var gSyncUI = { return; // active tasks are still ongoing... } - let syncButton = document.getElementById("sync-button"); - if (syncButton) { - syncButton.removeAttribute("status"); - } - let fxaContainer = document.getElementById("PanelUI-footer-fxa"); - if (fxaContainer) { - fxaContainer.removeAttribute("syncstatus"); - } + let broadcaster = document.getElementById("sync-status"); + broadcaster.removeAttribute("syncstatus"); + broadcaster.removeAttribute("disabled"); + broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncnow.label")); this.updateUI(); }, @@ -313,8 +309,25 @@ var gSyncUI = { gFxAccounts.openSignInAgainPage(entryPoint); }, - /* Update the tooltip for the Sync Toolbar button and the Sync spinner in the - FxA hamburger area. + openSyncedTabsPanel() { + let placement = CustomizableUI.getPlacementOfWidget("sync-button"); + let area = placement ? placement.area : CustomizableUI.AREA_NAVBAR; + let anchor = document.getElementById("sync-button") || + document.getElementById("PanelUI-menu-button"); + if (area == CustomizableUI.AREA_PANEL) { + // The button is in the panel, so we need to show the panel UI, then our + // subview. + PanelUI.show().then(() => { + PanelUI.showSubView("PanelUI-remotetabs", anchor, area); + }).catch(Cu.reportError); + } else { + // It is placed somewhere else - just try and show it. + PanelUI.showSubView("PanelUI-remotetabs", anchor, area); + } + }, + + /* Update the tooltip for the sync-status broadcaster (which will update the + Sync Toolbar button and the Sync spinner in the FxA hamburger area.) If Sync is configured, the tooltip is when the last sync occurred, otherwise the tooltip reflects the fact that Sync needs to be (re-)configured. @@ -364,16 +377,13 @@ var gSyncUI = { // sure it hasn't been torn down since we started. if (!gBrowser) return; - let syncButton = document.getElementById("sync-button"); - let statusButton = document.getElementById("PanelUI-fxa-icon"); - - for (let button of [syncButton, statusButton]) { - if (button) { - if (tooltiptext) { - button.setAttribute("tooltiptext", tooltiptext); - } else { - button.removeAttribute("tooltiptext"); - } + + let broadcaster = document.getElementById("sync-status"); + if (broadcaster) { + if (tooltiptext) { + broadcaster.setAttribute("tooltiptext", tooltiptext); + } else { + broadcaster.removeAttribute("tooltiptext"); } } }), diff --git a/browser/base/content/browser-trackingprotection.js b/browser/base/content/browser-trackingprotection.js index e45cbee75c1f632757d6adb2b1b09dc7c689d68f..2c13f088062335ab7648960c638d96a014bf68a7 100644 --- a/browser/base/content/browser-trackingprotection.js +++ b/browser/base/content/browser-trackingprotection.js @@ -218,7 +218,7 @@ var TrackingProtection = { ]; let panelTarget = yield UITour.getTarget(window, "trackingProtection"); - UITour.initForBrowser(gBrowser.selectedBrowser); + UITour.initForBrowser(gBrowser.selectedBrowser, window); UITour.showInfo(window, mm, panelTarget, gNavigatorBundle.getString("trackingProtection.intro.title"), gNavigatorBundle.getFormattedString("trackingProtection.intro.description", diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index 298bb48a9f7c1a33e3b8d174311393d0a1307658..c5dd9b4aff26bf5a7139a80f672d80e7ef88af70 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -1136,6 +1136,7 @@ chatbox:-moz-full-screen-ancestor > .chat-titlebar { /* Apply crisp rendering for favicons at exactly 2dppx resolution */ @media (resolution: 2dppx) { + #PanelUI-remotetabs-tabslist > toolbarbutton > .toolbarbutton-icon, #PanelUI-recentlyClosedWindows > toolbarbutton > .toolbarbutton-icon, #PanelUI-recentlyClosedTabs > toolbarbutton > .toolbarbutton-icon, #PanelUI-historyItems > toolbarbutton > .toolbarbutton-icon { diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index e99391bca5ceee310d78f9cc12979de21cdf3c01..018ba43694eadaa7b2d38665a68a6bff939cb1e0 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -4456,8 +4456,11 @@ var XULBrowserWindow = { try { gCrashReporter.annotateCrashReport("URL", uri.spec); - } catch (ex if ex.result == Components.results.NS_ERROR_NOT_INITIALIZED) { + } catch (ex) { // Don't make noise when the crash reporter is built but not enabled. + if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED) { + throw ex; + } } } }, @@ -6471,7 +6474,7 @@ function isTabEmpty(aTab) { } function BrowserOpenSyncTabs() { - switchToTabHavingURI("about:sync-tabs", true); + gSyncUI.openSyncedTabsPanel(); } /** diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index 28b8b6b8bb999389759939684979ee4691c49bcb..0325faf2e7c8dcb9226071013f2c3eda58a65ce5 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -1038,11 +1038,6 @@ type="checkbox" label="&fullScreenCmd.label;" tooltip="dynamic-shortcut-tooltip"/> - - <toolbarbutton id="sync-button" - class="toolbarbutton-1 chromeclass-toolbar-additional" - label="&syncToolbarButton.label;" - oncommand="gSyncUI.handleToolbarButton()"/> </toolbarpalette> </toolbox> diff --git a/browser/base/content/global-scripts.inc b/browser/base/content/global-scripts.inc index 96422b50aa88561bc4fd8dfecb690c73d76ce283..db78dbc6780039e5ee616d54048302daa20874d8 100755 --- a/browser/base/content/global-scripts.inc +++ b/browser/base/content/global-scripts.inc @@ -31,7 +31,6 @@ <script type="application/javascript" src="chrome://browser/content/browser-social.js"/> <script type="application/javascript" src="chrome://browser/content/browser-syncui.js"/> <script type="application/javascript" src="chrome://browser/content/browser-tabsintitlebar.js"/> -<script type="application/javascript" src="chrome://browser/content/browser-tabview.js"/> <script type="application/javascript" src="chrome://browser/content/browser-thumbnails.js"/> <script type="application/javascript" src="chrome://browser/content/browser-trackingprotection.js"/> diff --git a/browser/base/content/pageinfo/permissions.js b/browser/base/content/pageinfo/permissions.js index 70e95348411297cd95374f6b71e9b4598a18d09f..f1dce3d54c9fb905d978b772b8f3a07439ba82d6 100644 --- a/browser/base/content/pageinfo/permissions.js +++ b/browser/base/content/pageinfo/permissions.js @@ -297,14 +297,13 @@ function initPluginsRow() { } } - let entries = [{name: item[1], permission: item[0]} for (item of permissionMap)]; + let entries = Array.from(permissionMap, item => ({ name: item[1], permission: item[0] })); + entries.sort(function(a, b) { return a.name < b.name ? -1 : (a.name == b.name ? 0 : 1); }); - let permissionEntries = [ - fillInPluginPermissionTemplate(p.name, p.permission) for (p of entries) - ]; + let permissionEntries = entries.map(p => fillInPluginPermissionTemplate(p.name, p.permission)); let permPluginsRow = document.getElementById("perm-plugins-row"); clearPluginPermissionTemplate(); diff --git a/browser/base/content/sanitize.js b/browser/base/content/sanitize.js index f97ab580f73b15db37c9aac67f542508d3c4cf0e..763ef9d29f293f7d3d3db9bcf10ca577bb96083a 100644 --- a/browser/base/content/sanitize.js +++ b/browser/base/content/sanitize.js @@ -450,7 +450,7 @@ Sanitizer.prototype = { { let refObj = {}; TelemetryStopwatch.start("FX_SANITIZE_DOWNLOADS", refObj); - Task.spawn(function () { + Task.spawn(function*() { let filterByTime = null; if (this.range) { // Convert microseconds back to milliseconds for date comparisons. diff --git a/browser/base/content/test/general/browser.ini b/browser/base/content/test/general/browser.ini index ab9f47f4d1c6b2f827f2a322b7d3cee6b4d908c1..d18049f1772d6214c4a4cfe8c06f229eb6d98431 100644 --- a/browser/base/content/test/general/browser.ini +++ b/browser/base/content/test/general/browser.ini @@ -472,6 +472,7 @@ skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test un [browser_urlbarSearchSingleWordNotification.js] [browser_urlbarSearchSuggestions.js] [browser_urlbarSearchSuggestionsNotification.js] +[browser_urlbarSearchTelemetry.js] [browser_urlbarStop.js] [browser_urlbarTrimURLs.js] [browser_urlbar_autoFill_backspaced.js] diff --git a/browser/base/content/test/general/browser_aboutHome.js b/browser/base/content/test/general/browser_aboutHome.js index 441c3f5a9a2fbb6bfa2915f89748be65d0c60e83..c32eb95ebd7242503ae5387ddbf971f4d21c812b 100644 --- a/browser/base/content/test/general/browser_aboutHome.js +++ b/browser/base/content/test/general/browser_aboutHome.js @@ -114,7 +114,7 @@ var gTests = [ // Get the current number of recorded searches. let searchStr = "a search"; - getNumberOfSearches(engineName).then(num => { + getNumberOfSearchesInFHR(engineName, "abouthome").then(num => { numSearchesBefore = num; info("Perform a search."); @@ -126,7 +126,7 @@ var gTests = [ getSubmission(searchStr, null, "homepage"). uri.spec; let loadPromise = waitForDocLoadAndStopIt(expectedURL).then(() => { - getNumberOfSearches(engineName).then(num => { + getNumberOfSearchesInFHR(engineName, "abouthome").then(num => { is(num, numSearchesBefore + 1, "One more search recorded."); searchEventDeferred.resolve(); }); @@ -601,58 +601,6 @@ function promiseSetupSnippetsMap(aTab, aSetupFn) return deferred.promise; } -/** - * Retrieves the number of about:home searches recorded for the current day. - * - * @param aEngineName - * name of the setup search engine. - * - * @return {Promise} Returns a promise resolving to the number of searches. - */ -function getNumberOfSearches(aEngineName) { - let reporter = Components.classes["@mozilla.org/datareporting/service;1"] - .getService() - .wrappedJSObject - .healthReporter; - ok(reporter, "Health Reporter instance available."); - - return reporter.onInit().then(function onInit() { - let provider = reporter.getProvider("org.mozilla.searches"); - ok(provider, "Searches provider is available."); - - let m = provider.getMeasurement("counts", 3); - return m.getValues().then(data => { - let now = new Date(); - let yday = new Date(now); - yday.setDate(yday.getDate() - 1); - - // Add the number of searches recorded yesterday to the number of searches - // recorded today. This makes the test not fail intermittently when it is - // run at midnight and we accidentally compare the number of searches from - // different days. Tests are always run with an empty profile so there - // are no searches from yesterday, normally. Should the test happen to run - // past midnight we make sure to count them in as well. - return getNumberOfSearchesByDate(aEngineName, data, now) + - getNumberOfSearchesByDate(aEngineName, data, yday); - }); - }); -} - -function getNumberOfSearchesByDate(aEngineName, aData, aDate) { - if (aData.days.hasDay(aDate)) { - let id = Services.search.getEngineByName(aEngineName).identifier; - - let day = aData.days.getDay(aDate); - let field = id + ".abouthome"; - - if (day.has(field)) { - return day.get(field) || 0; - } - } - - return 0; // No records found. -} - function waitForLoad(cb) { let browser = gBrowser.selectedBrowser; browser.addEventListener("load", function listener() { diff --git a/browser/base/content/test/general/browser_syncui.js b/browser/base/content/test/general/browser_syncui.js index 48b9b419a5bc71bca297d9b1f2e84db1a54393e1..78ce8baa6facf17444483260a5a907dd939d9c52 100644 --- a/browser/base/content/test/general/browser_syncui.js +++ b/browser/base/content/test/general/browser_syncui.js @@ -53,7 +53,7 @@ function promiseObserver(topic) { } function checkButtonTooltips(stringPrefix) { - for (let butId of ["sync-button", "PanelUI-fxa-icon"]) { + for (let butId of ["PanelUI-remotetabs-syncnow", "PanelUI-fxa-icon"]) { let text = document.getElementById(butId).getAttribute("tooltiptext"); let desc = `Text is "${text}", expecting it to start with "${stringPrefix}"` Assert.ok(text.startsWith(stringPrefix), desc); @@ -89,6 +89,8 @@ add_task(function* prepare() { // and a notification to have the state change away from "needs setup" yield notifyAndPromiseUIUpdated("weave:service:login:finish"); checkBroadcasterVisible("sync-syncnow-state"); + // open the sync-button panel so we can check elements in that. + document.getElementById("sync-button").click(); }); add_task(function* testSyncNeedsVerification() { @@ -128,14 +130,17 @@ add_task(function* testSyncLoginError() { }); function checkButtonsStatus(shouldBeActive) { - let button = document.getElementById("sync-button"); - let fxaContainer = document.getElementById("PanelUI-footer-fxa"); - if (shouldBeActive) { - Assert.equal(button.getAttribute("status"), "active"); - Assert.equal(fxaContainer.getAttribute("syncstatus"), "active"); - } else { - Assert.ok(!button.hasAttribute("status")); - Assert.ok(!fxaContainer.hasAttribute("syncstatus")); + for (let eid of [ + "sync-status", // the broadcaster itself. + "sync-button", // the main sync button which observes the broadcaster + "PanelUI-fxa-icon", // the sync icon in the fxa footer that observes it. + ]) { + let elt = document.getElementById(eid); + if (shouldBeActive) { + Assert.equal(elt.getAttribute("syncstatus"), "active", `${eid} should be active`);; + } else { + Assert.ok(!elt.hasAttribute("syncstatus"), `${eid} should have no status attr`); + } } } diff --git a/browser/base/content/test/general/browser_urlbarSearchTelemetry.js b/browser/base/content/test/general/browser_urlbarSearchTelemetry.js new file mode 100644 index 0000000000000000000000000000000000000000..73adec47f7c218e75efa8706c0a27267f10d29be --- /dev/null +++ b/browser/base/content/test/general/browser_urlbarSearchTelemetry.js @@ -0,0 +1,177 @@ +"use strict"; + +Cu.import("resource:///modules/BrowserUITelemetry.jsm"); + +const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches"; +const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml"; + +// Must run first. +add_task(function* prepare() { + Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true); + let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME); + let oldCurrentEngine = Services.search.currentEngine; + Services.search.currentEngine = engine; + registerCleanupFunction(function* () { + Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF); + Services.search.currentEngine = oldCurrentEngine; + + // Clicking urlbar results causes visits to their associated pages, so clear + // that history now. + yield PlacesTestUtils.clearHistory(); + + // Make sure the popup is closed for the next test. + gURLBar.blur(); + Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed"); + }); +}); + +add_task(function* heuristicResult() { + yield compareCounts(function* () { + yield promiseAutocompleteResultPopup("heuristicResult"); + let action = getActionAtIndex(0); + Assert.ok(!!action, "there should be an action at index 0"); + Assert.equal(action.type, "searchengine", "type should be searchengine"); + let item = gURLBar.popup.richlistbox.getItemAtIndex(0); + let loadPromise = promiseTabLoaded(gBrowser.selectedTab); + item.click(); + yield loadPromise; + }); +}); + +add_task(function* searchSuggestion() { + yield compareCounts(function* () { + yield promiseAutocompleteResultPopup("searchSuggestion"); + let idx = getFirstSuggestionIndex(); + Assert.ok(idx >= 0, "there should be a first suggestion"); + let item = gURLBar.popup.richlistbox.getItemAtIndex(idx); + let loadPromise = promiseTabLoaded(gBrowser.selectedTab); + item.click(); + yield loadPromise; + }); +}); + +/** + * This does three things: gets current telemetry/FHR counts, calls + * clickCallback, gets telemetry/FHR counts again to compare them to the old + * counts. + * + * @param clickCallback Use this to open the urlbar popup and choose and click a + * result. + */ +function* compareCounts(clickCallback) { + // Search events triggered by clicks (not the Return key in the urlbar) are + // recorded in three places: + // * BrowserUITelemetry + // * Telemetry histogram named "SEARCH_COUNTS" + // * FHR + + let engine = Services.search.currentEngine; + let engineID = "org.mozilla.testsearchsuggestions"; + + // First, get the current counts. + + // BrowserUITelemetry + let uiTelemCount = 0; + let bucket = BrowserUITelemetry.currentBucket; + let events = BrowserUITelemetry.getToolbarMeasures().countableEvents; + if (events[bucket] && + events[bucket].search && + events[bucket].search.urlbar) { + uiTelemCount = events[bucket].search.urlbar; + } + + // telemetry histogram SEARCH_COUNTS + let histogramCount = 0; + let histogramKey = engineID + ".urlbar"; + let histogram; + try { + histogram = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS"); + } catch (ex) { + // No searches performed yet, not a problem. + } + if (histogram) { + let snapshot = histogram.snapshot(); + if (histogramKey in snapshot) { + histogramCount = snapshot[histogramKey].sum; + } + } + + // FHR -- first make sure the engine has an identifier so that FHR is happy. + Object.defineProperty(engine.wrappedJSObject, "identifier", + { value: engineID }); + let fhrCount = yield getNumberOfSearchesInFHR(engine.name, "urlbar"); + + gURLBar.focus(); + yield clickCallback(); + + // Now get the new counts and compare them to the old. + + // BrowserUITelemetry + events = BrowserUITelemetry.getToolbarMeasures().countableEvents; + Assert.ok(bucket in events, "bucket should be recorded"); + events = events[bucket]; + Assert.ok("search" in events, "search should be recorded"); + events = events.search; + Assert.ok("urlbar" in events, "urlbar should be recorded"); + Assert.equal(events.urlbar, uiTelemCount + 1, + "clicked suggestion should be recorded"); + + // telemetry histogram SEARCH_COUNTS + histogram = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS"); + let snapshot = histogram.snapshot(); + Assert.ok(histogramKey in snapshot, "histogram with key should be recorded"); + Assert.equal(snapshot[histogramKey].sum, histogramCount + 1, + "histogram sum should be incremented"); + + // FHR + let newFHRCount = yield getNumberOfSearchesInFHR(engine.name, "urlbar"); + Assert.equal(newFHRCount, fhrCount + 1, "should be recorded in FHR"); +} + +/** + * Returns the "action" object at the given index in the urlbar results: + * { type, params: {}} + * + * @param index The index in the urlbar results. + * @return An action object, or null if index >= number of results. + */ +function getActionAtIndex(index) { + let controller = gURLBar.popup.input.controller; + if (controller.matchCount <= index) { + return null; + } + let url = controller.getValueAt(index); + let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/); + if (!mozActionMatch) { + let msg = "result at index " + index + " is not a moz-action: " + url; + Assert.ok(false, msg); + throw new Error(msg); + } + let [, type, paramStr] = mozActionMatch; + return { + type: type, + params: JSON.parse(paramStr), + }; +} + +/** + * Returns the index of the first search suggestion in the urlbar results. + * + * @return An index, or -1 if there are no search suggestions. + */ +function getFirstSuggestionIndex() { + let controller = gURLBar.popup.input.controller; + let matchCount = controller.matchCount; + for (let i = 0; i < matchCount; i++) { + let url = controller.getValueAt(i); + let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/); + if (mozActionMatch) { + let [, type, paramStr] = mozActionMatch; + let params = JSON.parse(paramStr); + if (type == "searchengine" && "searchSuggestion" in params) { + return i; + } + } + } + return -1; +} diff --git a/browser/base/content/test/general/head.js b/browser/base/content/test/general/head.js index 2f20399e19a1591fe471f9af0084c5b5aa43cef2..4e5f98f5a0741dc450e9e7a92e296c826dd641cd 100644 --- a/browser/base/content/test/general/head.js +++ b/browser/base/content/test/general/head.js @@ -1202,3 +1202,61 @@ function promiseCrashReport(expectedExtra) { } }); } + +/** + * Retrieves the number of searches recorded in FHR for the current day. + * + * @param aEngineName + * name of the setup search engine. + * @param aSource + * The FHR "source" name for the search, like "abouthome" or "urlbar". + * + * @return {Promise} Returns a promise resolving to the number of searches. + */ +function getNumberOfSearchesInFHR(aEngineName, aSource) { + let reporter = Components.classes["@mozilla.org/datareporting/service;1"] + .getService() + .wrappedJSObject + .healthReporter; + ok(reporter, "Health Reporter instance available."); + + return reporter.onInit().then(function onInit() { + let provider = reporter.getProvider("org.mozilla.searches"); + ok(provider, "Searches provider is available."); + + let m = provider.getMeasurement("counts", 3); + return m.getValues().then(data => { + let now = new Date(); + let yday = new Date(now); + yday.setDate(yday.getDate() - 1); + + // Add the number of searches recorded yesterday to the number of searches + // recorded today. This makes the test not fail intermittently when it is + // run at midnight and we accidentally compare the number of searches from + // different days. Tests are always run with an empty profile so there + // are no searches from yesterday, normally. Should the test happen to run + // past midnight we make sure to count them in as well. + return getNumberOfSearchesInFHRByDate(aEngineName, aSource, data, now) + + getNumberOfSearchesInFHRByDate(aEngineName, aSource, data, yday); + }); + }); +} + +/** + * Helper for getNumberOfSearchesInFHR. You probably don't want to call this + * directly. + */ +function getNumberOfSearchesInFHRByDate(aEngineName, aSource, aData, aDate) { + if (aData.days.hasDay(aDate)) { + let id = Services.search.getEngineByName(aEngineName).identifier; + + let day = aData.days.getDay(aDate); + let field = id + "." + aSource; + + if (day.has(field)) { + return day.get(field) || 0; + } + } + + return 0; // No records found. +} diff --git a/browser/base/content/urlbarBindings.xml b/browser/base/content/urlbarBindings.xml index e34c83ea13bddbb4b2e1ef6b20138429f96416bb..37eeaab95e92c41ef47e0e7e3d9ba2633386c6f4 100644 --- a/browser/base/content/urlbarBindings.xml +++ b/browser/base/content/urlbarBindings.xml @@ -361,13 +361,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. } else if (action.type == "keyword") { url = action.params.url; } else if (action.type == "searchengine") { - let engine = Services.search.getEngineByName(action.params.engineName); - let query = action.params.searchSuggestion || - action.params.searchQuery; - let submission = engine.getSubmission(query, null, "keyword"); - - url = submission.uri.spec; - postData = submission.postData; + [url, postData] = this._parseAndRecordSearchEngineAction(action); } else if (action.type == "visiturl") { url = action.params.url; } @@ -455,6 +449,19 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. ]]></body> </method> + <method name="_parseAndRecordSearchEngineAction"> + <parameter name="action"/> + <body><![CDATA[ + let engine = + Services.search.getEngineByName(action.params.engineName); + BrowserSearch.recordSearchInHealthReport(engine, "urlbar"); + let query = action.params.searchSuggestion || + action.params.searchQuery; + let submission = engine.getSubmission(query, null, "keyword"); + return [submission.uri.spec, submission.postData]; + ]]></body> + </method> + <method name="_canonizeURL"> <parameter name="aTriggeringEvent"/> <parameter name="aCallback"/> @@ -1455,12 +1462,8 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. break; } case "searchengine": { - let engine = Services.search.getEngineByName(action.params.engineName); - let query = action.params.searchSuggestion || - action.params.searchQuery; - let submission = engine.getSubmission(query, null, "keyword"); - url = submission.uri.spec; - options.postData = submission.postData; + [url, options.postData] = + this._parseAndRecordSearchEngineAction(action); break; } default: { diff --git a/browser/components/customizableui/CustomizableUI.jsm b/browser/components/customizableui/CustomizableUI.jsm index 205395a3a0a978fe466d7d9a832f9f4dd81615fd..3b8111a9acc3f4217ff476c55562fab23f49cab7 100644 --- a/browser/components/customizableui/CustomizableUI.jsm +++ b/browser/components/customizableui/CustomizableUI.jsm @@ -53,14 +53,16 @@ const kSubviewEvents = [ * The current version. We can use this to auto-add new default widgets as necessary. * (would be const but isn't because of testing purposes) */ -var kVersion = 4; +var kVersion = 5; /** * Buttons removed from built-ins by version they were removed. kVersion must be * bumped any time a new id is added to this. Use the button id as key, and * version the button is removed in as the value. e.g. "pocket-button": 5 */ -var ObsoleteBuiltinButtons = {}; +var ObsoleteBuiltinButtons = { + "loop-button": 5 +}; /** * gPalette is a map of every widget that CustomizableUI.jsm knows about, keyed @@ -178,6 +180,7 @@ var CustomizableUIInternal = { #ifndef MOZ_DEV_EDITION "developer-button", #endif + "sync-button", ]; #ifdef E10S_TESTING_ONLY diff --git a/browser/components/customizableui/CustomizableWidgets.jsm b/browser/components/customizableui/CustomizableWidgets.jsm index b7a1fc9d0aab5427f013a2624e553fef3385d3c2..0c825907bce2696d47d881cec19931cfcd220bf2 100644 --- a/browser/components/customizableui/CustomizableWidgets.jsm +++ b/browser/components/customizableui/CustomizableWidgets.jsm @@ -30,6 +30,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "SocialService", "resource://gre/modules/SocialService.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "SyncedTabs", + "resource://services-sync/SyncedTabs.jsm"); XPCOMUtils.defineLazyGetter(this, "CharsetBundle", function() { const kCharsetBundle = "chrome://global/locale/charsetMenu.properties"; @@ -249,12 +251,6 @@ const CustomizableWidgets = [ tabsFromOtherComputers.setAttribute("hidden", true); } - if (PlacesUIUtils.shouldEnableTabsFromOtherComputersMenuitem()) { - tabsFromOtherComputers.removeAttribute("disabled"); - } else { - tabsFromOtherComputers.setAttribute("disabled", true); - } - let utils = RecentlyClosedTabsAndWindowsMenuUtils; let tabsFragment = utils.getTabsFragment(doc.defaultView, "toolbarbutton", true, "menuRestoreAllTabsSubview.label"); @@ -292,6 +288,207 @@ const CustomizableWidgets = [ onViewHiding: function(aEvent) { LOG("History view is being hidden!"); } + }, { + id: "sync-button", + label: "remotetabs-panelmenu.label", + tooltiptext: "remotetabs-panelmenu.tooltiptext", + type: "view", + viewId: "PanelUI-remotetabs", + defaultArea: CustomizableUI.AREA_PANEL, + deckIndices: { + DECKINDEX_TABS: 0, + DECKINDEX_TABSDISABLED: 1, + DECKINDEX_FETCHING: 2, + DECKINDEX_NOCLIENTS: 3, + }, + onCreated(aNode) { + // Add an observer to the button so we get the animation during sync. + // (Note the observer sets many attributes, including label and + // tooltiptext, but we only want the 'syncstatus' attribute for the + // animation) + let doc = aNode.ownerDocument; + let obnode = doc.createElementNS(kNSXUL, "observes"); + obnode.setAttribute("element", "sync-status"); + obnode.setAttribute("attribute", "syncstatus"); + aNode.appendChild(obnode); + + // A somewhat complicated dance to format the mobilepromo label. + let bundle = doc.getElementById("bundle_browser"); + let formatArgs = ["android", "ios"].map(os => { + let link = doc.createElement("label"); + link.textContent = bundle.getString(`appMenuRemoteTabs.mobilePromo.${os}`) + link.href = Services.prefs.getCharPref(`identity.mobilepromo.${os}`) + "synced-tabs"; + link.className = "text-link remotetabs-promo-link"; + return link.outerHTML; + }); + // Put it all together... + let contents = bundle.getFormattedString("appMenuRemoteTabs.mobilePromo", formatArgs); + doc.getElementById("PanelUI-remotetabs-mobile-promo").innerHTML = contents; + }, + onViewShowing(aEvent) { + let doc = aEvent.target.ownerDocument; + this._tabsList = doc.getElementById("PanelUI-remotetabs-tabslist"); + Services.obs.addObserver(this, SyncedTabs.TOPIC_TABS_CHANGED, false); + + let deck = doc.getElementById("PanelUI-remotetabs-deck"); + if (SyncedTabs.isConfiguredToSyncTabs) { + if (SyncedTabs.hasSyncedThisSession) { + deck.selectedIndex = this.deckIndices.DECKINDEX_TABS; + } else { + // Sync hasn't synced tabs yet, so show the "fetching" panel. + deck.selectedIndex = this.deckIndices.DECKINDEX_FETCHING; + } + // force a background sync. + SyncedTabs.syncTabs().catch(ex => { + Cu.reportError(ex); + }); + // show the current list - it will be updated by our observer. + this._showTabs(); + } else { + // not configured to sync tabs, so no point updating the list. + deck.selectedIndex = this.deckIndices.DECKINDEX_TABSDISABLED; + } + }, + onViewHiding() { + Services.obs.removeObserver(this, SyncedTabs.TOPIC_TABS_CHANGED); + this._tabsList = null; + }, + _tabsList: null, + observe(subject, topic, data) { + switch (topic) { + case SyncedTabs.TOPIC_TABS_CHANGED: + this._showTabs(); + break; + default: + break; + } + }, + _showTabsPromise: Promise.resolve(), + // Update the tab list after any existing in-flight updates are complete. + _showTabs() { + this._showTabsPromise = this._showTabsPromise.then(() => { + return this.__showTabs(); + }); + }, + // Return a new promise to update the tab list. + __showTabs() { + let doc = this._tabsList.ownerDocument; + let deck = doc.getElementById("PanelUI-remotetabs-deck"); + return SyncedTabs.getTabClients().then(clients => { + // The view may have been hidden while the promise was resolving. + if (!this._tabsList) { + return; + } + if (clients.length === 0 && !SyncedTabs.hasSyncedThisSession) { + // the "fetching tabs" deck is being shown - let's leave it there. + // When that first sync completes we'll be notified and update. + return; + } + + if (clients.length === 0) { + deck.selectedIndex = this.deckIndices.DECKINDEX_NOCLIENTS; + return; + } + + deck.selectedIndex = this.deckIndices.DECKINDEX_TABS; + this._clearTabList(); + this._sortFilterClientsAndTabs(clients); + let fragment = doc.createDocumentFragment(); + + for (let client of clients) { + // add a menu separator for all clients other than the first. + if (fragment.lastChild) { + let separator = doc.createElementNS(kNSXUL, "menuseparator"); + fragment.appendChild(separator); + } + this._appendClient(client, fragment); + } + this._tabsList.appendChild(fragment); + }).catch(err => { + Cu.reportError(err); + }).then(() => { + // an observer for tests. + Services.obs.notifyObservers(null, "synced-tabs-menu:test:tabs-updated", null); + }); + }, + _clearTabList () { + let list = this._tabsList; + while (list.lastChild) { + list.lastChild.remove(); + } + }, + _showNoClientMessage() { + this._appendMessageLabel("notabslabel"); + }, + _appendMessageLabel(messageAttr, appendTo = null) { + if (!appendTo) { + appendTo = this._tabsList; + } + let message = this._tabsList.getAttribute(messageAttr); + let doc = this._tabsList.ownerDocument; + let messageLabel = doc.createElementNS(kNSXUL, "label"); + messageLabel.textContent = message; + appendTo.appendChild(messageLabel); + return messageLabel; + }, + _appendClient: function (client, attachFragment) { + let doc = attachFragment.ownerDocument; + // Create the element for the remote client. + let clientItem = doc.createElementNS(kNSXUL, "label"); + clientItem.setAttribute("itemtype", "client"); + clientItem.textContent = client.name; + + attachFragment.appendChild(clientItem); + + if (client.tabs.length == 0) { + let label = this._appendMessageLabel("notabsforclientlabel", attachFragment); + label.setAttribute("class", "PanelUI-remotetabs-notabsforclient-label"); + } else { + for (let tab of client.tabs) { + let tabEnt = this._createTabElement(doc, tab); + attachFragment.appendChild(tabEnt); + } + } + }, + _createTabElement(doc, tabInfo) { + let win = doc.defaultView; + let item = doc.createElementNS(kNSXUL, "toolbarbutton"); + item.setAttribute("itemtype", "tab"); + item.setAttribute("class", "subviewbutton"); + item.setAttribute("targetURI", tabInfo.url); + item.setAttribute("label", tabInfo.title != "" ? tabInfo.title : tabInfo.url); + item.setAttribute("image", tabInfo.icon); + // We need to use "click" instead of "command" here so openUILink + // respects different buttons (eg, to open in a new tab). + item.addEventListener("click", e => { + doc.defaultView.openUILink(tabInfo.url, e); + CustomizableUI.hidePanelForNode(item); + }); + return item; + }, + _sortFilterClientsAndTabs(clients) { + // First sort and filter the list of tabs for each client. Note that the + // SyncedTabs module promises that the objects it returns are never + // shared, so we are free to mutate those objects directly. + const maxTabs = 15; + for (let client of clients) { + let tabs = client.tabs; + tabs.sort((a, b) => b.lastUsed - a.lastUsed); + client.tabs = tabs.slice(0, maxTabs); + } + // Now sort the clients - the clients are sorted in the order of the + // most recent tab for that client (ie, it is important the tabs for + // each client are already sorted.) + clients.sort((a, b) => { + if (a.tabs.length == 0) { + return 1; // b comes first. + } + if (b.tabs.length == 0) { + return -1; // a comes first. + } + return b.tabs[0].lastUsed - a.tabs[0].lastUsed; + }); + }, }, { id: "privatebrowsing-button", shortcutId: "key_privatebrowsing", diff --git a/browser/components/customizableui/content/panelUI.inc.xul b/browser/components/customizableui/content/panelUI.inc.xul index b8aaf5f7f30434812dc05239765d9678fbc621f6..1bfe42046baecacd1120fca1def7b77c84470083 100644 --- a/browser/components/customizableui/content/panelUI.inc.xul +++ b/browser/components/customizableui/content/panelUI.inc.xul @@ -34,7 +34,10 @@ <toolbarseparator/> <toolbarbutton id="PanelUI-fxa-icon" oncommand="gSyncUI.doSync();" - closemenu="none"/> + closemenu="none"> + <observes element="sync-status" attribute="syncstatus"/> + <observes element="sync-status" attribute="tooltiptext"/> + </toolbarbutton> </hbox> <hbox id="PanelUI-footer-inner"> @@ -83,7 +86,7 @@ command="Tools:Sanitize"/> <toolbarbutton id="sync-tabs-menuitem2" class="syncTabsMenuItem subviewbutton" - label="&syncTabsMenu2.label;" + label="&syncTabsMenu3.label;" oncommand="BrowserOpenSyncTabs();" disabled="true"/> <toolbarbutton id="appMenuRestoreLastSession" @@ -103,6 +106,83 @@ oncommand="PlacesCommandHook.showPlacesOrganizer('History'); CustomizableUI.hidePanelForNode(this);"/> </panelview> + <panelview id="PanelUI-remotetabs" flex="1" class="PanelUI-subView"> + <label value="&appMenuRemoteTabs.label;" class="panel-subview-header"/> + <vbox class="panel-subview-body"> + <!-- this widget has 3 boxes in the body, but only 1 is ever visible --> + <!-- When Sync is ready to sync --> + <vbox id="PanelUI-remotetabs-main" observes="sync-syncnow-state"> + <toolbarbutton id="PanelUI-remotetabs-syncnow" + observes="sync-status" + class="subviewbutton" + oncommand="gSyncUI.doSync();" + closemenu="none"/> + <menuseparator id="PanelUI-remotetabs-separator"/> + <deck id="PanelUI-remotetabs-deck"> + <!-- Sync is ready to Sync and the "tabs" engine is enabled --> + <vbox id="PanelUI-remotetabs-tabspane"> + <vbox id="PanelUI-remotetabs-tabslist" + notabsforclientlabel="&appMenuRemoteTabs.notabs.label;" + /> + </vbox> + <!-- Sync is ready to Sync but the "tabs" engine isn't enabled--> + <vbox id="PanelUI-remotetabs-tabsdisabledpane" + class="PanelUI-remotetabs-instruction-box"> + <hbox pack="center"> + <image class="fxaSyncIllustration" alt=""/> + </hbox> + <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.tabsnotsyncing.label;</label> + <hbox pack="center"> + <toolbarbutton class="PanelUI-remotetabs-prefs-button" + label="&appMenuRemoteTabs.openprefs.label;" + oncommand="gSyncUI.openSetup();"/> + </hbox> + </vbox> + <!-- Sync is ready to Sync but we are still fetching the tabs to show --> + <vbox id="PanelUI-remotetabs-fetching"> + <label>&appMenuRemoteTabs.fetching.label;</label> + </vbox> + <!-- Sync has only 1 (ie, this) device connected --> + <vbox id="PanelUI-remotetabs-nodevicespane" + class="PanelUI-remotetabs-instruction-box"> + <hbox pack="center"> + <image class="fxaSyncIllustration" alt=""/> + </hbox> + <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.noclients.label;</label> + <!-- The inner HTML for PanelUI-remotetabs-mobile-promo is built at runtime --> + <label id="PanelUI-remotetabs-mobile-promo"/> + </vbox> + </deck> + </vbox> + <!-- When Sync is not configured --> + <vbox id="PanelUI-remotetabs-setupsync" + flex="1" + align="center" + class="PanelUI-remotetabs-instruction-box" + observes="sync-setup-state"> + <image class="fxaSyncIllustration" alt=""/> + <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label> + <toolbarbutton class="PanelUI-remotetabs-prefs-button" + label="&appMenuRemoteTabs.signin.label;" + oncommand="gSyncUI.openSetup();"/> + </vbox> + <!-- When Sync needs re-authentication. This uses the exact same messaging + as "Sync is not configured" but remains a separate box so we get + the goodness of observing broadcasters to manage the hidden states --> + <vbox id="PanelUI-remotetabs-reauthsync" + flex="1" + align="center" + class="PanelUI-remotetabs-instruction-box" + observes="sync-reauth-state"> + <image class="fxaSyncIllustration" alt=""/> + <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label> + <toolbarbutton class="PanelUI-remotetabs-prefs-button" + label="&appMenuRemoteTabs.signin.label;" + oncommand="gSyncUI.openSetup();"/> + </vbox> + </vbox> + </panelview> + <panelview id="PanelUI-bookmarks" flex="1" class="PanelUI-subView"> <label value="&bookmarksMenu.label;" class="panel-subview-header"/> <vbox class="panel-subview-body"> diff --git a/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js b/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js index 73ed39f2da61c92a763b92d9b73a07f8a949808d..5f1c8215bad07bdac70e5ccb079297a862a8fe1a 100644 --- a/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js +++ b/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js @@ -4,7 +4,7 @@ "use strict"; -const kXULWidgetId = "sync-button"; +const kXULWidgetId = "a-test-button"; // we'll create a button with this ID. const kAPIWidgetId = "feed-button"; const kPanel = CustomizableUI.AREA_PANEL; const kToolbar = CustomizableUI.AREA_NAVBAR; @@ -141,6 +141,16 @@ function checkPalette(id, method) { checkWrapper(id); } +// This test needs a XUL button that's in the palette by default. No such +// button currently exists, so we create a simple one. +function createXULButtonForWindow(win) { + createDummyXULButton(kXULWidgetId, "test-button", win); +} + +function removeXULButtonForWindow(win) { + win.gNavToolbox.palette.querySelector(`#${kXULWidgetId}`).remove(); +} + var otherWin; // Moving widgets in two windows, one with customize mode and one without, should work. @@ -148,6 +158,9 @@ add_task(function MoveWidgetsInTwoWindows() { yield startCustomizing(); otherWin = yield openAndLoadWindow(null, true); yield otherWin.PanelUI.ensureReady(); + // Create the XUL button to use in the test in both windows. + createXULButtonForWindow(window); + createXULButtonForWindow(otherWin); ok(CustomizableUI.inDefaultState, "Should start in default state"); for (let widgetId of [kXULWidgetId, kAPIWidgetId]) { @@ -164,6 +177,7 @@ add_task(function MoveWidgetsInTwoWindows() { yield promiseWindowClosed(otherWin); otherWin = null; yield endCustomizing(); + removeXULButtonForWindow(window); }); add_task(function asyncCleanup() { diff --git a/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js b/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js index b9203af5d6636869b66db50c37850ae352b300d0..9f56a2676b942bd9498b03a5cc272dadadf6f0cc 100644 --- a/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js +++ b/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js @@ -27,7 +27,10 @@ add_task(function testWrapUnwrap() { // Creating and destroying a widget should correctly deal with panel placeholders add_task(function testPanelPlaceholders() { let panel = document.getElementById(CustomizableUI.AREA_PANEL); - let expectedPlaceholders = 2 + (isInDevEdition() ? 1 : 0); + // The value of expectedPlaceholders depends on the default palette layout. + // Bug 1229236 is for these tests to be smarter so the test doesn't need to + // change when the default placements change. + let expectedPlaceholders = 1 + (isInDevEdition() ? 1 : 0); is(panel.querySelectorAll(".panel-customization-placeholder").length, expectedPlaceholders, "The number of placeholders should be correct."); CustomizableUI.createWidget({id: kTestWidget2, label: 'Pretty label', tooltiptext: 'Pretty tooltip', defaultArea: CustomizableUI.AREA_PANEL}); let elem = document.getElementById(kTestWidget2); @@ -36,7 +39,7 @@ add_task(function testPanelPlaceholders() { ok(wrapper, "There should be a wrapper"); is(wrapper.firstChild.id, kTestWidget2, "Wrapper should have test widget"); is(wrapper.parentNode, panel, "Wrapper should be in panel"); - expectedPlaceholders = 1 + (isInDevEdition() ? 1 : 0); + expectedPlaceholders = isInDevEdition() ? 1 : 3; is(panel.querySelectorAll(".panel-customization-placeholder").length, expectedPlaceholders, "The number of placeholders should be correct."); CustomizableUI.destroyWidget(kTestWidget2); wrapper = document.getElementById("wrapper-" + kTestWidget2); diff --git a/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js b/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js index f5c95e9f57653016728e4b8ea108e627d4186b1c..7847b999f1d1e515f79df60867849d377b2857b4 100644 --- a/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js +++ b/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js @@ -22,7 +22,9 @@ add_task(function() { "find-button", "preferences-button", "add-ons-button", - "developer-button"]; + "developer-button", + "sync-button", + ]; removeDeveloperButtonIfDevEdition(placementsAfterMove); simulateItemDrag(zoomControls, printButton); assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); @@ -48,7 +50,9 @@ add_task(function() { "find-button", "preferences-button", "add-ons-button", - "developer-button"]; + "developer-button", + "sync-button", + ]; removeDeveloperButtonIfDevEdition(placementsAfterMove); simulateItemDrag(zoomControls, savePageButton); assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); @@ -72,7 +76,9 @@ add_task(function() { "find-button", "preferences-button", "add-ons-button", - "developer-button"]; + "developer-button", + "sync-button", + ]; removeDeveloperButtonIfDevEdition(placementsAfterMove); simulateItemDrag(zoomControls, newWindowButton); assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); @@ -95,7 +101,9 @@ add_task(function() { "find-button", "preferences-button", "add-ons-button", - "developer-button"]; + "developer-button", + "sync-button", + ]; removeDeveloperButtonIfDevEdition(placementsAfterMove); simulateItemDrag(zoomControls, historyPanelMenu); assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); @@ -122,7 +130,9 @@ add_task(function() { "find-button", "preferences-button", "add-ons-button", - "developer-button"]; + "developer-button", + "sync-button", + ]; removeDeveloperButtonIfDevEdition(placementsAfterMove); simulateItemDrag(zoomControls, preferencesButton); assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); @@ -149,7 +159,9 @@ add_task(function() { "find-button", "preferences-button", "add-ons-button", - "developer-button"]; + "developer-button", + "sync-button", + ]; removeDeveloperButtonIfDevEdition(placementsAfterInsert); simulateItemDrag(openFileButton, zoomControls); assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert); @@ -188,7 +200,9 @@ add_task(function() { "find-button", "preferences-button", "add-ons-button", - "developer-button"]; + "developer-button", + "sync-button", + ]; removeDeveloperButtonIfDevEdition(placementsAfterInsert); simulateItemDrag(openFileButton, editControls); assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert); @@ -224,7 +238,9 @@ add_task(function() { "find-button", "preferences-button", "add-ons-button", - "developer-button"]; + "developer-button", + "sync-button", + ]; removeDeveloperButtonIfDevEdition(placementsAfterMove); simulateItemDrag(editControls, zoomControls); assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); @@ -248,7 +264,9 @@ add_task(function() { "find-button", "preferences-button", "add-ons-button", - "developer-button"]; + "developer-button", + "sync-button", + ]; removeDeveloperButtonIfDevEdition(placementsAfterMove); simulateItemDrag(editControls, newWindowButton); assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); @@ -275,7 +293,9 @@ add_task(function() { "find-button", "preferences-button", "add-ons-button", - "developer-button"]; + "developer-button", + "sync-button", + ]; removeDeveloperButtonIfDevEdition(placementsAfterMove); simulateItemDrag(editControls, privateBrowsingButton); assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); @@ -302,7 +322,9 @@ add_task(function() { "find-button", "preferences-button", "add-ons-button", - "developer-button"]; + "developer-button", + "sync-button", + ]; removeDeveloperButtonIfDevEdition(placementsAfterMove); simulateItemDrag(editControls, savePageButton); assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); @@ -328,7 +350,9 @@ add_task(function() { "preferences-button", "add-ons-button", "edit-controls", - "developer-button"]; + "developer-button", + "sync-button", + ]; removeDeveloperButtonIfDevEdition(placementsAfterMove); simulateItemDrag(editControls, panel); assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); @@ -353,7 +377,9 @@ add_task(function() { "find-button", "preferences-button", "add-ons-button", - "developer-button"]; + "developer-button", + "sync-button", + ]; removeDeveloperButtonIfDevEdition(placementsAfterMove); let paletteChildElementCount = palette.childElementCount; simulateItemDrag(editControls, palette); @@ -379,6 +405,12 @@ add_task(function() { let panel = document.getElementById(CustomizableUI.AREA_PANEL); let numPlaceholders = 2; for (let i = 0; i < numPlaceholders; i++) { + // This test relies on there being a specific number of widgets in the + // panel. The addition of sync-button screwed this up, so we remove it + // here. We should either fix the tests to not rely on the specific layout, + // or fix bug 1007910 which would change the placeholder logic in different + // ways. Bug 1229236 is for these tests to be smarter. + CustomizableUI.removeWidgetFromArea("sync-button"); // NB: We can't just iterate over all of the placeholders // because each drag-drop action recreates them. let placeholder = panel.getElementsByClassName("panel-customization-placeholder")[i]; @@ -399,6 +431,7 @@ add_task(function() { assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); let zoomControls = document.getElementById("zoom-controls"); simulateItemDrag(editControls, zoomControls); + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); ok(CustomizableUI.inDefaultState, "Should still be in default state."); } }); @@ -420,6 +453,10 @@ add_task(function() { // Dragging a small button onto the last big button should work. add_task(function() { + // Bug 1007910 requires there be a placeholder on the final row for this + // test to work as written. The addition of sync-button meant that's not true + // so we remove it from here. Bug 1229236 is for these tests to be smarter. + CustomizableUI.removeWidgetFromArea("sync-button"); yield startCustomizing(); let editControls = document.getElementById("edit-controls"); let panel = document.getElementById(CustomizableUI.AREA_PANEL); @@ -439,7 +476,7 @@ add_task(function() { removeDeveloperButtonIfDevEdition(placementsAfterMove); simulateItemDrag(editControls, target); assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove); - let itemToDrag = "sync-button"; + let itemToDrag = "email-link-button"; // any button in the palette by default. let button = document.getElementById(itemToDrag); placementsAfterMove.splice(11, 0, itemToDrag); simulateItemDrag(button, editControls); @@ -450,6 +487,7 @@ add_task(function() { let zoomControls = document.getElementById("zoom-controls"); simulateItemDrag(button, palette); simulateItemDrag(editControls, zoomControls); + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); ok(CustomizableUI.inDefaultState, "Should be in default state again."); }); diff --git a/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js b/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js index 667b6147d599c293726baeee30427af8542ecf9c..3707f7a2b8084123612799de3bed951366028dad 100644 --- a/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js +++ b/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js @@ -20,7 +20,9 @@ add_task(function() { ok(!CustomizableUI.inDefaultState, "Should not be in default state if on DevEdition."); } - let btn = document.getElementById("open-file-button"); + // This test relies on an exact number of widgets being in the panel. + // Remove the sync-button to satisfy that. (bug 1229236) + CustomizableUI.removeWidgetFromArea("sync-button"); let panel = document.getElementById(CustomizableUI.AREA_PANEL); let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL); @@ -35,6 +37,7 @@ add_task(function() { CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2); } + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); ok(CustomizableUI.inDefaultState, "Should be in default state again."); }); @@ -46,6 +49,10 @@ add_task(function() { CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL); } + // This test relies on an exact number of widgets being in the panel. + // Remove the sync-button to satisfy that. (bug 1229236) + CustomizableUI.removeWidgetFromArea("sync-button"); + let btn = document.getElementById("open-file-button"); let panel = document.getElementById(CustomizableUI.AREA_PANEL); let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL); @@ -74,7 +81,8 @@ add_task(function() { CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2); } - ok(CustomizableUI.inDefaultState, "Should be in default state again."); + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); + ok(CustomizableUI.inDefaultState, "Should be in default state again."); }); // Two orphaned items should have one placeholder next to them (case 2). @@ -84,6 +92,9 @@ add_task(function() { if (isInDevEdition()) { CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL); } + // This test relies on an exact number of widgets being in the panel. + // Remove the sync-button to satisfy that. (bug 1229236) + CustomizableUI.removeWidgetFromArea("sync-button"); let btn = document.getElementById("add-ons-button"); let btn2 = document.getElementById("developer-button"); @@ -112,6 +123,7 @@ add_task(function() { CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2); } + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); ok(CustomizableUI.inDefaultState, "Should be in default state again."); }); @@ -123,6 +135,10 @@ add_task(function() { CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL); } + // This test relies on an exact number of widgets being in the panel. + // Remove the sync-button to satisfy that. (bug 1229236) + CustomizableUI.removeWidgetFromArea("sync-button"); + let btn = document.getElementById("edit-controls"); let btn2 = document.getElementById("developer-button"); let panel = document.getElementById(CustomizableUI.AREA_PANEL); @@ -151,6 +167,7 @@ add_task(function() { CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2); } + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); ok(CustomizableUI.inDefaultState, "Should be in default state again."); }); @@ -167,12 +184,18 @@ add_task(function() { let panel = document.getElementById(CustomizableUI.AREA_PANEL); ok(CustomizableUI.inDefaultState, "Should be in default state."); + + // This test relies on an exact number of widgets being in the panel. + // Remove the sync-button to satisfy that. (bug 1229236) + CustomizableUI.removeWidgetFromArea("sync-button"); + is(getVisiblePlaceholderCount(panel), numPlaceholders, "Should have " + numPlaceholders + " visible placeholders before exiting"); yield endCustomizing(); yield startCustomizing(); is(getVisiblePlaceholderCount(panel), numPlaceholders, "Should have " + numPlaceholders + " visible placeholders after re-entering"); + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); ok(CustomizableUI.inDefaultState, "Should still be in default state."); }); diff --git a/browser/components/customizableui/test/browser_967000_button_sync.js b/browser/components/customizableui/test/browser_967000_button_sync.js index b567bf7fce3cc08fe99bd38038bdfc5a5bca26d5..bf6be6e5b29e199fb3ac9081283e96e8b25a8338 100644 --- a/browser/components/customizableui/test/browser_967000_button_sync.js +++ b/browser/components/customizableui/test/browser_967000_button_sync.js @@ -2,16 +2,51 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// The test expects the about:accounts page to open in the current tab - "use strict"; +let {SyncedTabs} = Cu.import("resource://services-sync/SyncedTabs.jsm", {}); + XPCOMUtils.defineLazyModuleGetter(this, "UITour", "resource:///modules/UITour.jsm"); +// These are available on the widget implementation, but it seems impossible +// to grab that impl at runtime. +const DECKINDEX_TABS = 0; +const DECKINDEX_TABSDISABLED = 1; +const DECKINDEX_FETCHING = 2; +const DECKINDEX_NOCLIENTS = 3; + var initialLocation = gBrowser.currentURI.spec; var newTab = null; -function openAboutAccountsFromMenuPanel(entryPoint) { +// A helper to notify there are new tabs. Returns a promise that is resolved +// once the UI has been updated. +function updateTabsPanel() { + let promiseTabsUpdated = promiseObserverNotified("synced-tabs-menu:test:tabs-updated"); + Services.obs.notifyObservers(null, SyncedTabs.TOPIC_TABS_CHANGED, null); + return promiseTabsUpdated; +} + +// This is the mock we use for SyncedTabs.jsm - tests may override various +// functions. +let mockedInternal = { + get isConfiguredToSyncTabs() { return true; }, + getTabClients() { return []; }, + syncTabs() {}, + hasSyncedThisSession: false, +}; + + +add_task(function* setup() { + let oldInternal = SyncedTabs._internal; + SyncedTabs._internal = mockedInternal; + + registerCleanupFunction(() => { + SyncedTabs._internal = oldInternal; + }); +}); + +// The test expects the about:preferences#sync page to open in the current tab +function openPrefsFromMenuPanel(expectedPanelId, entryPoint) { info("Check Sync button functionality"); Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri", "http://example.com/"); @@ -29,6 +64,18 @@ function openAboutAccountsFromMenuPanel(entryPoint) { let syncButton = document.getElementById("sync-button"); ok(syncButton, "The Sync button was added to the Panel Menu"); + syncButton.click(); + let syncPanel = document.getElementById("PanelUI-remotetabs"); + ok(syncPanel.getAttribute("current"), "Sync Panel is in view"); + + // Sync is not configured - verify that state is reflected. + let subpanel = document.getElementById(expectedPanelId) + ok(!subpanel.hidden, "sync setup element is visible"); + + // Find and click the "setup" button. + let setupButton = subpanel.querySelector(".PanelUI-remotetabs-prefs-button"); + setupButton.click(); + let deferred = Promise.defer(); let handler = (e) => { if (e.originalTarget != gBrowser.selectedBrowser.contentDocument || @@ -41,7 +88,6 @@ function openAboutAccountsFromMenuPanel(entryPoint) { } gBrowser.selectedBrowser.addEventListener("load", handler, true); - syncButton.click(); yield deferred.promise; newTab = gBrowser.selectedTab; @@ -68,8 +114,162 @@ function asyncCleanup() { UITour.tourBrowsersByWindow.delete(window); } -add_task(() => openAboutAccountsFromMenuPanel("syncbutton")); +// When Sync is not setup. +add_task(() => openPrefsFromMenuPanel("PanelUI-remotetabs-setupsync", "syncbutton")); add_task(asyncCleanup); // Test that uitour is in progress, the entrypoint is `uitour` and not `menupanel` -add_task(() => openAboutAccountsFromMenuPanel("uitour")); +add_task(() => openPrefsFromMenuPanel("PanelUI-remotetabs-setupsync", "uitour")); add_task(asyncCleanup); + +// When Sync is configured in a "needs reauthentication" state. +add_task(function* () { + // configure our broadcasters so we are in the right state. + document.getElementById("sync-reauth-state").hidden = false; + document.getElementById("sync-setup-state").hidden = true; + document.getElementById("sync-syncnow-state").hidden = true; + yield openPrefsFromMenuPanel("PanelUI-remotetabs-reauthsync", "syncbutton") +}); + +// Test the "Sync Now" button +add_task(function* () { + let nSyncs = 0; + mockedInternal.getTabClients = () => []; + mockedInternal.syncTabs = () => { + nSyncs++; + return Promise.resolve(); + } + + // configure our broadcasters so we are in the right state. + document.getElementById("sync-reauth-state").hidden = true; + document.getElementById("sync-setup-state").hidden = true; + document.getElementById("sync-syncnow-state").hidden = false; + + // add the Sync button to the panel + CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); + yield PanelUI.show(); + document.getElementById("sync-button").click(); + let syncPanel = document.getElementById("PanelUI-remotetabs"); + ok(syncPanel.getAttribute("current"), "Sync Panel is in view"); + + let subpanel = document.getElementById("PanelUI-remotetabs-main") + ok(!subpanel.hidden, "main pane is visible"); + let deck = document.getElementById("PanelUI-remotetabs-deck"); + + // The widget is still fetching tabs, as we've neutered everything that + // provides them + is(deck.selectedIndex, DECKINDEX_FETCHING, "first deck entry is visible"); + + let syncNowButton = document.getElementById("PanelUI-remotetabs-syncnow"); + + let didSync = false; + let oldDoSync = gSyncUI.doSync; + gSyncUI.doSync = function() { + didSync = true; + mockedInternal.hasSyncedThisSession = true; + gSyncUI.doSync = oldDoSync; + } + syncNowButton.click(); + ok(didSync, "clicking the button called the correct function"); + + // Tell the widget there are tabs available, but with zero clients. + mockedInternal.getTabClients = () => { + return Promise.resolve([]); + } + yield updateTabsPanel(); + // The UI should be showing the "no clients" pane. + is(deck.selectedIndex, DECKINDEX_NOCLIENTS, "no-clients deck entry is visible"); + + // Tell the widget there are tabs available - we have 3 clients, one with no + // tabs. + mockedInternal.getTabClients = () => { + return Promise.resolve([ + { + id: "guid_mobile", + type: "client", + name: "My Phone", + tabs: [], + }, + { + id: "guid_desktop", + type: "client", + name: "My Desktop", + tabs: [ + { + title: "http://example.com/10", + lastUsed: 10, // the most recent + }, + { + title: "http://example.com/1", + lastUsed: 1, // the least recent. + }, + { + title: "http://example.com/5", + lastUsed: 5, + }, + ], + }, + { + id: "guid_second_desktop", + name: "My Other Desktop", + tabs: [ + { + title: "http://example.com/6", + lastUsed: 6, + } + ], + }, + ]); + }; + yield updateTabsPanel(); + + // The UI should be showing tabs! + is(deck.selectedIndex, DECKINDEX_TABS, "no-clients deck entry is visible"); + let tabList = document.getElementById("PanelUI-remotetabs-tabslist"); + let node = tabList.firstChild; + // First entry should be the client with the most-recent tab. + is(node.getAttribute("itemtype"), "client", "node is a client entry"); + is(node.textContent, "My Desktop", "correct client"); + // Next entry is the most-recent tab + node = node.nextSibling; + is(node.getAttribute("itemtype"), "tab", "node is a tab"); + is(node.getAttribute("label"), "http://example.com/10"); + + // Next entry is the next-most-recent tab + node = node.nextSibling; + is(node.getAttribute("itemtype"), "tab", "node is a tab"); + is(node.getAttribute("label"), "http://example.com/5"); + + // Next entry is the least-recent tab from the first client. + node = node.nextSibling; + is(node.getAttribute("itemtype"), "tab", "node is a tab"); + is(node.getAttribute("label"), "http://example.com/1"); + + // Next is a menuseparator between the clients. + node = node.nextSibling; + is(node.nodeName, "menuseparator"); + + // Next is the client with 1 tab. + node = node.nextSibling; + is(node.getAttribute("itemtype"), "client", "node is a client entry"); + is(node.textContent, "My Other Desktop", "correct client"); + // Its single tab + node = node.nextSibling; + is(node.getAttribute("itemtype"), "tab", "node is a tab"); + is(node.getAttribute("label"), "http://example.com/6"); + + // Next is a menuseparator between the clients. + node = node.nextSibling; + is(node.nodeName, "menuseparator"); + + // Next is the client with no tab. + node = node.nextSibling; + is(node.getAttribute("itemtype"), "client", "node is a client entry"); + is(node.textContent, "My Phone", "correct client"); + // There is a single node saying there's no tabs for the client. + node = node.nextSibling; + is(node.nodeName, "label", "node is a label"); + is(node.getAttribute("itemtype"), "", "node is neither a tab nor a client"); + + node = node.nextSibling; + is(node, null, "no more entries"); +}); diff --git a/browser/components/customizableui/test/browser_975719_customtoolbars_behaviour.js b/browser/components/customizableui/test/browser_975719_customtoolbars_behaviour.js index ed0f8864a2e0502bee808b383647985e64a3a138..95964e9e6984a6ce65e5cf5e23f5d429204ff902 100644 --- a/browser/components/customizableui/test/browser_975719_customtoolbars_behaviour.js +++ b/browser/components/customizableui/test/browser_975719_customtoolbars_behaviour.js @@ -4,6 +4,13 @@ "use strict"; +const kXULWidgetId = "a-test-button"; // we'll create a button with this ID. + +add_task(function setup() { + // create a XUL button and add it to the palette. + createDummyXULButton(kXULWidgetId, "test-button"); +}); + add_task(function customizeToolbarAndKeepIt() { ok(gNavToolbox.toolbarset, "There should be a toolbarset"); let toolbarID = "testAustralisCustomToolbar"; @@ -97,11 +104,11 @@ add_task(function resetShouldDealWithCustomToolbars() { return; } ok(!CustomizableUI.getWidgetIdsInArea(toolbarDOMID).length, "There should be no widgets in the area yet."); - CustomizableUI.addWidgetToArea("sync-button", toolbarDOMID, 0); + CustomizableUI.addWidgetToArea(kXULWidgetId, toolbarDOMID, 0); ok(toolbarElement.hasChildNodes(), "Toolbar should now have a button."); - assertAreaPlacements(toolbarDOMID, ["sync-button"]); + assertAreaPlacements(toolbarDOMID, [kXULWidgetId]); - gNavToolbox.toolbarset.setAttribute("toolbar2", toolbarID + ":sync-button"); + gNavToolbox.toolbarset.setAttribute("toolbar2", `${toolbarID}:${kXULWidgetId}`); document.persist(gNavToolbox.toolbarset.id, "toolbar2"); let newWindow = yield openAndLoadWindow({}, true); @@ -118,9 +125,9 @@ add_task(function resetShouldDealWithCustomToolbars() { yield promiseWindowClosed(newWindow); ok(CustomizableUI.inDefaultState, "Should be in default state after reset."); - let syncButton = document.getElementById("sync-button"); - ok(!syncButton, "Sync button shouldn't be in the document anymore."); - ok(gNavToolbox.palette.querySelector("#sync-button"), "Sync button should be in the palette"); + let xulButton = document.getElementById(kXULWidgetId); + ok(!xulButton, "XUL button shouldn't be in the document anymore."); + ok(gNavToolbox.palette.querySelector(`#${kXULWidgetId}`), "XUL button should be in the palette"); ok(!toolbarElement.hasChildNodes(), "Toolbar should have no more child nodes."); ok(!toolbarElement.parentNode, "Toolbar should no longer be in the DOM."); cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID); diff --git a/browser/components/customizableui/test/browser_987185_syncButton.js b/browser/components/customizableui/test/browser_987185_syncButton.js index 7bd0640158b6da799c01bf7461cc259c4b464b02..988d738be4b8e48eb4fabc99ad483aab94ea3dc9 100755 --- a/browser/components/customizableui/test/browser_987185_syncButton.js +++ b/browser/components/customizableui/test/browser_987185_syncButton.js @@ -26,7 +26,15 @@ add_task(function* testSyncButtonFunctionality() { let syncButton = document.getElementById("sync-button"); ok(syncButton, "The Sync button was added to the Panel Menu"); + // click the button - the panel should open. syncButton.click(); + let syncPanel = document.getElementById("PanelUI-remotetabs"); + ok(syncPanel.getAttribute("current"), "Sync Panel is in view"); + + // Find and click the "setup" button. + let syncNowButton = document.getElementById("PanelUI-remotetabs-syncnow"); + syncNowButton.click(); + info("The sync button was clicked"); yield waitForCondition(() => syncWasCalled); diff --git a/browser/components/customizableui/test/browser_995164_registerArea_during_customize_mode.js b/browser/components/customizableui/test/browser_995164_registerArea_during_customize_mode.js index 422272051242f76716c6cac456f1501ccf2bf3d9..708124325474976e9678895ead6c72dd9036af77 100644 --- a/browser/components/customizableui/test/browser_995164_registerArea_during_customize_mode.js +++ b/browser/components/customizableui/test/browser_995164_registerArea_during_customize_mode.js @@ -6,33 +6,36 @@ const TOOLBARID = "test-toolbar-added-during-customize-mode"; +// The ID of a button that is not placed (ie, is in the palette) by default +const kNonPlacedWidgetId = "open-file-button"; + add_task(function*() { yield startCustomizing(); let toolbar = createToolbarWithPlacements(TOOLBARID, []); - CustomizableUI.addWidgetToArea("sync-button", TOOLBARID); - let syncButton = document.getElementById("sync-button"); - ok(syncButton, "Sync button should exist."); - is(syncButton.parentNode.localName, "toolbarpaletteitem", "Sync button's parent node should be a wrapper."); + CustomizableUI.addWidgetToArea(kNonPlacedWidgetId, TOOLBARID); + let button = document.getElementById(kNonPlacedWidgetId); + ok(button, "Button should exist."); + is(button.parentNode.localName, "toolbarpaletteitem", "Button's parent node should be a wrapper."); - simulateItemDrag(syncButton, gNavToolbox.palette); - ok(!CustomizableUI.getPlacementOfWidget("sync-button"), "Button moved to the palette"); - ok(gNavToolbox.palette.querySelector("#sync-button"), "Sync button really is in palette."); + simulateItemDrag(button, gNavToolbox.palette); + ok(!CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved to the palette"); + ok(gNavToolbox.palette.querySelector(`#${kNonPlacedWidgetId}`), "Button really is in palette."); - syncButton.scrollIntoView(); - simulateItemDrag(syncButton, toolbar); - ok(CustomizableUI.getPlacementOfWidget("sync-button"), "Button moved out of palette"); - is(CustomizableUI.getPlacementOfWidget("sync-button").area, TOOLBARID, "Button's back on toolbar"); - ok(toolbar.querySelector("#sync-button"), "Sync button really is on toolbar."); + button.scrollIntoView(); + simulateItemDrag(button, toolbar); + ok(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved out of palette"); + is(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId).area, TOOLBARID, "Button's back on toolbar"); + ok(toolbar.querySelector(`#${kNonPlacedWidgetId}`), "Button really is on toolbar."); yield endCustomizing(); - isnot(syncButton.parentNode.localName, "toolbarpaletteitem", "Sync button's parent node should not be a wrapper outside customize mode."); + isnot(button.parentNode.localName, "toolbarpaletteitem", "Button's parent node should not be a wrapper outside customize mode."); yield startCustomizing(); - is(syncButton.parentNode.localName, "toolbarpaletteitem", "Sync button's parent node should be a wrapper back in customize mode."); + is(button.parentNode.localName, "toolbarpaletteitem", "Button's parent node should be a wrapper back in customize mode."); - simulateItemDrag(syncButton, gNavToolbox.palette); - ok(!CustomizableUI.getPlacementOfWidget("sync-button"), "Button moved to the palette"); - ok(gNavToolbox.palette.querySelector("#sync-button"), "Sync button really is in palette."); + simulateItemDrag(button, gNavToolbox.palette); + ok(!CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved to the palette"); + ok(gNavToolbox.palette.querySelector(`#${kNonPlacedWidgetId}`), "Button really is in palette."); ok(!CustomizableUI.inDefaultState, "Not in default state while toolbar is not collapsed yet."); setToolbarVisibility(toolbar, false); @@ -50,11 +53,11 @@ add_task(function*() { ok(!CustomizableUI.inDefaultState, "Now that the toolbar is registered again, should no longer be in default state."); ok(gCustomizeMode.areas.has(toolbar), "Toolbar should be known to customize mode again."); - syncButton.scrollIntoView(); - simulateItemDrag(syncButton, toolbar); - ok(CustomizableUI.getPlacementOfWidget("sync-button"), "Button moved out of palette"); - is(CustomizableUI.getPlacementOfWidget("sync-button").area, TOOLBARID, "Button's back on toolbar"); - ok(toolbar.querySelector("#sync-button"), "Sync button really is on toolbar."); + button.scrollIntoView(); + simulateItemDrag(button, toolbar); + ok(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved out of palette"); + is(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId).area, TOOLBARID, "Button's back on toolbar"); + ok(toolbar.querySelector(`#${kNonPlacedWidgetId}`), "Button really is on toolbar."); let otherWin = yield openAndLoadWindow({}, true); let otherTB = otherWin.document.createElementNS(kNSXUL, "toolbar"); @@ -73,19 +76,19 @@ add_task(function*() { ok(wasInformedCorrectlyOfAreaAppearing, "Should have been told area was registered."); CustomizableUI.removeListener(listener); - ok(otherTB.querySelector("#sync-button"), "Sync button is on other toolbar, too."); + ok(otherTB.querySelector(`#${kNonPlacedWidgetId}`), "Button is on other toolbar, too."); - simulateItemDrag(syncButton, gNavToolbox.palette); - ok(!CustomizableUI.getPlacementOfWidget("sync-button"), "Button moved to the palette"); - ok(gNavToolbox.palette.querySelector("#sync-button"), "Sync button really is in palette."); - ok(!otherTB.querySelector("#sync-button"), "Sync button is in palette in other window, too."); + simulateItemDrag(button, gNavToolbox.palette); + ok(!CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved to the palette"); + ok(gNavToolbox.palette.querySelector(`#${kNonPlacedWidgetId}`), "Button really is in palette."); + ok(!otherTB.querySelector(`#${kNonPlacedWidgetId}`), "Button is in palette in other window, too."); - syncButton.scrollIntoView(); - simulateItemDrag(syncButton, toolbar); - ok(CustomizableUI.getPlacementOfWidget("sync-button"), "Button moved out of palette"); - is(CustomizableUI.getPlacementOfWidget("sync-button").area, TOOLBARID, "Button's back on toolbar"); - ok(toolbar.querySelector("#sync-button"), "Sync button really is on toolbar."); - ok(otherTB.querySelector("#sync-button"), "Sync button is on other toolbar, too."); + button.scrollIntoView(); + simulateItemDrag(button, toolbar); + ok(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved out of palette"); + is(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId).area, TOOLBARID, "Button's back on toolbar"); + ok(toolbar.querySelector(`#${kNonPlacedWidgetId}`), "Button really is on toolbar."); + ok(otherTB.querySelector(`#${kNonPlacedWidgetId}`), "Button is on other toolbar, too."); let wasInformedCorrectlyOfAreaDisappearing = false; //XXXgijs So we could be using promiseWindowClosed here. However, after @@ -120,7 +123,7 @@ add_task(function*() { is(windowClosed, otherWin, "Window should have sent onWindowClosed notification."); ok(wasInformedCorrectlyOfAreaDisappearing, "Should be told about window closing."); // Closing the other window should not be counted against this window's customize mode: - is(syncButton.parentNode.localName, "toolbarpaletteitem", "Sync button's parent node should still be a wrapper."); + is(button.parentNode.localName, "toolbarpaletteitem", "Button's parent node should still be a wrapper."); ok(gCustomizeMode.areas.has(toolbar), "Toolbar should still be a customizable area for this customize mode instance."); yield gCustomizeMode.reset(); diff --git a/browser/components/customizableui/test/head.js b/browser/components/customizableui/test/head.js index 49c2ea5c4b02e407607c5d8a4a8a9e5ad5dfc1a9..b4819668448aacee6a27bb39aa1bccf35fcca500 100644 --- a/browser/components/customizableui/test/head.js +++ b/browser/components/customizableui/test/head.js @@ -26,12 +26,12 @@ var {synthesizeDragStart, synthesizeDrop} = ChromeUtils; const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; const kTabEventFailureTimeoutInMs = 20000; -function createDummyXULButton(id, label) { +function createDummyXULButton(id, label, win = window) { let btn = document.createElementNS(kNSXUL, "toolbarbutton"); btn.id = id; btn.setAttribute("label", label || id); btn.className = "toolbarbutton-1 chromeclass-toolbar-additional"; - window.gNavToolbox.palette.appendChild(btn); + win.gNavToolbox.palette.appendChild(btn); return btn; } diff --git a/browser/components/extensions/ext-browserAction.js b/browser/components/extensions/ext-browserAction.js index 81bdb33e5d47589e9d22f02cfa5bbe801ad3e2b5..4c5cd177a45b74421a69e75e55bb3206ac39f9c1 100644 --- a/browser/components/extensions/ext-browserAction.js +++ b/browser/components/extensions/ext-browserAction.js @@ -32,6 +32,8 @@ function BrowserAction(options, extension) this.id = makeWidgetId(extension.id) + "-browser-action"; this.widget = null; + this.tabManager = TabManager.for(extension); + let title = extension.localize(options.default_title || ""); let popup = extension.localize(options.default_popup || ""); if (popup) { @@ -74,6 +76,7 @@ BrowserAction.prototype = { node.addEventListener("command", event => { let tab = tabbrowser.selectedTab; let popup = this.getProperty(tab, "popup"); + this.tabManager.addActiveTabPermission(tab); if (popup) { this.togglePopup(node, popup); } else { diff --git a/browser/components/extensions/ext-contextMenus.js b/browser/components/extensions/ext-contextMenus.js index 131880dcc58c12c03e2a2e6caacc7d2203bc5032..8e4724175cd37b68c001a1744b2987643cb773d3 100644 --- a/browser/components/extensions/ext-contextMenus.js +++ b/browser/components/extensions/ext-contextMenus.js @@ -43,8 +43,11 @@ var menuBuilder = { for (let [ext, menuItemMap] of contextMenuMap) { let parentMap = new Map(); let topLevelItems = new Set(); - for (let [id, item] of menuItemMap) { - dump(id + " : " + item + "\n"); + for (let entry of menuItemMap) { + // We need a closure over |item|, and we don't currently get a + // fresh binding per loop if we declare it in the loop head. + let [id, item] = entry; + if (item.enabledForContext(contextData)) { let element; if (item.isMenu) { @@ -79,15 +82,13 @@ var menuBuilder = { topLevelItems.add(element); } - if (item.onclick) { - function clickHandlerForItem(item) { - return event => { - let clickData = item.getClickData(contextData, event); - runSafe(item.extContext, item.onclick, clickData); - } + element.addEventListener("command", event => { + item.tabManager.addActiveTabPermission(); + if (item.onclick) { + let clickData = item.getClickData(contextData, event); + runSafe(item.extContext, item.onclick, clickData); } - element.addEventListener("command", clickHandlerForItem(item)); - } + }); } } if (topLevelItems.size > 1) { @@ -166,6 +167,8 @@ function MenuItem(extension, extContext, createProperties) this.extension = extension; this.extContext = extContext; + this.tabManager = TabManager.for(extension); + this.init(createProperties); } diff --git a/browser/components/extensions/ext-pageAction.js b/browser/components/extensions/ext-pageAction.js index 4a48f327d21ec092b488cab74b9921fd70b1dd64..5d40af343a5a598989c431b5206f688cbd26567d 100644 --- a/browser/components/extensions/ext-pageAction.js +++ b/browser/components/extensions/ext-pageAction.js @@ -20,6 +20,8 @@ function PageAction(options, extension) this.extension = extension; this.id = makeWidgetId(extension.id) + "-page-action"; + this.tabManager = TabManager.for(extension); + let title = extension.localize(options.default_title || ""); let popup = extension.localize(options.default_popup || ""); if (popup) { @@ -139,6 +141,8 @@ PageAction.prototype = { let tab = window.gBrowser.selectedTab; let popup = this.tabContext.get(tab).popup; + this.tabManager.addActiveTabPermission(tab); + if (popup) { openPanel(this.getButton(window), popup, this.extension); } else { diff --git a/browser/components/extensions/ext-tabs.js b/browser/components/extensions/ext-tabs.js index 67f661e1fc98b399436485eca427cfdee3cfc317..7bef2189b1ffc64c15f1a66b152735198dceccf2 100644 --- a/browser/components/extensions/ext-tabs.js +++ b/browser/components/extensions/ext-tabs.js @@ -457,13 +457,8 @@ extensions.registerAPI((extension, context) => { } let result = []; - let e = Services.wm.getEnumerator("navigator:browser"); - while (e.hasMoreElements()) { - let window = e.getNext(); - if (window.document.readyState != "complete") { - continue; - } - let tabs = TabManager.getTabs(extension, window); + for (let window of WindowListManager.browserWindows()) { + let tabs = TabManager.for(extension).getTabs(window); for (let tab of tabs) { if (matches(window, tab)) { result.push(tab); @@ -477,12 +472,38 @@ extensions.registerAPI((extension, context) => { let tab = tabId ? TabManager.getTab(tabId) : TabManager.activeTab; let mm = tab.linkedBrowser.messageManager; - let options = {js: [], css: []}; + let options = { + js: [], + css: [], + + // We need to send the inner window ID to make sure we only + // execute the script if the window is currently navigated to + // the document that we expect. + // + // TODO: When we add support for callbacks, non-matching + // window IDs and insufficient permissions need to result in a + // callback with |lastError| set. + innerWindowID: tab.linkedBrowser.innerWindowID, + }; + + if (TabManager.for(extension).hasActiveTabPermission(tab)) { + // If we have the "activeTab" permission for this tab, ignore + // the host whitelist. + options.matchesHost = ["<all_urls>"]; + } else { + options.matchesHost = extension.whiteListedHosts.serialize(); + } + if (details.code) { options[kind + 'Code'] = details.code; } if (details.file) { - options[kind].push(extension.baseURI.resolve(details.file)); + let url = context.uri.resolve(details.file); + if (extension.isExtensionURL(url)) { + // We should really set |lastError| here, and go straight to + // the callback, but we don't have |lastError| yet. + options[kind].push(url); + } } if (details.allFrames) { options.all_frames = details.allFrames; diff --git a/browser/components/extensions/ext-utils.js b/browser/components/extensions/ext-utils.js index b7626d8f26c90e5b06411a964daeb5cf7580ae66..4259651f776ec351b7a82d515976826c1b3ac1df 100644 --- a/browser/components/extensions/ext-utils.js +++ b/browser/components/extensions/ext-utils.js @@ -267,7 +267,87 @@ TabContext.prototype = { }, }; -// Manages mapping between XUL tabs and extension tab IDs. +// Manages tab mappings and permissions for a specific extension. +function ExtensionTabManager(extension) { + this.extension = extension; + + // A mapping of tab objects to the inner window ID the extension currently has + // the active tab permission for. The active permission for a given tab is + // valid only for the inner window that was active when the permission was + // granted. If the tab navigates, the inner window ID changes, and the + // permission automatically becomes stale. + // + // WeakMap[tab => inner-window-id<int>] + this.hasTabPermissionFor = new WeakMap(); +} + +ExtensionTabManager.prototype = { + addActiveTabPermission(tab = TabManager.activeTab) { + if (this.extension.hasPermission("activeTab")) { + // Note that, unlike Chrome, we don't currently clear this permission with + // the tab navigates. If the inner window is revived from BFCache before + // we've granted this permission to a new inner window, the extension + // maintains its permissions for it. + this.hasTabPermissionFor.set(tab, tab.linkedBrowser.innerWindowID); + } + }, + + // Returns true if the extension has the "activeTab" permission for this tab. + // This is somewhat more permissive than the generic "tabs" permission, as + // checked by |hasTabPermission|, in that it also allows programmatic script + // injection without an explicit host permission. + hasActiveTabPermission(tab) { + // This check is redundant with addTabPermission, but cheap. + if (this.extension.hasPermission("activeTab")) { + return (this.hasTabPermissionFor.has(tab) && + this.hasTabPermissionFor.get(tab) === tab.linkedBrowser.innerWindowID); + } + return false; + }, + + hasTabPermission(tab) { + return this.extension.hasPermission("tabs") || this.hasActiveTabPermission(tab); + }, + + convert(tab) { + let window = tab.ownerDocument.defaultView; + let windowActive = window == WindowManager.topWindow; + + let result = { + id: TabManager.getId(tab), + index: tab._tPos, + windowId: WindowManager.getId(window), + selected: tab.selected, + highlighted: tab.selected, + active: tab.selected, + pinned: tab.pinned, + status: TabManager.getStatus(tab), + incognito: PrivateBrowsingUtils.isBrowserPrivate(tab.linkedBrowser), + width: tab.linkedBrowser.clientWidth, + height: tab.linkedBrowser.clientHeight, + }; + + if (this.hasTabPermission(tab)) { + result.url = tab.linkedBrowser.currentURI.spec; + if (tab.linkedBrowser.contentTitle) { + result.title = tab.linkedBrowser.contentTitle; + } + let icon = window.gBrowser.getIcon(tab); + if (icon) { + result.favIconUrl = icon; + } + } + + return result; + }, + + getTabs(window) { + return Array.from(window.gBrowser.tabs, tab => this.convert(tab)); + }, +}; + + +// Manages global mappings between XUL tabs and extension tab IDs. global.TabManager = { _tabs: new WeakMap(), _nextId: 1, @@ -322,44 +402,26 @@ global.TabManager = { }, convert(extension, tab) { - let window = tab.ownerDocument.defaultView; - let windowActive = window == WindowManager.topWindow; - let result = { - id: this.getId(tab), - index: tab._tPos, - windowId: WindowManager.getId(window), - selected: tab.selected, - highlighted: tab.selected, - active: tab.selected, - pinned: tab.pinned, - status: this.getStatus(tab), - incognito: PrivateBrowsingUtils.isBrowserPrivate(tab.linkedBrowser), - width: tab.linkedBrowser.clientWidth, - height: tab.linkedBrowser.clientHeight, - }; - - if (extension.hasPermission("tabs")) { - result.url = tab.linkedBrowser.currentURI.spec; - if (tab.linkedBrowser.contentTitle) { - result.title = tab.linkedBrowser.contentTitle; - } - let icon = window.gBrowser.getIcon(tab); - if (icon) { - result.favIconUrl = icon; - } - } - - return result; + return TabManager.for(extension).convert(tab); }, +}; - getTabs(extension, window) { - if (!window.gBrowser) { - return []; - } - return Array.map(window.gBrowser.tabs, tab => this.convert(extension, tab)); - }, +// WeakMap[Extension -> ExtensionTabManager] +let tabManagers = new WeakMap(); + +// Returns the extension-specific tab manager for the given extension, or +// creates one if it doesn't already exist. +TabManager.for = function (extension) { + if (!tabManagers.has(extension)) { + tabManagers.set(extension, new ExtensionTabManager(extension)); + } + return tabManagers.get(extension); }; +extensions.on("shutdown", (type, extension) => { + tabManagers.delete(extension); +}); + // Manages mapping between XUL windows and extension window IDs. global.WindowManager = { _windows: new WeakMap(), @@ -411,7 +473,7 @@ global.WindowManager = { }; if (getInfo && getInfo.populate) { - results.tabs = TabManager.getTabs(extension, window); + results.tabs = TabManager.for(extension).getTabs(window); } return result; diff --git a/browser/components/extensions/test/browser/browser.ini b/browser/components/extensions/test/browser/browser.ini index afa070235d4286f068772d6f3b1f9aed5f7c9e9e..31ba3d3b0b86fb3be4d05e9b70a8b1aa5239210e 100644 --- a/browser/components/extensions/test/browser/browser.ini +++ b/browser/components/extensions/test/browser/browser.ini @@ -20,7 +20,8 @@ support-files = [browser_ext_popup_api_injection.js] [browser_ext_contextMenus.js] [browser_ext_getViews.js] -[browser_ext_tabs_executeScript.js] +[browser_ext_tabs_executeScript_good.js] +[browser_ext_tabs_executeScript_bad.js] [browser_ext_tabs_query.js] [browser_ext_tabs_getCurrent.js] [browser_ext_tabs_update.js] diff --git a/browser/components/extensions/test/browser/browser_ext_contentscript_connect.js b/browser/components/extensions/test/browser/browser_ext_contentscript_connect.js index c45113eb1c4515d37f2605725adf8716ba619d29..09a3a4c3ad25d0dbd2e54d353bd15784d51b6bb1 100644 --- a/browser/components/extensions/test/browser/browser_ext_contentscript_connect.js +++ b/browser/components/extensions/test/browser/browser_ext_contentscript_connect.js @@ -3,7 +3,7 @@ add_task(function* () { let extension = ExtensionTestUtils.loadExtension({ manifest: { - "permissions": ["tabs"] + "permissions": ["http://mochi.test/"] }, background: function() { diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js deleted file mode 100644 index 6a13fd57befe192b5a7c0904fafadc154fbd7ccf..0000000000000000000000000000000000000000 --- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js +++ /dev/null @@ -1,32 +0,0 @@ -add_task(function* () { - let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/"); - - let extension = ExtensionTestUtils.loadExtension({ - manifest: { - "permissions": ["tabs"] - }, - - background: function() { - browser.runtime.onMessage.addListener((msg, sender) => { - browser.test.assertEq(msg, "script ran", "script ran"); - browser.test.notifyPass("executeScript"); - }); - - browser.tabs.executeScript({ - file: "script.js" - }); - }, - - files: { - "script.js": function() { - browser.runtime.sendMessage("script ran"); - } - } - }); - - yield extension.startup(); - yield extension.awaitFinish("executeScript"); - yield extension.unload(); - - yield BrowserTestUtils.removeTab(tab); -}); diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js new file mode 100644 index 0000000000000000000000000000000000000000..69486fc8b297c432da23e24034c85e292843b997 --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js @@ -0,0 +1,110 @@ +"use strict"; + +// This is a pretty terrible hack, but it's the best we can do until we +// support |executeScript| callbacks and |lastError|. +function* testHasNoPermission(params) { + let contentSetup = params.contentSetup || (() => Promise.resolve()); + + let extension = ExtensionTestUtils.loadExtension({ + manifest: params.manifest, + + background: `(${function(contentSetup) { + browser.runtime.onMessage.addListener((msg, sender) => { + browser.test.assertEq(msg, "second script ran", "second script ran"); + browser.test.notifyPass("executeScript"); + }); + + browser.test.onMessage.addListener(msg => { + browser.test.assertEq(msg, "execute-script"); + + browser.tabs.query({ activeWindow: true }, tabs => { + browser.tabs.executeScript({ + file: "script.js" + }); + + // Execute a script we know we have permissions for in the + // second tab, in the hopes that it will execute after the + // first one. This has intermittent failure written all over + // it, but it's just about the best we can do until we + // support callbacks for executeScript. + browser.tabs.executeScript(tabs[1].id, { + file: "second-script.js" + }); + }); + }); + + contentSetup().then(() => { + browser.test.sendMessage("ready"); + }); + }})(${contentSetup})`, + + files: { + "script.js": function() { + browser.runtime.sendMessage("first script ran"); + }, + + "second-script.js": function() { + browser.runtime.sendMessage("second script ran"); + } + } + }); + + yield extension.startup(); + yield extension.awaitMessage("ready"); + + if (params.setup) { + yield params.setup(extension); + } + + extension.sendMessage("execute-script"); + + yield extension.awaitFinish("executeScript"); + yield extension.unload(); +} + +add_task(function* testBadPermissions() { + let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/"); + let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/"); + + info("Test no special permissions"); + yield testHasNoPermission({ + manifest: { "permissions": ["http://example.com/"] } + }); + + info("Test tabs permissions"); + yield testHasNoPermission({ + manifest: { "permissions": ["http://example.com/", "tabs"] } + }); + + info("Test active tab, browser action, no click"); + yield testHasNoPermission({ + manifest: { + "permissions": ["http://example.com/", "activeTab"], + "browser_action": {}, + }, + }); + + info("Test active tab, page action, no click"); + yield testHasNoPermission({ + manifest: { + "permissions": ["http://example.com/", "activeTab"], + "page_action": {}, + }, + contentSetup() { + return new Promise(resolve => { + browser.tabs.query({ active: true, currentWindow: true }, tabs => { + browser.pageAction.show(tabs[0].id); + resolve(); + }); + }); + } + }); + + yield BrowserTestUtils.removeTab(tab2); + yield BrowserTestUtils.removeTab(tab1); +}); + +// TODO: Test that |executeScript| fails if the tab has navigated to a +// new page, and no longer matches our expected state. This involves +// intentionally trying to trigger a race condition, and is probably not +// even worth attempting until we have proper |executeScript| callbacks. diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js new file mode 100644 index 0000000000000000000000000000000000000000..d2d09b0a8456c9a1a6eb13887e44a5fc780b9a76 --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js @@ -0,0 +1,151 @@ +"use strict"; + +function* testHasPermission(params) { + let contentSetup = params.contentSetup || (() => Promise.resolve()); + + let extension = ExtensionTestUtils.loadExtension({ + manifest: params.manifest, + + background: `(${function(contentSetup) { + browser.runtime.onMessage.addListener((msg, sender) => { + browser.test.assertEq(msg, "script ran", "script ran"); + browser.test.notifyPass("executeScript"); + }); + + browser.test.onMessage.addListener(msg => { + browser.test.assertEq(msg, "execute-script"); + + browser.tabs.executeScript({ + file: "script.js" + }); + }); + + contentSetup().then(() => { + browser.test.sendMessage("ready"); + }); + }})(${contentSetup})`, + + files: { + "script.js": function() { + browser.runtime.sendMessage("script ran"); + } + } + }); + + yield extension.startup(); + yield extension.awaitMessage("ready"); + + if (params.setup) { + yield params.setup(extension); + } + + extension.sendMessage("execute-script"); + + yield extension.awaitFinish("executeScript"); + yield extension.unload(); +} + +add_task(function* testGoodPermissions() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/", true); + + info("Test explicit host permission"); + yield testHasPermission({ + manifest: { "permissions": ["http://mochi.test/"] } + }); + + info("Test explicit host subdomain permission"); + yield testHasPermission({ + manifest: { "permissions": ["http://*.mochi.test/"] } + }); + + info("Test explicit <all_urls> permission"); + yield testHasPermission({ + manifest: { "permissions": ["<all_urls>"] } + }); + + info("Test activeTab permission with a browser action click"); + yield testHasPermission({ + manifest: { + "permissions": ["activeTab"], + "browser_action": {}, + }, + contentSetup() { + browser.browserAction.onClicked.addListener(() => { + browser.test.log("Clicked."); + }); + return Promise.resolve(); + }, + setup: clickBrowserAction, + }); + + info("Test activeTab permission with a page action click"); + yield testHasPermission({ + manifest: { + "permissions": ["activeTab"], + "page_action": {}, + }, + contentSetup() { + return new Promise(resolve => { + browser.tabs.query({ active: true, currentWindow: true }, tabs => { + browser.pageAction.show(tabs[0].id); + resolve(); + }); + }); + }, + setup: clickPageAction, + }); + + info("Test activeTab permission with a browser action w/popup click"); + yield testHasPermission({ + manifest: { + "permissions": ["activeTab"], + "browser_action": { "default_popup": "_blank.html" }, + }, + setup: clickBrowserAction, + }); + + info("Test activeTab permission with a page action w/popup click"); + yield testHasPermission({ + manifest: { + "permissions": ["activeTab"], + "page_action": { "default_popup": "_blank.html" }, + }, + contentSetup() { + return new Promise(resolve => { + browser.tabs.query({ active: true, currentWindow: true }, tabs => { + browser.pageAction.show(tabs[0].id); + resolve(); + }); + }); + }, + setup: clickPageAction, + }); + + info("Test activeTab permission with a context menu click"); + yield testHasPermission({ + manifest: { + "permissions": ["activeTab", "contextMenus"], + }, + contentSetup() { + browser.contextMenus.create({ title: "activeTab", contexts: ["all"] }); + return Promise.resolve(); + }, + setup: function* (extension) { + let contextMenu = document.getElementById("contentAreaContextMenu"); + let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown"); + let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden"); + + yield BrowserTestUtils.synthesizeMouseAtCenter("a[href]", { type: "contextmenu", button: 2 }, + gBrowser.selectedBrowser); + yield awaitPopupShown; + + let item = contextMenu.querySelector("[label=activeTab]"); + + yield EventUtils.synthesizeMouseAtCenter(item, {}, window); + + yield awaitPopupHidden; + }, + }); + + yield BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/components/extensions/test/browser/head.js b/browser/components/extensions/test/browser/head.js index aa6d9bedebcb364f576eb8085e6eb5a5500120e2..6372fd0838dafbced45fe0a9b7e5d0fa05c99cfe 100644 --- a/browser/components/extensions/test/browser/head.js +++ b/browser/components/extensions/test/browser/head.js @@ -44,6 +44,6 @@ function clickPageAction(extension, win = window) { let pageActionId = makeWidgetId(extension.id) + "-page-action"; let elem = win.document.getElementById(pageActionId); - EventUtils.synthesizeMouse(elem, 8, 8, {}, win); + EventUtils.synthesizeMouseAtCenter(elem, {}, win); return new Promise(SimpleTest.executeSoon); } diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index e4bd610874d50a10b536a470e5472863d38cf248..e8faeeedbc6a34580563cfafb964dcd7d370e345 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -422,39 +422,16 @@ BrowserGlue.prototype = { this._dispose(); break; case "keyword-search": - // This is very similar to code in - // browser.js:BrowserSearch.recordSearchInHealthReport(). The code could - // be consolidated if there is will. We need the observer in - // nsBrowserGlue to prevent double counting. - let win = RecentWindow.getMostRecentBrowserWindow(); - BrowserUITelemetry.countSearchEvent("urlbar", win.gURLBar.value); - + // This notification is broadcast by the docshell when it "fixes up" a + // URI that it's been asked to load into a keyword search. let engine = null; try { engine = subject.QueryInterface(Ci.nsISearchEngine); } catch (ex) { Cu.reportError(ex); } - - win.BrowserSearch.recordSearchInTelemetry(engine, "urlbar"); -#ifdef MOZ_SERVICES_HEALTHREPORT - let reporter = Cc["@mozilla.org/datareporting/service;1"] - .getService() - .wrappedJSObject - .healthReporter; - - if (!reporter) { - return; - } - - reporter.onInit().then(function record() { - try { - reporter.getProvider("org.mozilla.searches").recordSearch(engine, "urlbar"); - } catch (ex) { - Cu.reportError(ex); - } - }); -#endif + let win = RecentWindow.getMostRecentBrowserWindow(); + win.BrowserSearch.recordSearchInHealthReport(engine, "urlbar"); break; case "browser-search-engine-modified": // Ensure we cleanup the hiddenOneOffs pref when removing diff --git a/browser/components/places/PlacesUIUtils.jsm b/browser/components/places/PlacesUIUtils.jsm index 058d4ce8061889d246a944224241f38e7fbd5864..22b03f4b6853cd389c06285ba456466ebb4d601e 100644 --- a/browser/components/places/PlacesUIUtils.jsm +++ b/browser/components/places/PlacesUIUtils.jsm @@ -1223,16 +1223,7 @@ this.PlacesUIUtils = { shouldShowTabsFromOtherComputersMenuitem: function() { let weaveOK = Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED && Weave.Svc.Prefs.get("firstSync", "") != "notReady"; - let cloudSyncOK = CloudSync && CloudSync.ready && CloudSync().tabsReady && CloudSync().tabs.hasRemoteTabs(); - return weaveOK || cloudSyncOK; - }, - - shouldEnableTabsFromOtherComputersMenuitem: function() { - let weaveEnabled = Weave.Service.isLoggedIn && - Weave.Service.engineManager.get("tabs") && - Weave.Service.engineManager.get("tabs").enabled; - let cloudSyncEnabled = CloudSync && CloudSync.ready && CloudSync().tabsReady && CloudSync().tabs.hasRemoteTabs(); - return weaveEnabled || cloudSyncEnabled; + return weaveOK; }, /** diff --git a/browser/components/preferences/in-content/sync.js b/browser/components/preferences/in-content/sync.js index 425e88adce479ea6f104b8882e54585575a783ad..f68970026ac90c6c30ac076bfc8fda7ba3f6c058 100644 --- a/browser/components/preferences/in-content/sync.js +++ b/browser/components/preferences/in-content/sync.js @@ -134,11 +134,16 @@ var gSyncPane = { XPCOMUtils.defineLazyGetter(this, '_stringBundle', () => { return Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties"); - }), + }); XPCOMUtils.defineLazyGetter(this, '_accountsStringBundle', () => { return Services.strings.createBundle("chrome://browser/locale/accounts.properties"); - }), + }); + + let url = Services.prefs.getCharPref("identity.mobilepromo.android") + "sync-preferences"; + document.getElementById("fxaMobilePromo-android").setAttribute("href", url); + url = Services.prefs.getCharPref("identity.mobilepromo.ios") + "sync-preferences"; + document.getElementById("fxaMobilePromo-ios").setAttribute("href", url); this.updateWeavePrefs(); diff --git a/browser/components/preferences/in-content/sync.xul b/browser/components/preferences/in-content/sync.xul index 79fefed7d4479b3cdad64da20617320ec7b1e3f7..f2f7ca532b9986b1b8a51db1059bc7ef51bed281 100644 --- a/browser/components/preferences/in-content/sync.xul +++ b/browser/components/preferences/in-content/sync.xul @@ -214,12 +214,12 @@ </hbox> <label class="fxaMobilePromo"> &mobilePromo2.start;<!-- We put these comments to avoid inserting white spaces - --><label class="androidLink text-link" - href="https://www.mozilla.org/firefox/android/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=sync-preferences"><!-- + --><label id="fxaMobilePromo-android" + class="androidLink text-link"><!-- -->&mobilePromo2.androidLink;</label><!-- -->&mobilePromo2.iOSBefore;<!-- - --><label class="iOSLink text-link" - href="https://www.mozilla.org/firefox/ios/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=sync-preferences"><!-- + --><label id="fxaMobilePromo-ios" + class="iOSLink text-link"><!-- -->&mobilePromo2.iOSLink;</label><!-- -->&mobilePromo2.end; </label> diff --git a/browser/components/search/test/browser_addEngine.js b/browser/components/search/test/browser_addEngine.js index 44819b70df0fd12b5a5ae74b97df7921361fa811..b971ea5f78e3d91bdcf90ccb1cb9329d6dce6430 100644 --- a/browser/components/search/test/browser_addEngine.js +++ b/browser/components/search/test/browser_addEngine.js @@ -47,6 +47,8 @@ var gTests = [ searchForm: "http://mochi.test:8888/browser/browser/components/search/test/" }, run: function () { + Services.obs.addObserver(observer, "browser-search-engine-modified", false); + gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/testEngine.xml", null, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC", false); @@ -72,6 +74,12 @@ var gTests = [ gSS.removeEngine(engine); }, removed: function (engine) { + // Remove the observer before calling the currentEngine getter, + // as that getter will set the currentEngine to the original default + // which will trigger a notification causing the test to loop over all + // engines. + Services.obs.removeObserver(observer, "browser-search-engine-modified"); + let currentEngine = gSS.currentEngine; ok(currentEngine, "An engine is present."); isnot(currentEngine.name, this.engine.name, "Current engine reset after removal"); @@ -93,12 +101,5 @@ function nextTest() { function test() { waitForExplicitFinish(); - Services.obs.addObserver(observer, "browser-search-engine-modified", false); - registerCleanupFunction(cleanup); - nextTest(); } - -function cleanup() { - Services.obs.removeObserver(observer, "browser-search-engine-modified"); -} diff --git a/browser/components/uitour/UITour.jsm b/browser/components/uitour/UITour.jsm index c91a996daaa071643d64216b357a9d05a58bc1cd..724d31eeda8e2a539d75a2683d64eb288b5ada90 100644 --- a/browser/components/uitour/UITour.jsm +++ b/browser/components/uitour/UITour.jsm @@ -735,13 +735,12 @@ this.UITour = { } } - this.initForBrowser(browser); + this.initForBrowser(browser, window); return true; }, - initForBrowser(aBrowser) { - let window = aBrowser.ownerDocument.defaultView; + initForBrowser(aBrowser, window) { let gBrowser = window.gBrowser; if (gBrowser) { diff --git a/browser/extensions/loop/bootstrap.js b/browser/extensions/loop/bootstrap.js index e492af2261fd47a4bf8dfbcfb9b07a50973dd2ed..de59c0439513b382dffcf56921d23c6f7b43d2a2 100644 --- a/browser/extensions/loop/bootstrap.js +++ b/browser/extensions/loop/bootstrap.js @@ -11,6 +11,7 @@ const kPrefBrowserSharingInfoBar = "browserSharing.showInfoBar"; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/AppConstants.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"); @@ -39,7 +40,8 @@ var WindowListener = { var LoopUI = { /** * @var {XULWidgetSingleWrapper} toolbarButton Getter for the Loop toolbarbutton - * instance for this window. + * instance for this window. This should + * not be used in the hidden window. */ get toolbarButton() { delete this.toolbarButton; @@ -262,18 +264,10 @@ var WindowListener = { }, /** - * Triggers the initialization of the loop service. Called by - * delayedStartup. + * Triggers the initialization of the loop service if necessary. + * Also adds appropraite observers for the UI. */ init: function() { - // Cleanup when the window unloads. - window.addEventListener("unload", () => { - this.uninit(); - }); - - // Add observer notifications before the service is initialized - Services.obs.addObserver(this, "loop-status-changed", false); - // This is a promise for test purposes, but we don't want to be logging // expected errors to the console, so we catch them here. this.MozLoopService.initialize().catch(ex => { @@ -283,11 +277,21 @@ var WindowListener = { console.error(ex); } }); - this.updateToolbarState(); - }, - uninit: function() { - Services.obs.removeObserver(this, "loop-status-changed"); + // Don't do the rest if this is for the hidden window - we don't + // have a toolbar there. + if (window == Services.appShell.hiddenDOMWindow) { + return; + } + + // Cleanup when the window unloads. + window.addEventListener("unload", () => { + Services.obs.removeObserver(this, "loop-status-changed"); + }); + + Services.obs.addObserver(this, "loop-status-changed", false); + + this.updateToolbarState(); }, // Implements nsIObserver @@ -299,7 +303,8 @@ var WindowListener = { }, /** - * Updates the toolbar/menu-button state to reflect Loop status. + * Updates the toolbar/menu-button state to reflect Loop status. This should + * not be called from the hidden window. * * @param {string} [aReason] Some states are only shown if * a related reason is provided. @@ -351,7 +356,8 @@ var WindowListener = { }, /** - * Updates the tootltiptext to reflect Loop status. + * Updates the tootltiptext to reflect Loop status. This should not be called + * from the hidden window. * * @param {string} [mozL10nId] l10n ID that refelct the current * Loop status. @@ -758,15 +764,17 @@ function startup() { createLoopButton(); // Attach to hidden window (for OS X). - try { - WindowListener.setupBrowserUI(Services.appShell.hiddenDOMWindow); - } catch (ex) { - // Hidden window didn't exist, so wait until startup is done. - let topic = "browser-delayed-startup-finished"; - Services.obs.addObserver(function observer() { - Services.obs.removeObserver(observer, topic); + if (AppConstants.platform == "macosx") { + try { WindowListener.setupBrowserUI(Services.appShell.hiddenDOMWindow); - }, topic, false); + } catch (ex) { + // Hidden window didn't exist, so wait until startup is done. + let topic = "browser-delayed-startup-finished"; + Services.obs.addObserver(function observer() { + Services.obs.removeObserver(observer, topic); + WindowListener.setupBrowserUI(Services.appShell.hiddenDOMWindow); + }, topic, false); + } } // Attach to existing browser windows, for modifying UI. @@ -783,8 +791,12 @@ function startup() { // Load our stylesheets. let styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"] .getService(Components.interfaces.nsIStyleSheetService); - let sheets = ["chrome://loop-shared/skin/loop.css", - "chrome://loop/skin/platform.css"]; + let sheets = ["chrome://loop-shared/skin/loop.css"]; + + if (AppConstants.platform != "linux") { + sheets.push("chrome://loop/skin/platform.css"); + } + for (let sheet of sheets) { let styleSheetURI = Services.io.newURI(sheet, null, null); // XXX We would love to specify AUTHOR_SHEET here and in shutdown, however @@ -809,7 +821,9 @@ function shutdown() { }); // Detach from hidden window (for OS X). - WindowListener.tearDownBrowserUI(Services.appShell.hiddenDOMWindow); + if (AppConstants.platform == "macosx") { + WindowListener.tearDownBrowserUI(Services.appShell.hiddenDOMWindow); + } // Detach from browser windows. let wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator); diff --git a/browser/locales/en-US/chrome/browser/aboutSyncTabs.dtd b/browser/locales/en-US/chrome/browser/aboutSyncTabs.dtd index 5865c1231751bf999f3145c7c0f8d301780fbd68..fad491bbdbb7333d6f8c42f641ac89fed0fb8194 100644 --- a/browser/locales/en-US/chrome/browser/aboutSyncTabs.dtd +++ b/browser/locales/en-US/chrome/browser/aboutSyncTabs.dtd @@ -2,7 +2,6 @@ - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -<!-- LOCALIZATION NOTE (tabs.otherDevices.label): Keep this in sync with syncTabsMenu2.label from browser.dtd --> <!ENTITY tabs.otherDevices.label "Tabs From Other Devices"> <!ENTITY tabs.searchText.label "Type here to find tabs…"> diff --git a/browser/locales/en-US/chrome/browser/browser.dtd b/browser/locales/en-US/chrome/browser/browser.dtd index db8fa663ae9f365a67704744907da139ec5420c0..2b50e687fa7378004f83e87f6381fdffc2075a3b 100644 --- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -410,6 +410,22 @@ These should match what Safari and other Apple applications use on OS X Lion. -- <!ENTITY appMenuHistory.viewSidebar.label "View History Sidebar"> <!ENTITY appMenuHelp.tooltip "Open Help Menu"> +<!ENTITY appMenuRemoteTabs.label "Synced Tabs"> +<!ENTITY appMenuRemoteTabs.fetching.label "Fetching Synced Tabs…"> +<!-- LOCALIZATION NOTE (appMenuRemoteTabs.notabs.label): This is shown beneath + the name of a device when that device has no open tabs --> +<!ENTITY appMenuRemoteTabs.notabs.label "No open tabs"> +<!-- LOCALIZATION NOTE (appMenuRemoteTabs.tabsnotsyncing.label): This is shown + when Sync is configured but syncing tabs is disabled. --> +<!ENTITY appMenuRemoteTabs.tabsnotsyncing.label "Turn on tab syncing to view a list of tabs from your other devices."> +<!-- LOCALIZATION NOTE (appMenuRemoteTabs.noclients.label): This is shown + when Sync is configured but this appears to be the only device attached to + the account. We also show links to download Firefox for android/ios. --> +<!ENTITY appMenuRemoteTabs.noclients.label "Sign in to Firefox from your other devices to view their tabs here."> +<!ENTITY appMenuRemoteTabs.openprefs.label "Sync Preferences"> +<!ENTITY appMenuRemoteTabs.notsignedin.label "Sign in to view a list of tabs from your other devices."> +<!ENTITY appMenuRemoteTabs.signin.label "Sign in to Sync"> + <!ENTITY customizeMenu.addToToolbar.label "Add to Toolbar"> <!ENTITY customizeMenu.addToToolbar.accesskey "A"> <!ENTITY customizeMenu.addToPanel.label "Add to Menu"> @@ -771,8 +787,8 @@ you can use these alternative items. Otherwise, their values should be empty. - The word "toolbar" is appended automatically and should not be contained below! --> <!ENTITY tabsToolbar.label "Browser tabs"> -<!-- LOCALIZATION NOTE (syncTabsMenu2.label): This appears in the history menu --> -<!ENTITY syncTabsMenu2.label "Tabs From Other Devices"> +<!-- LOCALIZATION NOTE (syncTabsMenu3.label): This appears in the history menu and history panel --> +<!ENTITY syncTabsMenu3.label "Synced Tabs"> <!ENTITY syncBrand.shortName.label "Sync"> diff --git a/browser/locales/en-US/chrome/browser/browser.properties b/browser/locales/en-US/chrome/browser/browser.properties index b4a61eb82d5116aad8ee58c2ccea61d510fccd22..2add6fcf859de10e4698cf6ec1c0b0fad758e34c 100644 --- a/browser/locales/en-US/chrome/browser/browser.properties +++ b/browser/locales/en-US/chrome/browser/browser.properties @@ -728,6 +728,17 @@ appmenu.downloadUpdateButton.label = Download Update readingList.promo.firstUse.readerView.title = Reader View readingList.promo.firstUse.readerView.body = Remove clutter so you can focus exactly on what you want to read. +# LOCALIZATION NOTE (appMenuRemoteTabs.mobilePromo): +# %1$S will be replaced with a link, the text of which is +# appMenuRemoteTabs.mobilePromo.android and the link will be to +# https://www.mozilla.org/firefox/android/. +# %2$S will be replaced with a link, the text of which is +# appMenuRemoteTabs.mobilePromo.ios +# and the link will be to https://www.mozilla.org/firefox/ios/. +appMenuRemoteTabs.mobilePromo = Get %1$S or %2$S. +appMenuRemoteTabs.mobilePromo.android = Firefox for Android +appMenuRemoteTabs.mobilePromo.ios = Firefox for iOS + # LOCALIZATION NOTE (e10s.offerPopup.mainMessage # e10s.offerPopup.highlight1 # e10s.offerPopup.highlight2 diff --git a/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties b/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties index b2b74d3985b8f7f5c5951ff43484da61b820313d..d5f8d2efdc1be19144e665c5cf09dc3d19ba5367 100644 --- a/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties +++ b/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties @@ -6,6 +6,9 @@ history-panelmenu.label = History # LOCALIZATION NOTE(history-panelmenu.tooltiptext2): %S is the keyboard shortcut history-panelmenu.tooltiptext2 = Show your history (%S) +remotetabs-panelmenu.label = Synced Tabs +remotetabs-panelmenu.tooltiptext = Show your synced tabs from other devices + privatebrowsing-button.label = New Private Window # LOCALIZATION NOTE(privatebrowsing-button.tooltiptext): %S is the keyboard shortcut privatebrowsing-button.tooltiptext = Open a new Private Browsing window (%S) diff --git a/browser/modules/TabGroupsMigrator.jsm b/browser/modules/TabGroupsMigrator.jsm index 2145e33be7d0364dba686f508d66e4fbb9deb92f..cf931b6039b2b636eedb370f2f13125379093892 100644 --- a/browser/modules/TabGroupsMigrator.jsm +++ b/browser/modules/TabGroupsMigrator.jsm @@ -162,7 +162,7 @@ this.TabGroupsMigrator = { let groupFolder = yield PlacesUtils.bookmarks.insert({ parentGuid: tabgroupsFolder.guid, type: PlacesUtils.bookmarks.TYPE_FOLDER, - title: group.title || + title: group.tabGroupsMigrationTitle || gBrowserBundle.formatStringFromName("tabgroups.migration.anonGroup", [group.anonGroupID], 1), }).catch(Cu.reportError); diff --git a/browser/modules/test/browser_SelfSupportBackend.js b/browser/modules/test/browser_SelfSupportBackend.js index 86eed34d81c91353d42b9051d29d439f4a046d3d..56f27320f88bfe7c0ea9ea2632a7bd75b6712181 100644 --- a/browser/modules/test/browser_SelfSupportBackend.js +++ b/browser/modules/test/browser_SelfSupportBackend.js @@ -146,6 +146,29 @@ add_task(function* test_selfSupport() { uitourAPI.ping(resolve); }); yield pingPromise; + info("Ping succeeded"); + + let observePromise = ContentTask.spawn(selfSupportBrowser, null, function* checkObserve() { + yield new Promise(resolve => { + let win = Cu.waiveXrays(content); + win.Mozilla.UITour.observe((event, data) => { + if (event != "Heartbeat:Engaged") { + return; + } + is(data.flowId, "myFlowID", "Check flowId"); + ok(!!data.timestamp, "Check timestamp"); + resolve(data); + }, () => {}); + }); + }); + + info("Notifying Heartbeat:Engaged"); + UITour.notify("Heartbeat:Engaged", { + flowId: "myFlowID", + timestamp: Date.now(), + }); + yield observePromise; + info("Observed in the hidden frame"); // Close SelfSupport from content. contentWindow.close(); diff --git a/browser/modules/test/xpcshell/test_TabGroupsMigrator.js b/browser/modules/test/xpcshell/test_TabGroupsMigrator.js index 5ecdd809a7e233e7c17170e934bd5872e7d02cba..b19124e4952bc5ed1b7423b10c5d4a9a08c4a612 100644 --- a/browser/modules/test/xpcshell/test_TabGroupsMigrator.js +++ b/browser/modules/test/xpcshell/test_TabGroupsMigrator.js @@ -78,7 +78,9 @@ add_task(function* gatherGroupDataTest() { }); add_task(function* bookmarkingTest() { - let groupInfo = TabGroupsMigrator._gatherGroupData(TEST_STATES.TWO_GROUPS); + let stateClone = JSON.parse(JSON.stringify(TEST_STATES.TWO_GROUPS)); + let groupInfo = TabGroupsMigrator._gatherGroupData(stateClone); + let removedGroups = TabGroupsMigrator._removeHiddenTabGroupsFromState(stateClone, groupInfo); yield TabGroupsMigrator._bookmarkAllGroupsFromState(groupInfo); let bmCounter = 0; let bmParents = {}; diff --git a/browser/themes/linux/Toolbar-inverted.png b/browser/themes/linux/Toolbar-inverted.png index e2ee5784cbce245db606ff5e0e94f4ab679f3316..99d7481b11663a22b8b2450d607f23d2cfb5f5ef 100644 Binary files a/browser/themes/linux/Toolbar-inverted.png and b/browser/themes/linux/Toolbar-inverted.png differ diff --git a/browser/themes/linux/Toolbar.png b/browser/themes/linux/Toolbar.png index d05345d2ff57dbf16a0b56bc0ff6bb95656e1bf9..b8151d18ff4a35677c8b35b777153e49bfa04337 100644 Binary files a/browser/themes/linux/Toolbar.png and b/browser/themes/linux/Toolbar.png differ diff --git a/browser/themes/linux/menuPanel.png b/browser/themes/linux/menuPanel.png index ccdc35b8852c0a128f7aba974d88281ed375356e..f8c7f889d60764cbbc754725a09cedac799e3d40 100644 Binary files a/browser/themes/linux/menuPanel.png and b/browser/themes/linux/menuPanel.png differ diff --git a/browser/themes/linux/menuPanel@2x.png b/browser/themes/linux/menuPanel@2x.png index d1d52568bcafa3125a44008846847017dedd6a8b..28176a120df7ca58f9bab9318af8ab891d6523a7 100644 Binary files a/browser/themes/linux/menuPanel@2x.png and b/browser/themes/linux/menuPanel@2x.png differ diff --git a/browser/themes/osx/Toolbar-inverted.png b/browser/themes/osx/Toolbar-inverted.png index 0b737e4793a2e2864889e96a49173a9a98664c78..33589c8cef5755419b28e154a5fc197c172e2413 100644 Binary files a/browser/themes/osx/Toolbar-inverted.png and b/browser/themes/osx/Toolbar-inverted.png differ diff --git a/browser/themes/osx/Toolbar-inverted@2x.png b/browser/themes/osx/Toolbar-inverted@2x.png index 2a484fae9dbdf717b9983590100010714466dd88..25fadbdf41d8808d398f0b318628f669fd050478 100644 Binary files a/browser/themes/osx/Toolbar-inverted@2x.png and b/browser/themes/osx/Toolbar-inverted@2x.png differ diff --git a/browser/themes/osx/Toolbar-yosemite.png b/browser/themes/osx/Toolbar-yosemite.png index f3fa39fcc7e37f29b6c21e206b6d4fdde1621593..95bd2d84bc9d0a4daab92aaf122295a280df76e8 100644 Binary files a/browser/themes/osx/Toolbar-yosemite.png and b/browser/themes/osx/Toolbar-yosemite.png differ diff --git a/browser/themes/osx/Toolbar-yosemite@2x.png b/browser/themes/osx/Toolbar-yosemite@2x.png index 385e9549a928d4278fe91ef07ca2c93bbe40e87f..c99976fd6f7f6662dc99a03d67b60373f7688eec 100644 Binary files a/browser/themes/osx/Toolbar-yosemite@2x.png and b/browser/themes/osx/Toolbar-yosemite@2x.png differ diff --git a/browser/themes/osx/Toolbar.png b/browser/themes/osx/Toolbar.png index 3470909258c684584a9528f75363ba0feaac66f6..73ad770926170cce3ea2a8eae46f9a042fe74333 100644 Binary files a/browser/themes/osx/Toolbar.png and b/browser/themes/osx/Toolbar.png differ diff --git a/browser/themes/osx/Toolbar@2x.png b/browser/themes/osx/Toolbar@2x.png index 83dfdeba1a78e16f50fe76637fb1012fbd83ba70..597b9846abb300c18e24c7a0113140cea85c9b06 100644 Binary files a/browser/themes/osx/Toolbar@2x.png and b/browser/themes/osx/Toolbar@2x.png differ diff --git a/browser/themes/osx/browser.css b/browser/themes/osx/browser.css index 46b10f1794a70550d8b65bdcf0e28f87ed989a89..57caf9f5eed55d330b65d04a117cf61380aa3d4f 100644 --- a/browser/themes/osx/browser.css +++ b/browser/themes/osx/browser.css @@ -811,8 +811,8 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic -moz-image-region: rect(18px, 252px, 36px, 234px); } - #sync-button@toolbarButtonPressed@:not([status="active"]) { - -moz-image-region: rect(18px, 270px, 36px, 252px); + #sync-button@toolbarButtonPressed@ { + -moz-image-region: rect(18px, 792px, 36px, 774px); } #feed-button@toolbarButtonPressed@ { @@ -974,8 +974,8 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic -moz-image-region: rect(36px, 504px, 72px, 468px); } - #sync-button@toolbarButtonPressed@:not([status="active"]) { - -moz-image-region: rect(36px, 540px, 72px, 504px); + #sync-button@toolbarButtonPressed@ { + -moz-image-region: rect(36px, 1584px, 72px, 1548px); } #feed-button@toolbarButtonPressed@ { diff --git a/browser/themes/osx/menuPanel-yosemite.png b/browser/themes/osx/menuPanel-yosemite.png index d402e07214e593400735aba7552069a029b40ed4..74e4ca860cac9bdba3f725fb8a0cd7b8a4876544 100644 Binary files a/browser/themes/osx/menuPanel-yosemite.png and b/browser/themes/osx/menuPanel-yosemite.png differ diff --git a/browser/themes/osx/menuPanel-yosemite@2x.png b/browser/themes/osx/menuPanel-yosemite@2x.png index 4b6a1b2b2c6b558fc12b88154fee58e764eb2650..072f6030ffb0786d18f4e266c1a4ba68e5e72bf6 100644 Binary files a/browser/themes/osx/menuPanel-yosemite@2x.png and b/browser/themes/osx/menuPanel-yosemite@2x.png differ diff --git a/browser/themes/osx/menuPanel.png b/browser/themes/osx/menuPanel.png index 768988e157f673acfd9fc4e6dd64ca08a0d66f0d..d7e74f3a11040fdef1ecd81d6de9dcdd1856a48c 100644 Binary files a/browser/themes/osx/menuPanel.png and b/browser/themes/osx/menuPanel.png differ diff --git a/browser/themes/osx/menuPanel@2x.png b/browser/themes/osx/menuPanel@2x.png index efeb482cd230acffe6b40a920aaaa1fc7d1f2524..bff0b13c4abbb8788f54976752081360c2c8f3d4 100644 Binary files a/browser/themes/osx/menuPanel@2x.png and b/browser/themes/osx/menuPanel@2x.png differ diff --git a/browser/themes/shared/customizableui/panelUIOverlay.inc.css b/browser/themes/shared/customizableui/panelUIOverlay.inc.css index 4cec72c2937abf8093c06d9bbb00fea87b8c3b2f..bf1869d0ecfc71f46984c1918a3b3eb94b5f5040 100644 --- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css +++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css @@ -634,6 +634,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton { #PanelUI-update-status > .toolbarbutton-icon, #PanelUI-fxa-label > .toolbarbutton-icon, #PanelUI-fxa-icon > .toolbarbutton-icon, +#PanelUI-remotetabs-syncnow > .toolbarbutton-icon, #PanelUI-customize > .toolbarbutton-icon, #PanelUI-help > .toolbarbutton-icon, #PanelUI-quit > .toolbarbutton-icon { @@ -667,12 +668,92 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton { list-style-image: url(chrome://branding/content/icon16.png); } +#PanelUI-remotetabs-syncnow, #PanelUI-fxa-label, #PanelUI-fxa-icon { list-style-image: url(chrome://browser/skin/sync-horizontalbar.png); } -#PanelUI-footer-fxa[syncstatus="active"] > #PanelUI-fxa-icon { +.PanelUI-remotetabs-instruction-label, +#PanelUI-remotetabs-mobile-promo { + margin: 15px; + text-align: center; + text-shadow: none; + max-width: 15em; + color: GrayText; +} + +/* The boxes with "instructions" get extra padding for space around the + illustration and buttons */ +.PanelUI-remotetabs-instruction-box { + padding: 30px 15px 15px 15px; +} + +.PanelUI-remotetabs-prefs-button { + -moz-appearance: none; + background-color: #0096dd; + color: white; + border-radius: 2px; + margin: 10px; + padding: 8px; + text-shadow: none; + min-width: 200px; +} + +.PanelUI-remotetabs-prefs-button:hover, +.PanelUI-remotetabs-prefs-button:hover:active { + background-color: #018acb; +} + +.remotetabs-promo-link { + margin: 0; +} + +.PanelUI-remotetabs-notabsforclient-label { + color: GrayText; + /* This margin is to line this label up with the labels in toolbarbuttons. */ + margin-left: 28px; +} + +.fxaSyncIllustration { + width: 180px; + list-style-image: url(chrome://browser/skin/fxa/sync-illustration.svg); +} + +.PanelUI-remotetabs-prefs-button > .toolbarbutton-text { + /* !important to override ".cui-widget-panel toolbarbutton > .toolbarbutton-text" above. */ + text-align: center !important; + text-shadow: none; +} + +#PanelUI-remotetabs[mainview] { /* panel anchored to toolbar button might be too skinny */ + min-width: 19em; +} + +/* Work around bug 1224412 - these boxes will cause scrollbars to appear when + the panel is anchored to a toolbar button. +*/ +#PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync, +#PanelUI-remotetabs[mainview] #PanelUI-remotetabs-reauthsync, +#PanelUI-remotetabs[mainview] #PanelUI-remotetabs-nodevicespane, +#PanelUI-remotetabs[mainview] #PanelUI-remotetabs-tabsdisabledpane { + min-height: 30em; +} + +#PanelUI-remotetabs-tabslist > label[itemtype="client"] { + color: GrayText; +} + +/* Collapse the non-active vboxes in the remotetabs deck to use only the + height the active box needs */ +#PanelUI-remotetabs-deck:not([selectedIndex="1"]) > #PanelUI-remotetabs-tabsdisabledpane, +#PanelUI-remotetabs-deck:not([selectedIndex="2"]) > #PanelUI-remotetabs-fetching, +#PanelUI-remotetabs-deck:not([selectedIndex="3"]) > #PanelUI-remotetabs-nodevicespane { + visibility: collapse; +} + +#PanelUI-remotetabs-syncnow[syncstatus="active"], +#PanelUI-fxa-icon[syncstatus="active"] { list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar.png); } @@ -701,6 +782,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton { #PanelUI-fxa-label, #PanelUI-fxa-icon, +#PanelUI-remotetabs-syncnow, #PanelUI-customize, #PanelUI-help, #PanelUI-quit { @@ -1070,10 +1152,19 @@ menuitem.panel-subview-footer@menuStateActive@, color: GrayText; } +#PanelUI-remotetabs-tabslist > toolbarbutton, #PanelUI-historyItems > toolbarbutton { list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png"); } +@media (min-resolution: 1.1dppx) { + #PanelUI-remotetabs-tabslist > toolbarbutton, + #PanelUI-historyItems > toolbarbutton { + list-style-image: url("chrome://mozapps/skin/places/defaultFavicon@2x.png"); + } +} + +#PanelUI-remotetabs-tabslist > toolbarbutton > .toolbarbutton-icon, #PanelUI-recentlyClosedWindows > toolbarbutton > .toolbarbutton-icon, #PanelUI-recentlyClosedTabs > toolbarbutton > .toolbarbutton-icon, #PanelUI-historyItems > toolbarbutton > .toolbarbutton-icon { @@ -1531,11 +1622,13 @@ menuitem[checked="true"].subviewbutton > .menu-iconic-left { } #PanelUI-fxa-label, + #PanelUI-remotetabs-syncnow, #PanelUI-fxa-icon { list-style-image: url(chrome://browser/skin/sync-horizontalbar@2x.png); } - #PanelUI-footer-fxa[syncstatus="active"] > #PanelUI-fxa-icon { + #PanelUI-remotetabs-syncnow[syncstatus="active"], + #PanelUI-fxa-icon[syncstatus="active"] { list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar@2x.png); } @@ -1557,6 +1650,7 @@ menuitem[checked="true"].subviewbutton > .menu-iconic-left { #PanelUI-fxa-label, #PanelUI-fxa-icon, + #PanelUI-remotetabs-syncnow, #PanelUI-customize, #PanelUI-help, #PanelUI-quit { @@ -1566,6 +1660,7 @@ menuitem[checked="true"].subviewbutton > .menu-iconic-left { #PanelUI-update-status > .toolbarbutton-icon, #PanelUI-fxa-label > .toolbarbutton-icon, #PanelUI-fxa-icon > .toolbarbutton-icon, + #PanelUI-remotetabs-syncnow > .toolbarbutton-icon, #PanelUI-customize > .toolbarbutton-icon, #PanelUI-help > .toolbarbutton-icon, #PanelUI-quit > .toolbarbutton-icon { diff --git a/browser/themes/shared/fxa/sync-illustration.svg b/browser/themes/shared/fxa/sync-illustration.svg new file mode 100644 index 0000000000000000000000000000000000000000..dd2b7bde68e52aeb6911cfeee24c1f387c86ed45 --- /dev/null +++ b/browser/themes/shared/fxa/sync-illustration.svg @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" width="320" height="280" viewBox="0 0 320 280"> + <g fill="#cdcdcd"> + <path d="M46.352,148.919 L46.352,148.919 L44.938,150.333 L43.523,148.919 L43.523,148.919 L37.866,143.262 L39.281,141.848 L44.938,147.505 L50.594,141.848 L52.009,143.262 L46.352,148.919 ZM43.937,134.000 L45.938,134.000 L45.938,142.000 L43.937,142.000 L43.937,134.000 ZM43.937,122.000 L45.938,122.000 L45.938,130.000 L43.937,130.000 L43.937,122.000 Z"/> + <path d="M306.641,132.110 L300.984,126.453 L295.328,132.110 L293.913,130.696 L300.984,123.625 L308.055,130.696 L306.641,132.110 ZM302.000,223.969 L300.000,223.969 L300.000,215.969 L302.000,215.969 L302.000,223.969 ZM302.000,211.969 L300.000,211.969 L300.000,203.969 L302.000,203.969 L302.000,211.969 ZM302.000,199.969 L300.000,199.969 L300.000,191.969 L302.000,191.969 L302.000,199.969 ZM302.000,187.969 L300.000,187.969 L300.000,179.969 L302.000,179.969 L302.000,187.969 ZM302.000,175.969 L300.000,175.969 L300.000,167.969 L302.000,167.969 L302.000,175.969 ZM302.000,163.969 L300.000,163.969 L300.000,155.969 L302.000,155.969 L302.000,163.969 ZM300.000,131.969 L302.000,131.969 L302.000,139.969 L300.000,139.969 L300.000,131.969 ZM302.000,151.969 L300.000,151.969 L300.000,143.969 L302.000,143.969 L302.000,151.969 ZM300.000,227.969 L302.000,227.969 L302.000,232.000 L302.000,234.000 L300.000,234.000 L292.000,234.000 L292.000,232.000 L300.000,232.000 L300.000,227.969 Z"/> + <path d="M101.335,236.009 L99.921,234.594 L105.578,228.938 L99.921,223.281 L101.335,221.866 L108.406,228.938 L101.335,236.009 ZM100.000,229.938 L92.000,229.938 L92.000,227.937 L100.000,227.937 L100.000,229.938 ZM80.000,227.937 L88.000,227.937 L88.000,229.938 L80.000,229.938 L80.000,227.937 Z"/> + <path d="M182.000,54.000 L182.000,52.000 L190.000,52.000 L190.000,54.000 L182.000,54.000 ZM170.000,52.000 L178.000,52.000 L178.000,54.000 L170.000,54.000 L170.000,52.000 ZM168.488,60.071 L161.417,53.000 L168.488,45.929 L169.902,47.343 L164.245,53.000 L169.902,58.657 L168.488,60.071 Z"/> + <path d="M297.688,276.000 L102.312,276.000 C97.721,276.000 94.000,272.279 94.000,267.688 L94.000,260.000 L306.000,260.000 L306.000,267.688 C306.000,272.279 302.279,276.000 297.688,276.000 ZM117.906,150.312 C117.906,145.721 121.628,142.000 126.218,142.000 L273.688,142.000 C278.279,142.000 282.000,145.721 282.000,150.312 L282.000,256.000 L117.906,256.000 L117.906,150.312 ZM132.000,242.000 L270.000,242.000 L270.000,156.000 L132.000,156.000 L132.000,242.000 Z"/> + <path d="M307.074,115.969 L206.926,115.969 C203.101,115.969 200.000,112.868 200.000,109.042 L200.000,38.926 C200.000,35.101 203.101,32.000 206.926,32.000 L307.074,32.000 C310.899,32.000 314.000,35.101 314.000,38.926 L314.000,109.042 C314.000,112.868 310.899,115.969 307.074,115.969 ZM210.000,65.875 C210.000,64.770 209.105,63.875 208.000,63.875 C206.895,63.875 206.000,64.770 206.000,65.875 L206.000,82.000 C206.000,83.105 206.895,84.000 208.000,84.000 C209.105,84.000 210.000,83.105 210.000,82.000 L210.000,65.875 ZM302.000,42.000 L216.000,42.000 L216.000,106.000 L302.000,106.000 L302.000,42.000 Z"/> + <path d="M65.844,240.000 L26.156,240.000 C23.861,240.000 22.000,238.139 22.000,235.844 L22.000,162.156 C22.000,159.861 23.861,158.000 26.156,158.000 L65.844,158.000 C68.139,158.000 70.000,159.861 70.000,162.156 L70.000,235.844 C70.000,238.139 68.139,240.000 65.844,240.000 ZM46.000,236.000 C48.287,236.000 50.141,234.195 50.141,231.969 C50.141,229.742 48.287,227.938 46.000,227.938 C43.713,227.938 41.859,229.742 41.859,231.969 C41.859,234.195 43.713,236.000 46.000,236.000 ZM66.000,168.000 L26.000,168.000 L26.000,224.000 L66.000,224.000 L66.000,168.000 Z"/> + <path d="M171.906,86.156 C171.906,102.329 159.026,115.469 143.017,115.797 L143.039,115.955 L28.850,115.955 L28.869,115.797 C12.872,115.475 -0.000,102.333 -0.000,86.156 C-0.000,71.661 10.336,59.603 23.994,57.019 C23.620,55.457 23.401,53.834 23.401,52.156 C23.401,40.714 32.606,31.438 43.962,31.438 C47.561,31.438 50.941,32.375 53.884,34.012 C53.883,33.930 53.878,33.848 53.878,33.766 C53.878,17.137 67.301,3.656 83.858,3.656 C97.763,3.656 109.453,13.164 112.843,26.059 C116.677,23.334 121.343,21.719 126.393,21.719 C139.394,21.719 149.933,32.331 149.933,45.422 C149.933,49.572 148.868,53.468 147.007,56.861 C161.114,59.082 171.906,71.351 171.906,86.156 Z"/> + </g> +</svg> diff --git a/browser/themes/shared/jar.inc.mn b/browser/themes/shared/jar.inc.mn index 35dcc112c00b1fede32b690bef52ba9921bf2906..9bd06352c8e345bc077978b9042e650bb70dd659 100644 --- a/browser/themes/shared/jar.inc.mn +++ b/browser/themes/shared/jar.inc.mn @@ -79,6 +79,7 @@ skin/classic/browser/fxa/logo@2x.png (../shared/fxa/logo@2x.png) skin/classic/browser/fxa/sync-illustration.png (../shared/fxa/sync-illustration.png) skin/classic/browser/fxa/sync-illustration@2x.png (../shared/fxa/sync-illustration@2x.png) + skin/classic/browser/fxa/sync-illustration.svg (../shared/fxa/sync-illustration.svg) skin/classic/browser/fxa/android.png (../shared/fxa/android.png) skin/classic/browser/fxa/android@2x.png (../shared/fxa/android@2x.png) skin/classic/browser/search-pref.png (../shared/search/search-pref.png) diff --git a/browser/themes/shared/menupanel.inc.css b/browser/themes/shared/menupanel.inc.css index 3fe8f4f9cadb64038cab9c2b23feaf8d36aa93d8..47b670e8bf178cb8c4b9632c5bb51a4b4e044189 100644 --- a/browser/themes/shared/menupanel.inc.css +++ b/browser/themes/shared/menupanel.inc.css @@ -60,12 +60,7 @@ #sync-button[cui-areatype="menu-panel"], toolbarpaletteitem[place="palette"] > #sync-button { - -moz-image-region: rect(0px, 384px, 32px, 352px); - } - - #sync-button[cui-areatype="menu-panel"][status="active"] { - list-style-image: url(chrome://browser/skin/syncProgress-menuPanel.png); - -moz-image-region: rect(0px, 32px, 32px, 0px); + -moz-image-region: rect(0px, 1024px, 32px, 992px); } #feed-button[cui-areatype="menu-panel"], @@ -279,12 +274,7 @@ #sync-button[cui-areatype="menu-panel"], toolbarpaletteitem[place="palette"] > #sync-button { - -moz-image-region: rect(0px, 768px, 64px, 704px); - } - - #sync-button[cui-areatype="menu-panel"][status="active"] { - list-style-image: url(chrome://browser/skin/syncProgress-menuPanel@2x.png); - -moz-image-region: rect(0px, 64px, 64px, 0px); + -moz-image-region: rect(0px, 2048px, 64px, 1984px); } #feed-button[cui-areatype="menu-panel"], diff --git a/browser/themes/shared/toolbarbuttons.inc.css b/browser/themes/shared/toolbarbuttons.inc.css index 34fe448fbc3e29000e32c7c38302a35b8531cce2..1e834d9e3e87e5ee0ea0cb4d567c78350f350821 100644 --- a/browser/themes/shared/toolbarbuttons.inc.css +++ b/browser/themes/shared/toolbarbuttons.inc.css @@ -53,16 +53,7 @@ toolbar[brighttext] #bookmarks-menu-button > .toolbarbutton-menubutton-dropmarke } #sync-button[cui-areatype="toolbar"] { - -moz-image-region: rect(0, 270px, 18px, 252px); -} - -#sync-button[cui-areatype="toolbar"][status="active"] { - list-style-image: url("chrome://browser/skin/syncProgress-toolbar.png"); - -moz-image-region: rect(0, 18px, 18px, 0px); -} - -toolbar[brighttext] #sync-button[status="active"] { - list-style-image: url("chrome://browser/skin/syncProgress-toolbar-inverted.png"); + -moz-image-region: rect(0, 792px, 18px, 774px); } #feed-button[cui-areatype="toolbar"] { @@ -253,16 +244,7 @@ toolbar[brighttext] #sync-button[status="active"] { } #sync-button[cui-areatype="toolbar"] { - -moz-image-region: rect(0, 540px, 36px, 504px); - } - - #sync-button[cui-areatype="toolbar"][status="active"] { - list-style-image: url("chrome://browser/skin/syncProgress-toolbar@2x.png"); - -moz-image-region: rect(0, 36px, 36px, 0px); - } - - toolbar[brighttext] #sync-button[cui-areatype="toolbar"][status="active"] { - list-style-image: url("chrome://browser/skin/syncProgress-toolbar-inverted@2x.png"); + -moz-image-region: rect(0, 1584px, 36px, 1548px); } #feed-button[cui-areatype="toolbar"] { diff --git a/browser/themes/windows/Toolbar-XP.png b/browser/themes/windows/Toolbar-XP.png index 299cb223bced7dcf8d7eaeff6a1a0866a975dcfd..0059e7eac01c244c0e695ea1134904cdf0ffea14 100644 Binary files a/browser/themes/windows/Toolbar-XP.png and b/browser/themes/windows/Toolbar-XP.png differ diff --git a/browser/themes/windows/Toolbar-aero.png b/browser/themes/windows/Toolbar-aero.png index a8d306265656688568e04c94c79785acd650fafe..ebf7db8bf7eba35cce8582426213c4a973f27f88 100644 Binary files a/browser/themes/windows/Toolbar-aero.png and b/browser/themes/windows/Toolbar-aero.png differ diff --git a/browser/themes/windows/Toolbar-aero@2x.png b/browser/themes/windows/Toolbar-aero@2x.png index 53ae2cc4c7147379e827485c21a121198c0a21ed..f8d7553ccb561fe7203678083b0ec059f80b133a 100644 Binary files a/browser/themes/windows/Toolbar-aero@2x.png and b/browser/themes/windows/Toolbar-aero@2x.png differ diff --git a/browser/themes/windows/Toolbar-inverted.png b/browser/themes/windows/Toolbar-inverted.png index e2ee5784cbce245db606ff5e0e94f4ab679f3316..d2b86b38fb18a687965d3849bdcefbd95a8810ea 100644 Binary files a/browser/themes/windows/Toolbar-inverted.png and b/browser/themes/windows/Toolbar-inverted.png differ diff --git a/browser/themes/windows/Toolbar-inverted@2x.png b/browser/themes/windows/Toolbar-inverted@2x.png index 586d4894e76dc55ecc8ef621d7ef5d8e28444b38..d3bb1c89d5ae8a6c13a2b8d6b396fc612ac6a508 100644 Binary files a/browser/themes/windows/Toolbar-inverted@2x.png and b/browser/themes/windows/Toolbar-inverted@2x.png differ diff --git a/browser/themes/windows/Toolbar-lunaSilver.png b/browser/themes/windows/Toolbar-lunaSilver.png index 5a51063a9a7bc3feedb14fa084a410d010ece43a..fd61576f1bb9eab61b2273cb31c11531f76eecf4 100644 Binary files a/browser/themes/windows/Toolbar-lunaSilver.png and b/browser/themes/windows/Toolbar-lunaSilver.png differ diff --git a/browser/themes/windows/Toolbar-win8.png b/browser/themes/windows/Toolbar-win8.png index 50a5df60c1ba04a6a05ad6d1c6faba4d4a537a99..1d6c2545f6957ba02c9998bb00c241c2d8cbf10b 100644 Binary files a/browser/themes/windows/Toolbar-win8.png and b/browser/themes/windows/Toolbar-win8.png differ diff --git a/browser/themes/windows/Toolbar-win8@2x.png b/browser/themes/windows/Toolbar-win8@2x.png index 8da9af263ec6e985608d22b30d1ff9dd2b5d880b..4f5894e453adb560cbcea76e76e683fa4de28d72 100644 Binary files a/browser/themes/windows/Toolbar-win8@2x.png and b/browser/themes/windows/Toolbar-win8@2x.png differ diff --git a/browser/themes/windows/Toolbar.png b/browser/themes/windows/Toolbar.png index d01a85e3f2cc1ba2847af79dcfc880bb1d6154e9..6f7c57c10dbb04601bce32be57f9725355a47235 100644 Binary files a/browser/themes/windows/Toolbar.png and b/browser/themes/windows/Toolbar.png differ diff --git a/browser/themes/windows/Toolbar@2x.png b/browser/themes/windows/Toolbar@2x.png index 4404a628bca5954a4f5fae6391fdf2b9803cac6a..9b5a282124b398a7f89872caf0edba647ccc32cc 100644 Binary files a/browser/themes/windows/Toolbar@2x.png and b/browser/themes/windows/Toolbar@2x.png differ diff --git a/browser/themes/windows/menuPanel-aero.png b/browser/themes/windows/menuPanel-aero.png index 7b3efcaed1141dd090b6ebf996513e11d12a690c..9058f6691178add90c416065610809acc91395c9 100644 Binary files a/browser/themes/windows/menuPanel-aero.png and b/browser/themes/windows/menuPanel-aero.png differ diff --git a/browser/themes/windows/menuPanel-aero@2x.png b/browser/themes/windows/menuPanel-aero@2x.png index c310dac494d79c6c1478449bc00175400ac69025..72f315c0f06285dd56e794911be43c92cb2cb219 100644 Binary files a/browser/themes/windows/menuPanel-aero@2x.png and b/browser/themes/windows/menuPanel-aero@2x.png differ diff --git a/browser/themes/windows/menuPanel.png b/browser/themes/windows/menuPanel.png index ed65b4fd61cbb0e2cb1e175cff861d7329b26786..cfc37702e9357572b56fa0546598cf7dee8e6f27 100644 Binary files a/browser/themes/windows/menuPanel.png and b/browser/themes/windows/menuPanel.png differ diff --git a/browser/themes/windows/menuPanel@2x.png b/browser/themes/windows/menuPanel@2x.png index d1d52568bcafa3125a44008846847017dedd6a8b..bd15b916c2a08c009f8c071b9fce978dd3d6dd01 100644 Binary files a/browser/themes/windows/menuPanel@2x.png and b/browser/themes/windows/menuPanel@2x.png differ diff --git a/configure.in b/configure.in index f71d5e6c9e3bdceacdf906eea42b4c0cdb44e9bd..97fce9cd7306bee522e634a6efd520d2b4929b0a 100644 --- a/configure.in +++ b/configure.in @@ -8481,13 +8481,6 @@ if test -n "$MOZ_DEVICES"; then AC_DEFINE(MOZ_DEVICES) fi -dnl Build Fennec Android native UI to manage accounts. This will go away as -dnl Fennec transitions to web UI to manage accounts. -AC_SUBST(MOZ_ANDROID_NATIVE_ACCOUNT_UI) -if test -n "$MOZ_ANDROID_NATIVE_ACCOUNT_UI"; then - AC_DEFINE(MOZ_ANDROID_NATIVE_ACCOUNT_UI) -fi - dnl ======================================================== if test "$MOZ_DEBUG" -o "$MOZ_DMD"; then MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS= diff --git a/devtools/client/animationinspector/components.js b/devtools/client/animationinspector/components.js index 800626780ffddb355a30b3328de480d4bc4a54e9..fe037b8edb45fa16eb22fae63c789052e984c63a 100644 --- a/devtools/client/animationinspector/components.js +++ b/devtools/client/animationinspector/components.js @@ -36,9 +36,10 @@ const MILLIS_TIME_FORMAT_MAX_DURATION = 4000; const TIME_GRADUATION_MIN_SPACING = 40; // List of playback rate presets displayed in the timeline toolbar. const PLAYBACK_RATES = [.1, .25, .5, 1, 2, 5, 10]; -// The size of the fast-track icon (for compositor-running animations), this is -// used to position the icon correctly. -const FAST_TRACK_ICON_SIZE = 20; +// When the container window is resized, the timeline background gets refreshed, +// but only after a timer, and the timer is reset if the window is continuously +// resized. +const TIMELINE_BACKGROUND_RESIZE_DEBOUNCE_TIMER = 50; /** * UI component responsible for displaying a preview of the target dom node of @@ -477,46 +478,42 @@ var TimeScale = { }, /** - * Convert a startTime to a distance in pixels, in the current time scale. + * Convert a startTime to a distance in %, in the current time scale. * @param {Number} time - * @param {Number} containerWidth The width of the container element. * @return {Number} */ - startTimeToDistance: function(time, containerWidth) { + startTimeToDistance: function(time) { time -= this.minStartTime; - return this.durationToDistance(time, containerWidth); + return this.durationToDistance(time); }, /** - * Convert a duration to a distance in pixels, in the current time scale. + * Convert a duration to a distance in %, in the current time scale. * @param {Number} time - * @param {Number} containerWidth The width of the container element. * @return {Number} */ - durationToDistance: function(duration, containerWidth) { - return containerWidth * duration / (this.maxEndTime - this.minStartTime); + durationToDistance: function(duration) { + return duration * 100 / (this.maxEndTime - this.minStartTime); }, /** - * Convert a distance in pixels to a time, in the current time scale. + * Convert a distance in % to a time, in the current time scale. * @param {Number} distance - * @param {Number} containerWidth The width of the container element. * @return {Number} */ - distanceToTime: function(distance, containerWidth) { + distanceToTime: function(distance) { return this.minStartTime + - ((this.maxEndTime - this.minStartTime) * distance / containerWidth); + ((this.maxEndTime - this.minStartTime) * distance / 100); }, /** - * Convert a distance in pixels to a time, in the current time scale. + * Convert a distance in % to a time, in the current time scale. * The time will be relative to the current minimum start time. * @param {Number} distance - * @param {Number} containerWidth The width of the container element. * @return {Number} */ - distanceToRelativeTime: function(distance, containerWidth) { - let time = this.distanceToTime(distance, containerWidth); + distanceToRelativeTime: function(distance) { + let time = this.distanceToTime(distance); return time - this.minStartTime; }, @@ -560,12 +557,15 @@ function AnimationsTimeline(inspector) { this.targetNodes = []; this.timeBlocks = []; this.inspector = inspector; + this.onAnimationStateChanged = this.onAnimationStateChanged.bind(this); this.onScrubberMouseDown = this.onScrubberMouseDown.bind(this); this.onScrubberMouseUp = this.onScrubberMouseUp.bind(this); this.onScrubberMouseOut = this.onScrubberMouseOut.bind(this); this.onScrubberMouseMove = this.onScrubberMouseMove.bind(this); this.onAnimationSelected = this.onAnimationSelected.bind(this); + this.onWindowResize = this.onWindowResize.bind(this); + EventEmitter.decorate(this); } @@ -582,8 +582,13 @@ AnimationsTimeline.prototype = { } }); - this.scrubberEl = createNode({ + let scrubberContainer = createNode({ parent: this.rootWrapperEl, + attributes: {"class": "scrubber-wrapper"} + }); + + this.scrubberEl = createNode({ + parent: scrubberContainer, attributes: { "class": "scrubber" } @@ -612,12 +617,15 @@ AnimationsTimeline.prototype = { "class": "animations" } }); + + this.win.addEventListener("resize", this.onWindowResize); }, destroy: function() { this.stopAnimatingScrubber(); this.unrender(); + this.win.removeEventListener("resize", this.onWindowResize); this.timeHeaderEl.removeEventListener("mousedown", this.onScrubberMouseDown); this.scrubberHandleEl.removeEventListener("mousedown", @@ -660,6 +668,16 @@ AnimationsTimeline.prototype = { this.animationsEl.innerHTML = ""; }, + onWindowResize: function() { + if (this.windowResizeTimer) { + this.win.clearTimeout(this.windowResizeTimer); + } + + this.windowResizeTimer = this.win.setTimeout(() => { + this.drawHeaderAndBackground(); + }, TIMELINE_BACKGROUND_RESIZE_DEBOUNCE_TIMER); + }, + onAnimationSelected: function(e, animation) { // Unselect the previously selected animation if any. [...this.rootWrapperEl.querySelectorAll(".animation.selected")].forEach(el => { @@ -713,15 +731,18 @@ AnimationsTimeline.prototype = { moveScrubberTo: function(pageX) { this.stopAnimatingScrubber(); - let offset = pageX - this.scrubberEl.offsetWidth; + // The offset needs to be in % and relative to the timeline's area (so we + // subtract the scrubber's left offset, which is equal to the sidebar's + // width). + let offset = (pageX - this.timeHeaderEl.offsetLeft) * 100 / + this.timeHeaderEl.offsetWidth; if (offset < 0) { offset = 0; } - this.scrubberEl.style.left = offset + "px"; + this.scrubberEl.style.left = offset + "%"; - let time = TimeScale.distanceToRelativeTime(offset, - this.timeHeaderEl.offsetWidth); + let time = TimeScale.distanceToRelativeTime(offset); this.emit("timeline-data-changed", { isPaused: true, @@ -817,8 +838,8 @@ AnimationsTimeline.prototype = { }, startAnimatingScrubber: function(time) { - let x = TimeScale.startTimeToDistance(time, this.timeHeaderEl.offsetWidth); - this.scrubberEl.style.left = x + "px"; + let x = TimeScale.startTimeToDistance(time); + this.scrubberEl.style.left = x + "%"; // Only stop the scrubber if it's out of bounds or all animations have been // paused, but not if at least an animation is infinite. @@ -833,7 +854,7 @@ AnimationsTimeline.prototype = { isPaused: !this.isAtLeastOneAnimationPlaying(), isMoving: false, isUserDrag: false, - time: TimeScale.distanceToRelativeTime(x, this.timeHeaderEl.offsetWidth) + time: TimeScale.distanceToRelativeTime(x) }); return; } @@ -842,7 +863,7 @@ AnimationsTimeline.prototype = { isPaused: false, isMoving: true, isUserDrag: false, - time: TimeScale.distanceToRelativeTime(x, this.timeHeaderEl.offsetWidth) + time: TimeScale.distanceToRelativeTime(x) }); let now = this.win.performance.now(); @@ -878,15 +899,15 @@ AnimationsTimeline.prototype = { this.timeHeaderEl.innerHTML = ""; let interval = findOptimalTimeInterval(scale, TIME_GRADUATION_MIN_SPACING); for (let i = 0; i < width; i += interval) { + let pos = 100 * i / width; createNode({ parent: this.timeHeaderEl, nodeType: "span", attributes: { "class": "time-tick", - "style": `left:${i}px` + "style": `left:${pos}%` }, - textContent: TimeScale.formatTime( - TimeScale.distanceToRelativeTime(i, width)) + textContent: TimeScale.formatTime(TimeScale.distanceToRelativeTime(pos)) }); } } @@ -922,8 +943,6 @@ AnimationTimeBlock.prototype = { this.animation = animation; let {state} = this.animation; - let width = this.containerEl.offsetWidth; - // Create a container element to hold the delay and iterations. // It is positioned according to its delay (divided by the playbackrate), // and its width is according to its duration (divided by the playbackrate). @@ -933,10 +952,10 @@ AnimationTimeBlock.prototype = { let count = state.iterationCount; let delay = state.delay || 0; - let x = TimeScale.startTimeToDistance(start + (delay / rate), width); - let w = TimeScale.durationToDistance(duration / rate, width); + let x = TimeScale.startTimeToDistance(start + (delay / rate)); + let w = TimeScale.durationToDistance(duration / rate); let iterationW = w * (count || 1); - let delayW = TimeScale.durationToDistance(Math.abs(delay) / rate, width); + let delayW = TimeScale.durationToDistance(Math.abs(delay) / rate); let iterations = createNode({ parent: this.containerEl, @@ -944,9 +963,9 @@ AnimationTimeBlock.prototype = { "class": state.type + " iterations" + (count ? "" : " infinite"), // Individual iterations are represented by setting the size of the // repeating linear-gradient. - "style": `left:${x}px; - width:${iterationW}px; - background-size:${Math.max(w, 2)}px 100%;` + "style": `left:${x}%; + width:${iterationW}%; + background-size:${100 / (count || 1)}% 100%;` } }); @@ -959,11 +978,8 @@ AnimationTimeBlock.prototype = { attributes: { "class": "name", "title": this.getTooltipText(state), - // Position the fast-track icon with background-position, and make space - // for the negative delay with a margin-left. - "style": "background-position:" + - (iterationW - FAST_TRACK_ICON_SIZE - negativeDelayW) + - "px center;margin-left:" + negativeDelayW + "px" + // Make space for the negative delay with a margin-left. + "style": `margin-left:${negativeDelayW}%` }, textContent: state.name }); @@ -971,14 +987,13 @@ AnimationTimeBlock.prototype = { // Delay. if (delay) { // Negative delays need to start at 0. - let delayX = TimeScale.durationToDistance( - (delay < 0 ? 0 : delay) / rate, width); + let delayX = TimeScale.durationToDistance((delay < 0 ? 0 : delay) / rate); createNode({ parent: iterations, attributes: { "class": "delay" + (delay < 0 ? " negative" : ""), - "style": `left:-${delayX}px; - width:${delayW}px;` + "style": `left:-${delayX}%; + width:${delayW}%;` } }); } diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_header.js b/devtools/client/animationinspector/test/browser_animation_timeline_header.js index e88024180004bc435b5b15980ec2090f4a131c7e..7f7c12b0f4994047180fde11e334f8a6725cc8bf 100644 --- a/devtools/client/animationinspector/test/browser_animation_timeline_header.js +++ b/devtools/client/animationinspector/test/browser_animation_timeline_header.js @@ -32,14 +32,15 @@ add_task(function*() { info("Make sure graduations are evenly distributed and show the right times"); [...headerEl.querySelectorAll(".time-tick")].forEach((tick, i) => { let left = parseFloat(tick.style.left); - is(Math.round(left), Math.round(i * interval), + let expectedPos = i * interval * 100 / width; + is(Math.round(left), Math.round(expectedPos), "Graduation " + i + " is positioned correctly"); // Note that the distancetoRelativeTime and formatTime functions are tested // separately in xpcshell test test_timeScale.js, so we assume that they // work here. let formattedTime = TimeScale.formatTime( - TimeScale.distanceToRelativeTime(i * interval, width)); + TimeScale.distanceToRelativeTime(expectedPos, width)); is(tick.textContent, formattedTime, "Graduation " + i + " has the right text content"); }); diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_pause_button.js b/devtools/client/animationinspector/test/browser_animation_timeline_pause_button.js index 066a0e0cc4436435380f1c0d2fcd09b95329e680..2cdcd9ba2338bec081487ea76cecf8ce2b01382a 100644 --- a/devtools/client/animationinspector/test/browser_animation_timeline_pause_button.js +++ b/devtools/client/animationinspector/test/browser_animation_timeline_pause_button.js @@ -64,7 +64,7 @@ add_task(function*() { "animation to complete"); yield selectNode(".negative-delay", inspector); yield reloadTab(inspector); - yield waitForOutOfBoundScrubber(timeline); + yield waitForScrubberStopped(timeline); ok(btn.classList.contains("paused"), "The button is in paused state once finite animations are done"); @@ -92,3 +92,14 @@ function waitForOutOfBoundScrubber({win, scrubberEl}) { check(); }); } + +function waitForScrubberStopped(timeline) { + return new Promise(resolve => { + timeline.on("timeline-data-changed", function onTimelineData(e, {isMoving}) { + if (!isMoving) { + timeline.off("timeline-data-changed", onTimelineData); + resolve(); + } + }); + }); +} diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_scrubber_movable.js b/devtools/client/animationinspector/test/browser_animation_timeline_scrubber_movable.js index fb857c878d9d425e5c956bbaa0cf158d105f4cc5..54fc349130d2178f2a639610c13ea3d89fbcb4a5 100644 --- a/devtools/client/animationinspector/test/browser_animation_timeline_scrubber_movable.js +++ b/devtools/client/animationinspector/test/browser_animation_timeline_scrubber_movable.js @@ -23,16 +23,14 @@ add_task(function*() { info("Mousedown in the header to move the scrubber"); yield synthesizeInHeaderAndWaitForChange(timeline, 50, 1, "mousedown"); - let newPos = parseInt(scrubberEl.style.left, 10); - is(newPos, 50, "The scrubber moved on mousedown"); + checkScrubberIsAt(scrubberEl, timeHeaderEl, 50); ok(playTimelineButtonEl.classList.contains("paused"), "The timeline play button is in its paused state after mousedown"); info("Continue moving the mouse and verify that the scrubber tracks it"); yield synthesizeInHeaderAndWaitForChange(timeline, 100, 1, "mousemove"); - newPos = parseInt(scrubberEl.style.left, 10); - is(newPos, 100, "The scrubber followed the mouse"); + checkScrubberIsAt(scrubberEl, timeHeaderEl, 100); ok(playTimelineButtonEl.classList.contains("paused"), "The timeline play button is in its paused state after mousemove"); @@ -40,8 +38,7 @@ add_task(function*() { info("Release the mouse and move again and verify that the scrubber stays"); EventUtils.synthesizeMouse(timeHeaderEl, 100, 1, {type: "mouseup"}, win); EventUtils.synthesizeMouse(timeHeaderEl, 200, 1, {type: "mousemove"}, win); - newPos = parseInt(scrubberEl.style.left, 10); - is(newPos, 100, "The scrubber stopped following the mouse"); + checkScrubberIsAt(scrubberEl, timeHeaderEl, 100); info("Try to drag the scrubber handle and check that the scrubber moves"); let onDataChanged = timeline.once("timeline-data-changed"); @@ -50,8 +47,7 @@ add_task(function*() { EventUtils.synthesizeMouse(timeHeaderEl, 0, 0, {type: "mouseup"}, win); yield onDataChanged; - newPos = parseInt(scrubberEl.style.left, 10); - is(newPos, 0, "The scrubber stopped following the mouse"); + checkScrubberIsAt(scrubberEl, timeHeaderEl, 0); }); function* synthesizeInHeaderAndWaitForChange(timeline, x, y, type) { @@ -59,3 +55,14 @@ function* synthesizeInHeaderAndWaitForChange(timeline, x, y, type) { EventUtils.synthesizeMouse(timeline.timeHeaderEl, x, y, {type}, timeline.win); yield onDataChanged; } + +function getPositionPercentage(pos, headerEl) { + return pos * 100 / headerEl.offsetWidth; +} + +function checkScrubberIsAt(scrubberEl, timeHeaderEl, pos) { + let newPos = Math.round(parseFloat(scrubberEl.style.left)); + let expectedPos = Math.round(getPositionPercentage(pos, timeHeaderEl)); + is(newPos, expectedPos, + `The scrubber is at position ${pos} (${expectedPos}%)`); +} diff --git a/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js b/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js index 2ef5a8e60486ac6c4c76a42f5a2bf96d156a9514..3752254a64f8818260a11f8c088d30a2755d7a8b 100644 --- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js +++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js @@ -46,6 +46,5 @@ add_task(function*() { function getIterationCountFromBackground(el) { let backgroundSize = parseFloat(el.style.backgroundSize.split(" ")[0]); - let width = el.offsetWidth; - return Math.round(width / backgroundSize); + return Math.round(100 / backgroundSize); } diff --git a/devtools/client/animationinspector/test/browser_animation_ui_updates_when_animation_data_changes.js b/devtools/client/animationinspector/test/browser_animation_ui_updates_when_animation_data_changes.js index bba47af817417b2bdcf9eed49f9faf9b9d9e107f..38e1c88f83a8d39f88cd96862f77060dad2b3295 100644 --- a/devtools/client/animationinspector/test/browser_animation_ui_updates_when_animation_data_changes.js +++ b/devtools/client/animationinspector/test/browser_animation_ui_updates_when_animation_data_changes.js @@ -9,12 +9,8 @@ add_task(function*() { yield addTab(TEST_URL_ROOT + "doc_simple_animation.html"); + let {panel, controller, inspector} = yield openAnimationInspector(); - let ui = yield openAnimationInspector(); - yield testDataUpdates(ui); -}); - -function* testDataUpdates({panel, controller, inspector}) { info("Select the test node"); yield selectNode(".animated", inspector); @@ -28,15 +24,14 @@ function* testDataUpdates({panel, controller, inspector}) { // 45s delay + (300 * 5.5)s duration let expectedTotalDuration = 1695 * 1000; - let timeRatio = expectedTotalDuration / timeBlockEl.offsetWidth; // XXX: the nb and size of each iteration cannot be tested easily (displayed // using a linear-gradient background and capped at 2px wide). They should // be tested in bug 1173761. let delayWidth = parseFloat(timeBlockEl.querySelector(".delay").style.width); - is(Math.round(delayWidth * timeRatio), 45 * 1000, + is(Math.round(delayWidth * expectedTotalDuration / 100), 45 * 1000, "The timeline has the right delay"); -} +}); function* setStyle(animation, panel, name, value) { info("Change the animation style via the content DOM. Setting " + diff --git a/devtools/client/animationinspector/test/unit/test_timeScale.js b/devtools/client/animationinspector/test/unit/test_timeScale.js index 2ec3ae3ea6ae2c38945ef70f3d8410e143cb9dd8..29fa35aaae6797b9b4e897e95b4d0114a9fb1be8 100644 --- a/devtools/client/animationinspector/test/unit/test_timeScale.js +++ b/devtools/client/animationinspector/test/unit/test_timeScale.js @@ -58,58 +58,46 @@ const TEST_ANIMATIONS = [{ const TEST_STARTTIME_TO_DISTANCE = [{ time: 50, - width: 100, expectedDistance: 0 }, { time: 50, - width: 0, expectedDistance: 0 }, { time: 3050, - width: 200, - expectedDistance: 200 + expectedDistance: 100 }, { time: 1550, - width: 200, - expectedDistance: 100 + expectedDistance: 50 }]; const TEST_DURATION_TO_DISTANCE = [{ time: 3000, - width: 100, expectedDistance: 100 }, { time: 0, - width: 100, expectedDistance: 0 }]; const TEST_DISTANCE_TO_TIME = [{ distance: 100, - width: 100, expectedTime: 3050 }, { distance: 0, - width: 100, expectedTime: 50 }, { distance: 25, - width: 200, - expectedTime: 425 + expectedTime: 800 }]; const TEST_DISTANCE_TO_RELATIVE_TIME = [{ distance: 100, - width: 100, expectedTime: 3000 }, { distance: 0, - width: 100, expectedTime: 0 }, { distance: 25, - width: 200, - expectedTime: 375 + expectedTime: 750 }]; const TEST_FORMAT_TIME_MS = [{ @@ -174,26 +162,26 @@ function run_test() { } do_print("Test converting start times to distances"); - for (let {time, width, expectedDistance} of TEST_STARTTIME_TO_DISTANCE) { - let distance = TimeScale.startTimeToDistance(time, width); + for (let {time, expectedDistance} of TEST_STARTTIME_TO_DISTANCE) { + let distance = TimeScale.startTimeToDistance(time); equal(distance, expectedDistance); } do_print("Test converting durations to distances"); - for (let {time, width, expectedDistance} of TEST_DURATION_TO_DISTANCE) { - let distance = TimeScale.durationToDistance(time, width); + for (let {time, expectedDistance} of TEST_DURATION_TO_DISTANCE) { + let distance = TimeScale.durationToDistance(time); equal(distance, expectedDistance); } do_print("Test converting distances to times"); - for (let {distance, width, expectedTime} of TEST_DISTANCE_TO_TIME) { - let time = TimeScale.distanceToTime(distance, width); + for (let {distance, expectedTime} of TEST_DISTANCE_TO_TIME) { + let time = TimeScale.distanceToTime(distance); equal(time, expectedTime); } do_print("Test converting distances to relative times"); - for (let {distance, width, expectedTime} of TEST_DISTANCE_TO_RELATIVE_TIME) { - let time = TimeScale.distanceToRelativeTime(distance, width); + for (let {distance, expectedTime} of TEST_DISTANCE_TO_RELATIVE_TIME) { + let time = TimeScale.distanceToRelativeTime(distance); equal(time, expectedTime); } diff --git a/devtools/client/themes/animationinspector.css b/devtools/client/themes/animationinspector.css index 0244ec812ce9548d079dde3e48040c1c1d648449..ff8e7c4b4ad34ee625d11dbbe76002c475ebebf0 100644 --- a/devtools/client/themes/animationinspector.css +++ b/devtools/client/themes/animationinspector.css @@ -165,21 +165,30 @@ body { This is done so that the background can be built dynamically from script */ background-image: -moz-element(#time-graduations); background-repeat: repeat-y; - /* The animations are drawn 150px from the left edge so that animated nodes - can be displayed in a sidebar */ + /* Make the background be 100% of the timeline area so that it resizes with + it*/ + background-size: calc(100% - var(--timeline-sidebar-width)) 100%; background-position: var(--timeline-sidebar-width) 0; display: flex; flex-direction: column; } +.animation-timeline .scrubber-wrapper { + position: absolute; + top: 0; + bottom: 0; + left: var(--timeline-sidebar-width); + right: 0; + z-index: 1; + pointer-events: none; +} + .animation-timeline .scrubber { position: absolute; height: 100%; - width: var(--timeline-sidebar-width); + width: 0; border-right: 1px solid red; box-sizing: border-box; - z-index: 1; - pointer-events: none; } .animation-timeline .scrubber::before { @@ -342,6 +351,7 @@ body { /* Animations running on the compositor have the fast-track background image*/ background-image: url("images/animation-fast-track.svg"); background-repeat: no-repeat; + background-position: calc(100% - 5px) center; } .animation-timeline .animation .delay { diff --git a/devtools/shared/styleinspector/css-logic.js b/devtools/shared/styleinspector/css-logic.js index f5d1fbf660b5f2c808a39f75184d5be4ebe9fd83..6c59ce7745a2922b8655daf08f445d78566fdb70 100644 --- a/devtools/shared/styleinspector/css-logic.js +++ b/devtools/shared/styleinspector/css-logic.js @@ -28,6 +28,8 @@ * reference to the selected element. */ +"use strict"; + /** * Provide access to the style information in a page. * CssLogic uses the standard DOM API, and the Gecko inIDOMUtils API to access @@ -47,10 +49,9 @@ const { getRootBindingParent } = require("devtools/shared/layout/utils"); // on the worker thread, where Cu is not available. loader.lazyRequireGetter(this, "CSS", "CSS"); -function CssLogic() -{ +function CssLogic() { // The cache of examined CSS properties. - _propertyInfos: {}; + this._propertyInfos = {}; } exports.CssLogic = CssLogic; @@ -59,8 +60,10 @@ exports.CssLogic = CssLogic; * Special values for filter, in addition to an href these values can be used */ CssLogic.FILTER = { - USER: "user", // show properties for all user style sheets. - UA: "ua", // USER, plus user-agent (i.e. browser) style sheets + // show properties for all user style sheets. + USER: "user", + // USER, plus user-agent (i.e. browser) style sheets + UA: "ua", }; /** @@ -79,7 +82,8 @@ CssLogic.MEDIA = { * Each rule has a status, the bigger the number, the better placed it is to * provide styling information. * - * These statuses are localized inside the styleinspector.properties string bundle. + * These statuses are localized inside the styleinspector.properties + * string bundle. * @see csshtmltree.js RuleView._cacheStatusNames() */ CssLogic.STATUS = { @@ -126,8 +130,7 @@ CssLogic.prototype = { /** * Reset various properties */ - reset: function CssLogic_reset() - { + reset: function() { this._propertyInfos = {}; this._ruleCount = 0; this._sheetIndex = 0; @@ -144,9 +147,8 @@ CssLogic.prototype = { * @param {nsIDOMElement} aViewedElement the element the user has highlighted * in the Inspector. */ - highlight: function CssLogic_highlight(aViewedElement) - { - if (!aViewedElement) { + highlight: function(viewedElement) { + if (!viewedElement) { this.viewedElement = null; this.viewedDocument = null; this._computedStyle = null; @@ -154,11 +156,11 @@ CssLogic.prototype = { return; } - if (aViewedElement === this.viewedElement) { + if (viewedElement === this.viewedElement) { return; } - this.viewedElement = aViewedElement; + this.viewedElement = viewedElement; let doc = this.viewedElement.ownerDocument; if (doc != this.viewedDocument) { @@ -200,17 +202,17 @@ CssLogic.prototype = { * unmatched system rules. * @see CssLogic.FILTER.* */ - set sourceFilter(aValue) { + set sourceFilter(value) { let oldValue = this._sourceFilter; - this._sourceFilter = aValue; + this._sourceFilter = value; let ruleCount = 0; // Update the CssSheet objects. - this.forEachSheet(function(aSheet) { - aSheet._sheetAllowed = -1; - if (aSheet.contentSheet && aSheet.sheetAllowed) { - ruleCount += aSheet.ruleCount; + this.forEachSheet(function(sheet) { + sheet._sheetAllowed = -1; + if (sheet.contentSheet && sheet.sheetAllowed) { + ruleCount += sheet.ruleCount; } }, this); @@ -219,7 +221,7 @@ CssLogic.prototype = { // Full update is needed because the this.processMatchedSelectors() method // skips UA stylesheets if the filter does not allow such sheets. let needFullUpdate = (oldValue == CssLogic.FILTER.UA || - aValue == CssLogic.FILTER.UA); + value == CssLogic.FILTER.UA); if (needFullUpdate) { this._matchedRules = null; @@ -238,20 +240,19 @@ CssLogic.prototype = { * and the specified CSS property. If there is no currently viewed element we * return an empty object. * - * @param {string} aProperty The CSS property to look for. + * @param {string} property The CSS property to look for. * @return {CssPropertyInfo} a CssPropertyInfo structure for the given * property. */ - getPropertyInfo: function CssLogic_getPropertyInfo(aProperty) - { + getPropertyInfo: function(property) { if (!this.viewedElement) { return {}; } - let info = this._propertyInfos[aProperty]; + let info = this._propertyInfos[property]; if (!info) { - info = new CssPropertyInfo(this, aProperty); - this._propertyInfos[aProperty] = info; + info = new CssPropertyInfo(this, property); + this._propertyInfos[property] = info; } return info; @@ -261,8 +262,7 @@ CssLogic.prototype = { * Cache all the stylesheets in the inspected document * @private */ - _cacheSheets: function CssLogic_cacheSheets() - { + _cacheSheets: function() { this._passId++; this.reset(); @@ -280,27 +280,27 @@ CssLogic.prototype = { * as well. In addition, the @keyframes rules in the stylesheet are cached. * * @private - * @param {CSSStyleSheet} aDomSheet the CSSStyleSheet object to cache. + * @param {CSSStyleSheet} domSheet the CSSStyleSheet object to cache. */ - _cacheSheet: function CssLogic_cacheSheet(aDomSheet) - { - if (aDomSheet.disabled) { + _cacheSheet: function(domSheet) { + if (domSheet.disabled) { return; } // Only work with stylesheets that have their media allowed. - if (!this.mediaMatches(aDomSheet)) { + if (!this.mediaMatches(domSheet)) { return; } // Cache the sheet. - let cssSheet = this.getSheet(aDomSheet, this._sheetIndex++); + let cssSheet = this.getSheet(domSheet, this._sheetIndex++); if (cssSheet._passId != this._passId) { cssSheet._passId = this._passId; // Find import and keyframes rules. - for (let aDomRule of aDomSheet.cssRules) { - if (aDomRule.type == Ci.nsIDOMCSSRule.IMPORT_RULE && aDomRule.styleSheet && + for (let aDomRule of domSheet.cssRules) { + if (aDomRule.type == Ci.nsIDOMCSSRule.IMPORT_RULE && + aDomRule.styleSheet && this.mediaMatches(aDomRule)) { this._cacheSheet(aDomRule.styleSheet); } else if (aDomRule.type == Ci.nsIDOMCSSRule.KEYFRAMES_RULE) { @@ -315,16 +315,15 @@ CssLogic.prototype = { * * @return {array} the list of stylesheets in the document. */ - get sheets() - { + get sheets() { if (!this._sheetsCached) { this._cacheSheets(); } let sheets = []; - this.forEachSheet(function (aSheet) { - if (aSheet.contentSheet) { - sheets.push(aSheet); + this.forEachSheet(function(sheet) { + if (sheet.contentSheet) { + sheets.push(sheet); } }, this); @@ -336,8 +335,7 @@ CssLogic.prototype = { * * @ return {array} the list of keyframes rules in the document. */ - get keyframesRules() - { + get keyframesRules() { if (!this._sheetsCached) { this._cacheSheets(); } @@ -349,30 +347,31 @@ CssLogic.prototype = { * stylesheet is already cached, you get the existing CssSheet object, * otherwise the new CSSStyleSheet object is cached. * - * @param {CSSStyleSheet} aDomSheet the CSSStyleSheet object you want. - * @param {number} aIndex the index, within the document, of the stylesheet. + * @param {CSSStyleSheet} domSheet the CSSStyleSheet object you want. + * @param {number} index the index, within the document, of the stylesheet. * * @return {CssSheet} the CssSheet object for the given CSSStyleSheet object. */ - getSheet: function CL_getSheet(aDomSheet, aIndex) - { + getSheet: function(domSheet, index) { let cacheId = ""; - if (aDomSheet.href) { - cacheId = aDomSheet.href; - } else if (aDomSheet.ownerNode && aDomSheet.ownerNode.ownerDocument) { - cacheId = aDomSheet.ownerNode.ownerDocument.location; + if (domSheet.href) { + cacheId = domSheet.href; + } else if (domSheet.ownerNode && domSheet.ownerNode.ownerDocument) { + cacheId = domSheet.ownerNode.ownerDocument.location; } let sheet = null; let sheetFound = false; if (cacheId in this._sheets) { - for (let i = 0, numSheets = this._sheets[cacheId].length; i < numSheets; i++) { + for (let i = 0, numSheets = this._sheets[cacheId].length; + i < numSheets; + i++) { sheet = this._sheets[cacheId][i]; - if (sheet.domSheet === aDomSheet) { - if (aIndex != -1) { - sheet.index = aIndex; + if (sheet.domSheet === domSheet) { + if (index != -1) { + sheet.index = index; } sheetFound = true; break; @@ -385,7 +384,7 @@ CssLogic.prototype = { this._sheets[cacheId] = []; } - sheet = new CssSheet(this, aDomSheet, aIndex); + sheet = new CssSheet(this, domSheet, index); if (sheet.sheetAllowed && sheet.contentSheet) { this._ruleCount += sheet.ruleCount; } @@ -399,25 +398,25 @@ CssLogic.prototype = { /** * Process each cached stylesheet in the document using your callback. * - * @param {function} aCallback the function you want executed for each of the + * @param {function} callback the function you want executed for each of the * CssSheet objects cached. - * @param {object} aScope the scope you want for the callback function. aScope - * will be the this object when aCallback executes. + * @param {object} scope the scope you want for the callback function. scope + * will be the this object when callback executes. */ - forEachSheet: function CssLogic_forEachSheet(aCallback, aScope) - { + forEachSheet: function(callback, scope) { for (let cacheId in this._sheets) { let sheets = this._sheets[cacheId]; - for (let i = 0; i < sheets.length; i ++) { + for (let i = 0; i < sheets.length; i++) { // We take this as an opportunity to clean dead sheets try { let sheet = sheets[i]; - sheet.domSheet; // If accessing domSheet raises an exception, then the - // style sheet is a dead object - aCallback.call(aScope, sheet, i, sheets); + // If accessing domSheet raises an exception, then the style + // sheet is a dead object. + sheet.domSheet; + callback.call(scope, sheet, i, sheets); } catch (e) { sheets.splice(i, 1); - i --; + i--; } } } @@ -427,17 +426,16 @@ CssLogic.prototype = { * Process *some* cached stylesheets in the document using your callback. The * callback function should return true in order to halt processing. * - * @param {function} aCallback the function you want executed for some of the + * @param {function} callback the function you want executed for some of the * CssSheet objects cached. - * @param {object} aScope the scope you want for the callback function. aScope - * will be the this object when aCallback executes. - * @return {Boolean} true if aCallback returns true during any iteration, + * @param {object} scope the scope you want for the callback function. scope + * will be the this object when callback executes. + * @return {Boolean} true if callback returns true during any iteration, * otherwise false is returned. */ - forSomeSheets: function CssLogic_forSomeSheets(aCallback, aScope) - { + forSomeSheets: function(callback, scope) { for (let cacheId in this._sheets) { - if (this._sheets[cacheId].some(aCallback, aScope)) { + if (this._sheets[cacheId].some(callback, scope)) { return true; } } @@ -455,8 +453,7 @@ CssLogic.prototype = { * * @return {number} the number of nsIDOMCSSRule (all rules). */ - get ruleCount() - { + get ruleCount() { if (!this._sheetsCached) { this._cacheSheets(); } @@ -466,7 +463,7 @@ CssLogic.prototype = { /** * Process the CssSelector objects that match the highlighted element and its - * parent elements. aScope.aCallback() is executed for each CssSelector + * parent elements. scope.callback() is executed for each CssSelector * object, being passed the CssSelector object and the match status. * * This method also includes all of the element.style properties, for each @@ -475,19 +472,18 @@ CssLogic.prototype = { * Note that the matched selectors are cached, such that next time your * callback is invoked for the cached list of CssSelector objects. * - * @param {function} aCallback the function you want to execute for each of + * @param {function} callback the function you want to execute for each of * the matched selectors. - * @param {object} aScope the scope you want for the callback function. aScope - * will be the this object when aCallback executes. + * @param {object} scope the scope you want for the callback function. scope + * will be the this object when callback executes. */ - processMatchedSelectors: function CL_processMatchedSelectors(aCallback, aScope) - { + processMatchedSelectors: function(callback, scope) { if (this._matchedSelectors) { - if (aCallback) { + if (callback) { this._passId++; - this._matchedSelectors.forEach(function(aValue) { - aCallback.call(aScope, aValue[0], aValue[1]); - aValue[0].cssRule._passId = this._passId; + this._matchedSelectors.forEach(function(value) { + callback.call(scope, value[0], value[1]); + value[0].cssRule._passId = this._passId; }, this); } return; @@ -504,15 +500,15 @@ CssLogic.prototype = { let rule = this._matchedRules[i][0]; let status = this._matchedRules[i][1]; - rule.selectors.forEach(function (aSelector) { - if (aSelector._matchId !== this._matchId && - (aSelector.elementStyle || - this.selectorMatchesElement(rule.domRule, aSelector.selectorIndex))) { - - aSelector._matchId = this._matchId; - this._matchedSelectors.push([ aSelector, status ]); - if (aCallback) { - aCallback.call(aScope, aSelector, status); + rule.selectors.forEach(function(selector) { + if (selector._matchId !== this._matchId && + (selector.elementStyle || + this.selectorMatchesElement(rule.domRule, + selector.selectorIndex))) { + selector._matchId = this._matchId; + this._matchedSelectors.push([ selector, status ]); + if (callback) { + callback.call(scope, selector, status); } } }, this); @@ -534,8 +530,7 @@ CssLogic.prototype = { * true if the given selector matches the highlighted element or any * of its parents, otherwise false is returned. */ - selectorMatchesElement: function CL_selectorMatchesElement2(domRule, idx) - { + selectorMatchesElement: function(domRule, idx) { let element = this.viewedElement; do { if (domUtils.selectorMatchesElement(element, domRule, idx)) { @@ -555,30 +550,30 @@ CssLogic.prototype = { * @return {object} An object that tells for each property if it has matched * selectors or not. Object keys are property names and values are booleans. */ - hasMatchedSelectors: function CL_hasMatchedSelectors(aProperties) - { + hasMatchedSelectors: function(properties) { if (!this._matchedRules) { this._buildMatchedRules(); } let result = {}; - this._matchedRules.some(function(aValue) { - let rule = aValue[0]; - let status = aValue[1]; - aProperties = aProperties.filter((aProperty) => { + this._matchedRules.some(function(value) { + let rule = value[0]; + let status = value[1]; + properties = properties.filter((property) => { // We just need to find if a rule has this property while it matches // the viewedElement (or its parents). - if (rule.getPropertyValue(aProperty) && + if (rule.getPropertyValue(property) && (status == CssLogic.STATUS.MATCHED || (status == CssLogic.STATUS.PARENT_MATCH && - domUtils.isInheritedProperty(aProperty)))) { - result[aProperty] = true; + domUtils.isInheritedProperty(property)))) { + result[property] = true; return false; } - return true; // Keep the property for the next rule. + // Keep the property for the next rule. + return true; }); - return aProperties.length == 0; + return properties.length == 0; }, this); return result; @@ -590,8 +585,7 @@ CssLogic.prototype = { * * @private */ - _buildMatchedRules: function CL__buildMatchedRules() - { + _buildMatchedRules: function() { let domRules; let element = this.viewedElement; let filter = this.sourceFilter; @@ -612,11 +606,11 @@ CssLogic.prototype = { try { // Handle finding rules on pseudo by reading style rules // on the parent node with proper pseudo arg to getCSSStyleRules. - let {bindingElement, pseudo} = CssLogic.getBindingElementAndPseudo(element); + let {bindingElement, pseudo} = + CssLogic.getBindingElementAndPseudo(element); domRules = domUtils.getCSSStyleRules(bindingElement, pseudo); } catch (ex) { - Services.console. - logStringMessage("CL__buildMatchedRules error: " + ex); + Services.console.logStringMessage("CL__buildMatchedRules error: " + ex); continue; } @@ -662,15 +656,14 @@ CssLogic.prototype = { /** * Tells if the given DOM CSS object matches the current view media. * - * @param {object} aDomObject The DOM CSS object to check. + * @param {object} domObject The DOM CSS object to check. * @return {boolean} True if the DOM CSS object matches the current view * media, or false otherwise. */ - mediaMatches: function CL_mediaMatches(aDomObject) - { - let mediaText = aDomObject.media.mediaText; - return !mediaText || this.viewedDocument.defaultView. - matchMedia(mediaText).matches; + mediaMatches: function(domObject) { + let mediaText = domObject.media.mediaText; + return !mediaText || + this.viewedDocument.defaultView.matchMedia(mediaText).matches; }, }; @@ -681,29 +674,28 @@ CssLogic.prototype = { * 'tagname:nth-of-type(n)' however this is unlikely to be more understood * and it is longer. * - * @param {nsIDOMElement} aElement the element for which you want the short name. - * @return {string} the string to be displayed for aElement. + * @param {nsIDOMElement} element the element for which you want the short name. + * @return {string} the string to be displayed for element. */ -CssLogic.getShortName = function CssLogic_getShortName(aElement) -{ - if (!aElement) { +CssLogic.getShortName = function(element) { + if (!element) { return "null"; } - if (aElement.id) { - return "#" + aElement.id; + if (element.id) { + return "#" + element.id; } let priorSiblings = 0; - let temp = aElement; + let temp = element; while ((temp = temp.previousElementSibling)) { priorSiblings++; } - return aElement.tagName + "[" + priorSiblings + "]"; + return element.tagName + "[" + priorSiblings + "]"; }; /** * Get an array of short names from the given element to document.body. * - * @param {nsIDOMElement} aElement the element for which you want the array of + * @param {nsIDOMElement} element the element for which you want the array of * short names. * @return {array} The array of elements. * <p>Each element is an object of the form: @@ -712,12 +704,11 @@ CssLogic.getShortName = function CssLogic_getShortName(aElement) * <li> element: referenceToTheElement } * </ul> */ -CssLogic.getShortNamePath = function CssLogic_getShortNamePath(aElement) -{ - let doc = aElement.ownerDocument; +CssLogic.getShortNamePath = function(element) { + let doc = element.ownerDocument; let reply = []; - if (!aElement) { + if (!element) { return reply; } @@ -725,11 +716,12 @@ CssLogic.getShortNamePath = function CssLogic_getShortNamePath(aElement) // has selected that node, in which case we need to report something. do { reply.unshift({ - display: CssLogic.getShortName(aElement), - element: aElement + display: CssLogic.getShortName(element), + element: element }); - aElement = aElement.parentNode; - } while (aElement && aElement != doc.body && aElement != doc.head && aElement != doc); + element = element.parentNode; + } while (element && element != doc.body && element != doc.head && + element != doc); return reply; }; @@ -737,22 +729,21 @@ CssLogic.getShortNamePath = function CssLogic_getShortNamePath(aElement) /** * Get a string list of selectors for a given DOMRule. * - * @param {DOMRule} aDOMRule + * @param {DOMRule} domRule * The DOMRule to parse. * @return {Array} * An array of string selectors. */ -CssLogic.getSelectors = function CssLogic_getSelectors(aDOMRule) -{ +CssLogic.getSelectors = function(domRule) { let selectors = []; - let len = domUtils.getSelectorCount(aDOMRule); + let len = domUtils.getSelectorCount(domRule); for (let i = 0; i < len; i++) { - let text = domUtils.getSelectorText(aDOMRule, i); + let text = domUtils.getSelectorText(domRule, i); selectors.push(text); } return selectors; -} +}; /** * Given a node, check to see if it is a ::before or ::after element. @@ -764,8 +755,7 @@ CssLogic.getSelectors = function CssLogic_getSelectors(aDOMRule) * - {DOMNode} node The non-anonymous node * - {string} pseudo One of ':before', ':after', or null. */ -CssLogic.getBindingElementAndPseudo = function(node) -{ +CssLogic.getBindingElementAndPseudo = function(node) { let bindingElement = node; let pseudo = null; if (node.nodeName == "_moz_generated_content_before") { @@ -781,7 +771,6 @@ CssLogic.getBindingElementAndPseudo = function(node) }; }; - /** * Get the computed style on a node. Automatically handles reading * computed styles on a ::before/::after element by reading on the @@ -790,8 +779,7 @@ CssLogic.getBindingElementAndPseudo = function(node) * @param {Node} * @returns {CSSStyleDeclaration} */ -CssLogic.getComputedStyle = function(node) -{ +CssLogic.getComputedStyle = function(node) { if (!node || Cu.isDeadWrapper(node) || node.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE || @@ -801,39 +789,40 @@ CssLogic.getComputedStyle = function(node) } let {bindingElement, pseudo} = CssLogic.getBindingElementAndPseudo(node); - return node.ownerDocument.defaultView.getComputedStyle(bindingElement, pseudo); + return node.ownerDocument.defaultView.getComputedStyle(bindingElement, + pseudo); }; /** * Memonized lookup of a l10n string from a string bundle. - * @param {string} aName The key to lookup. + * @param {string} name The key to lookup. * @returns A localized version of the given key. */ -CssLogic.l10n = function(aName) -{ - return CssLogic._strings.GetStringFromName(aName); +CssLogic.l10n = function(name) { + return CssLogic._strings.GetStringFromName(name); }; -DevToolsUtils.defineLazyGetter(CssLogic, "_strings", function() { return Services.strings - .createBundle("chrome://devtools-shared/locale/styleinspector.properties")}); +DevToolsUtils.defineLazyGetter(CssLogic, "_strings", function() { + return Services.strings + .createBundle("chrome://devtools-shared/locale/styleinspector.properties"); +}); /** * Is the given property sheet a content stylesheet? * - * @param {CSSStyleSheet} aSheet a stylesheet + * @param {CSSStyleSheet} sheet a stylesheet * @return {boolean} true if the given stylesheet is a content stylesheet, * false otherwise. */ -CssLogic.isContentStylesheet = function CssLogic_isContentStylesheet(aSheet) -{ +CssLogic.isContentStylesheet = function(sheet) { // All sheets with owner nodes have been included by content. - if (aSheet.ownerNode) { + if (sheet.ownerNode) { return true; } // If the sheet has a CSSImportRule we need to check the parent stylesheet. - if (aSheet.ownerRule instanceof Ci.nsIDOMCSSImportRule) { - return CssLogic.isContentStylesheet(aSheet.parentStyleSheet); + if (sheet.ownerRule instanceof Ci.nsIDOMCSSImportRule) { + return CssLogic.isContentStylesheet(sheet.parentStyleSheet); } return false; @@ -844,14 +833,13 @@ CssLogic.isContentStylesheet = function CssLogic_isContentStylesheet(aSheet) * for which we need to use document.defaultView.location.href rather than * sheet.href * - * @param {CSSStyleSheet} aSheet the DOM object for the style sheet. + * @param {CSSStyleSheet} sheet the DOM object for the style sheet. * @return {string} the address of the stylesheet. */ -CssLogic.href = function CssLogic_href(aSheet) -{ - let href = aSheet.href; +CssLogic.href = function(sheet) { + let href = sheet.href; if (!href) { - href = aSheet.ownerNode.ownerDocument.location; + href = sheet.ownerNode.ownerDocument.location; } return href; @@ -860,19 +848,18 @@ CssLogic.href = function CssLogic_href(aSheet) /** * Return a shortened version of a style sheet's source. * - * @param {CSSStyleSheet} aSheet the DOM object for the style sheet. + * @param {CSSStyleSheet} sheet the DOM object for the style sheet. */ -CssLogic.shortSource = function CssLogic_shortSource(aSheet) -{ +CssLogic.shortSource = function(sheet) { // Use a string like "inline" if there is no source href - if (!aSheet || !aSheet.href) { + if (!sheet || !sheet.href) { return CssLogic.l10n("rule.sourceInline"); } // We try, in turn, the filename, filePath, query string, whole thing let url = {}; try { - url = Services.io.newURI(aSheet.href, null, null); + url = Services.io.newURI(sheet.href, null, null); url = url.QueryInterface(Ci.nsIURL); } catch (ex) { // Some UA-provided stylesheets are not valid URLs. @@ -890,16 +877,16 @@ CssLogic.shortSource = function CssLogic_shortSource(aSheet) return url.query; } - let dataUrl = aSheet.href.match(/^(data:[^,]*),/); - return dataUrl ? dataUrl[1] : aSheet.href; -} + let dataUrl = sheet.href.match(/^(data:[^,]*),/); + return dataUrl ? dataUrl[1] : sheet.href; +}; /** * Find the position of [element] in [nodeList]. * @returns an index of the match, or -1 if there is no match */ function positionInNodeList(element, nodeList) { - for (var i = 0; i < nodeList.length; i++) { + for (let i = 0; i < nodeList.length; i++) { if (element === nodeList[i]) { return i; } @@ -912,36 +899,37 @@ function positionInNodeList(element, nodeList) { * @returns a string such that ele.ownerDocument.querySelector(reply) === ele * and ele.ownerDocument.querySelectorAll(reply).length === 1 */ -CssLogic.findCssSelector = function CssLogic_findCssSelector(ele) { +CssLogic.findCssSelector = function(ele) { ele = getRootBindingParent(ele); - var document = ele.ownerDocument; + let document = ele.ownerDocument; if (!document || !document.contains(ele)) { - throw new Error('findCssSelector received element not inside document'); + throw new Error("findCssSelector received element not inside document"); } // document.querySelectorAll("#id") returns multiple if elements share an ID - if (ele.id && document.querySelectorAll('#' + CSS.escape(ele.id)).length === 1) { - return '#' + CSS.escape(ele.id); + if (ele.id && + document.querySelectorAll("#" + CSS.escape(ele.id)).length === 1) { + return "#" + CSS.escape(ele.id); } // Inherently unique by tag name - var tagName = ele.localName; - if (tagName === 'html') { - return 'html'; + let tagName = ele.localName; + if (tagName === "html") { + return "html"; } - if (tagName === 'head') { - return 'head'; + if (tagName === "head") { + return "head"; } - if (tagName === 'body') { - return 'body'; + if (tagName === "body") { + return "body"; } // We might be able to find a unique class name - var selector, index, matches; + let selector, index, matches; if (ele.classList.length > 0) { - for (var i = 0; i < ele.classList.length; i++) { + for (let i = 0; i < ele.classList.length; i++) { // Is this className unique by itself? - selector = '.' + CSS.escape(ele.classList.item(i)); + selector = "." + CSS.escape(ele.classList.item(i)); matches = document.querySelectorAll(selector); if (matches.length === 1) { return selector; @@ -954,7 +942,7 @@ CssLogic.findCssSelector = function CssLogic_findCssSelector(ele) { } // Maybe it's unique using a tag name and nth-child index = positionInNodeList(ele, ele.parentNode.children) + 1; - selector = selector + ':nth-child(' + index + ')'; + selector = selector + ":nth-child(" + index + ")"; matches = document.querySelectorAll(selector); if (matches.length === 1) { return selector; @@ -966,8 +954,8 @@ CssLogic.findCssSelector = function CssLogic_findCssSelector(ele) { // continue recursing up until it is unique enough. if (ele.parentNode !== document) { index = positionInNodeList(ele, ele.parentNode.children) + 1; - selector = CssLogic_findCssSelector(ele.parentNode) + ' > ' + - tagName + ':nth-child(' + index + ')'; + selector = CssLogic.findCssSelector(ele.parentNode) + " > " + + tagName + ":nth-child(" + index + ")"; } return selector; @@ -990,11 +978,13 @@ CssLogic.prettifyCSS = function(text, ruleCount) { // remove initial and terminating HTML comments and surrounding whitespace text = text.replace(/(?:^\s*<!--[\r\n]*)|(?:\s*-->\s*$)/g, ""); + let originalText = text; + text = text.trim(); // don't attempt to prettify if there's more than one line per rule. let lineCount = text.split("\n").length - 1; if (ruleCount !== null && lineCount >= ruleCount) { - return text; + return originalText; } // We reformat the text using a simple state machine. The @@ -1092,7 +1082,7 @@ CssLogic.prettifyCSS = function(text, ruleCount) { } endIndex = token.endOffset; - if (token.tokenType === "symbol" && token.text === ';') { + if (token.tokenType === "symbol" && token.text === ";") { break; } @@ -1119,25 +1109,26 @@ CssLogic.prettifyCSS = function(text, ruleCount) { // need anything here. } else { result = result + indent + text.substring(startIndex, endIndex); - if (isCloseBrace) + if (isCloseBrace) { result += CssLogic.LINE_SEPARATOR; + } } } if (isCloseBrace) { indent = TAB_CHARS.repeat(--indentLevel); - result = result + indent + '}'; + result = result + indent + "}"; } if (!token) { break; } - if (token.tokenType === "symbol" && token.text === '{') { + if (token.tokenType === "symbol" && token.text === "{") { if (!lastWasWS) { - result += ' '; + result += " "; } - result += '{'; + result += "{"; indent = TAB_CHARS.repeat(++indentLevel); } @@ -1150,7 +1141,7 @@ CssLogic.prettifyCSS = function(text, ruleCount) { // the text. if (pushbackToken && token && token.tokenType === "whitespace" && /\n/g.test(text.substring(token.startOffset, token.endOffset))) { - return text; + return originalText; } // Finally time for that newline. @@ -1169,17 +1160,16 @@ CssLogic.prettifyCSS = function(text, ruleCount) { * A safe way to access cached bits of information about a stylesheet. * * @constructor - * @param {CssLogic} aCssLogic pointer to the CssLogic instance working with + * @param {CssLogic} cssLogic pointer to the CssLogic instance working with * this CssSheet object. - * @param {CSSStyleSheet} aDomSheet reference to a DOM CSSStyleSheet object. - * @param {number} aIndex tells the index/position of the stylesheet within the + * @param {CSSStyleSheet} domSheet reference to a DOM CSSStyleSheet object. + * @param {number} index tells the index/position of the stylesheet within the * main document. */ -function CssSheet(aCssLogic, aDomSheet, aIndex) -{ - this._cssLogic = aCssLogic; - this.domSheet = aDomSheet; - this.index = this.contentSheet ? aIndex : -100 * aIndex; +function CssSheet(cssLogic, domSheet, index) { + this._cssLogic = cssLogic; + this.domSheet = domSheet; + this.index = this.contentSheet ? index : -100 * index; // Cache of the sheets href. Cached by the getter. this._href = null; @@ -1206,8 +1196,7 @@ CssSheet.prototype = { * @return {boolean} false if this is a browser-provided stylesheet, or true * otherwise. */ - get contentSheet() - { + get contentSheet() { if (this._contentSheet === null) { this._contentSheet = CssLogic.isContentStylesheet(this.domSheet); } @@ -1218,8 +1207,7 @@ CssSheet.prototype = { * Tells if the stylesheet is disabled or not. * @return {boolean} true if this stylesheet is disabled, or false otherwise. */ - get disabled() - { + get disabled() { return this.domSheet.disabled; }, @@ -1228,8 +1216,7 @@ CssSheet.prototype = { * @return {boolean} true if this stylesheet matches the current browser view * media, or false otherwise. */ - get mediaMatches() - { + get mediaMatches() { if (this._mediaMatches === null) { this._mediaMatches = this._cssLogic.mediaMatches(this.domSheet); } @@ -1241,8 +1228,7 @@ CssSheet.prototype = { * * @return {string} the address of the stylesheet. */ - get href() - { + get href() { if (this._href) { return this._href; } @@ -1256,8 +1242,7 @@ CssSheet.prototype = { * * @return {string} the shorthand source of the stylesheet. */ - get shortSource() - { + get shortSource() { if (this._shortSource) { return this._shortSource; } @@ -1272,8 +1257,7 @@ CssSheet.prototype = { * @return {boolean} true if the stylesheet is allowed by the sourceFilter, or * false otherwise. */ - get sheetAllowed() - { + get sheetAllowed() { if (this._sheetAllowed !== null) { return this._sheetAllowed; } @@ -1296,8 +1280,7 @@ CssSheet.prototype = { * * @return {number} the number of nsIDOMCSSRule objects in this stylesheet. */ - get ruleCount() - { + get ruleCount() { return this._ruleCount > -1 ? this._ruleCount : this.domSheet.cssRules.length; @@ -1313,17 +1296,18 @@ CssSheet.prototype = { * @return {CssRule} the cached CssRule object for the given CSSStyleRule * object. */ - getRule: function CssSheet_getRule(aDomRule) - { - let cacheId = aDomRule.type + aDomRule.selectorText; + getRule: function(domRule) { + let cacheId = domRule.type + domRule.selectorText; let rule = null; let ruleFound = false; if (cacheId in this._rules) { - for (let i = 0, rulesLen = this._rules[cacheId].length; i < rulesLen; i++) { + for (let i = 0, rulesLen = this._rules[cacheId].length; + i < rulesLen; + i++) { rule = this._rules[cacheId][i]; - if (rule.domRule === aDomRule) { + if (rule.domRule === domRule) { ruleFound = true; break; } @@ -1335,7 +1319,7 @@ CssSheet.prototype = { this._rules[cacheId] = []; } - rule = new CssRule(this, aDomRule); + rule = new CssRule(this, domRule); this._rules[cacheId].push(rule); } @@ -1350,23 +1334,22 @@ CssSheet.prototype = { * Note that this method also iterates through @media rules inside the * stylesheet. * - * @param {function} aCallback the function you want to execute for each of + * @param {function} callback the function you want to execute for each of * the style rules. - * @param {object} aScope the scope you want for the callback function. aScope - * will be the this object when aCallback executes. + * @param {object} scope the scope you want for the callback function. scope + * will be the this object when callback executes. */ - forEachRule: function CssSheet_forEachRule(aCallback, aScope) - { + forEachRule: function(callback, scope) { let ruleCount = 0; let domRules = this.domSheet.cssRules; - function _iterator(aDomRule) { - if (aDomRule.type == Ci.nsIDOMCSSRule.STYLE_RULE) { - aCallback.call(aScope, this.getRule(aDomRule)); + function _iterator(domRule) { + if (domRule.type == Ci.nsIDOMCSSRule.STYLE_RULE) { + callback.call(scope, this.getRule(domRule)); ruleCount++; - } else if (aDomRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE && - aDomRule.cssRules && this._cssLogic.mediaMatches(aDomRule)) { - Array.prototype.forEach.call(aDomRule.cssRules, _iterator, this); + } else if (domRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE && + domRule.cssRules && this._cssLogic.mediaMatches(domRule)) { + Array.prototype.forEach.call(domRule.cssRules, _iterator, this); } } @@ -1384,30 +1367,28 @@ CssSheet.prototype = { * Note that this method also iterates through @media rules inside the * stylesheet. * - * @param {function} aCallback the function you want to execute for each of + * @param {function} callback the function you want to execute for each of * the style rules. - * @param {object} aScope the scope you want for the callback function. aScope - * will be the this object when aCallback executes. - * @return {Boolean} true if aCallback returns true during any iteration, + * @param {object} scope the scope you want for the callback function. scope + * will be the this object when callback executes. + * @return {Boolean} true if callback returns true during any iteration, * otherwise false is returned. */ - forSomeRules: function CssSheet_forSomeRules(aCallback, aScope) - { + forSomeRules: function(callback, scope) { let domRules = this.domSheet.cssRules; - function _iterator(aDomRule) { - if (aDomRule.type == Ci.nsIDOMCSSRule.STYLE_RULE) { - return aCallback.call(aScope, this.getRule(aDomRule)); - } else if (aDomRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE && - aDomRule.cssRules && this._cssLogic.mediaMatches(aDomRule)) { - return Array.prototype.some.call(aDomRule.cssRules, _iterator, this); + function _iterator(domRule) { + if (domRule.type == Ci.nsIDOMCSSRule.STYLE_RULE) { + return callback.call(scope, this.getRule(domRule)); + } else if (domRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE && + domRule.cssRules && this._cssLogic.mediaMatches(domRule)) { + return Array.prototype.some.call(domRule.cssRules, _iterator, this); } return false; } return Array.prototype.some.call(domRules, _iterator, this); }, - toString: function CssSheet_toString() - { + toString: function() { return "CssSheet[" + this.shortSource + "]"; } }; @@ -1415,22 +1396,21 @@ CssSheet.prototype = { /** * Information about a single CSSStyleRule. * - * @param {CSSSheet|null} aCssSheet the CssSheet object of the stylesheet that + * @param {CSSSheet|null} cssSheet the CssSheet object of the stylesheet that * holds the CSSStyleRule. If the rule comes from element.style, set this * argument to null. - * @param {CSSStyleRule|object} aDomRule the DOM CSSStyleRule for which you want + * @param {CSSStyleRule|object} domRule the DOM CSSStyleRule for which you want * to cache data. If the rule comes from element.style, then provide * an object of the form: {style: element.style}. - * @param {Element} [aElement] If the rule comes from element.style, then this + * @param {Element} [element] If the rule comes from element.style, then this * argument must point to the element. * @constructor */ -function CssRule(aCssSheet, aDomRule, aElement) -{ - this._cssSheet = aCssSheet; - this.domRule = aDomRule; +function CssRule(cssSheet, domRule, element) { + this._cssSheet = cssSheet; + this.domRule = domRule; - let parentRule = aDomRule.parentRule; + let parentRule = domRule.parentRule; if (parentRule && parentRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE) { this.mediaText = parentRule.media.mediaText; } @@ -1445,13 +1425,13 @@ function CssRule(aCssSheet, aDomRule, aElement) } this.href = this._cssSheet.href; this.contentRule = this._cssSheet.contentSheet; - } else if (aElement) { + } else if (element) { this._selectors = [ new CssSelector(this, "@element.style", 0) ]; this.line = -1; this.source = CssLogic.l10n("rule.sourceElement"); this.href = "#"; this.contentRule = true; - this.sourceElement = aElement; + this.sourceElement = element; } } @@ -1460,8 +1440,7 @@ CssRule.prototype = { mediaText: "", - get isMediaRule() - { + get isMediaRule() { return !!this.mediaText; }, @@ -1471,8 +1450,7 @@ CssRule.prototype = { * @return {boolean} true if the parent stylesheet is allowed by the current * sourceFilter, or false otherwise. */ - get sheetAllowed() - { + get sheetAllowed() { return this._cssSheet ? this._cssSheet.sheetAllowed : true; }, @@ -1482,33 +1460,30 @@ CssRule.prototype = { * @return {number} the parent stylesheet index/position in the viewed * document. */ - get sheetIndex() - { + get sheetIndex() { return this._cssSheet ? this._cssSheet.index : 0; }, /** * Retrieve the style property value from the current CSSStyleRule. * - * @param {string} aProperty the CSS property name for which you want the + * @param {string} property the CSS property name for which you want the * value. * @return {string} the property value. */ - getPropertyValue: function(aProperty) - { - return this.domRule.style.getPropertyValue(aProperty); + getPropertyValue: function(property) { + return this.domRule.style.getPropertyValue(property); }, /** * Retrieve the style property priority from the current CSSStyleRule. * - * @param {string} aProperty the CSS property name for which you want the + * @param {string} property the CSS property name for which you want the * priority. * @return {string} the property priority. */ - getPropertyPriority: function(aProperty) - { - return this.domRule.style.getPropertyPriority(aProperty); + getPropertyPriority: function(property) { + return this.domRule.style.getPropertyPriority(property); }, /** @@ -1517,8 +1492,7 @@ CssRule.prototype = { * * @return {array} the array hold the CssSelector objects. */ - get selectors() - { + get selectors() { if (this._selectors) { return this._selectors; } @@ -1539,8 +1513,7 @@ CssRule.prototype = { return this._selectors; }, - toString: function CssRule_toString() - { + toString: function() { return "[CssRule " + this.domRule.selectorText + "]"; }, }; @@ -1550,17 +1523,16 @@ CssRule.prototype = { * selectors. * * @constructor - * @param {CssRule} aCssRule the CssRule instance from where the selector comes. - * @param {string} aSelector The selector that we wish to investigate. - * @param {Number} aIndex The index of the selector within it's rule. + * @param {CssRule} cssRule the CssRule instance from where the selector comes. + * @param {string} selector The selector that we wish to investigate. + * @param {Number} index The index of the selector within it's rule. */ -function CssSelector(aCssRule, aSelector, aIndex) -{ - this.cssRule = aCssRule; - this.text = aSelector; +function CssSelector(cssRule, selector, index) { + this.cssRule = cssRule; + this.text = selector; this.elementStyle = this.text == "@element.style"; this._specificity = null; - this.selectorIndex = aIndex; + this.selectorIndex = index; } exports.CssSelector = CssSelector; @@ -1574,8 +1546,7 @@ CssSelector.prototype = { * * @return {string} the selector source. */ - get source() - { + get source() { return this.cssRule.source; }, @@ -1586,8 +1557,7 @@ CssSelector.prototype = { * * @return {string} the source element selector. */ - get sourceElement() - { + get sourceElement() { return this.cssRule.sourceElement; }, @@ -1597,8 +1567,7 @@ CssSelector.prototype = { * * @return {string} the address of the CssSelector. */ - get href() - { + get href() { return this.cssRule.href; }, @@ -1608,8 +1577,7 @@ CssSelector.prototype = { * @return {boolean} true if the selector comes from a content-provided * stylesheet, or false otherwise. */ - get contentRule() - { + get contentRule() { return this.cssRule.contentRule; }, @@ -1619,8 +1587,7 @@ CssSelector.prototype = { * @return {boolean} true if the parent stylesheet is allowed by the current * sourceFilter, or false otherwise. */ - get sheetAllowed() - { + get sheetAllowed() { return this.cssRule.sheetAllowed; }, @@ -1630,8 +1597,7 @@ CssSelector.prototype = { * @return {number} the parent stylesheet index/position in the viewed * document. */ - get sheetIndex() - { + get sheetIndex() { return this.cssRule.sheetIndex; }, @@ -1641,8 +1607,7 @@ CssSelector.prototype = { * @return {number} the line of the parent CSSStyleRule in the parent * stylesheet. */ - get ruleLine() - { + get ruleLine() { return this.cssRule.line; }, @@ -1654,8 +1619,7 @@ CssSelector.prototype = { * * @return {Number} The selector's specificity. */ - get specificity() - { + get specificity() { if (this.elementStyle) { // We can't ask specificity from DOMUtils as element styles don't provide // CSSStyleRule interface DOMUtils expect. However, specificity of element @@ -1674,8 +1638,7 @@ CssSelector.prototype = { return this._specificity; }, - toString: function CssSelector_toString() - { + toString: function() { return this.text; }, }; @@ -1689,14 +1652,13 @@ CssSelector.prototype = { * .matchedSelectors array. * Results are cached, for later reuse. * - * @param {CssLogic} aCssLogic Reference to the parent CssLogic instance - * @param {string} aProperty The CSS property we are gathering information for + * @param {CssLogic} cssLogic Reference to the parent CssLogic instance + * @param {string} property The CSS property we are gathering information for * @constructor */ -function CssPropertyInfo(aCssLogic, aProperty) -{ - this._cssLogic = aCssLogic; - this.property = aProperty; +function CssPropertyInfo(cssLogic, property) { + this._cssLogic = cssLogic; + this.property = property; this._value = ""; // The number of matched rules holding the this.property style property. @@ -1718,14 +1680,14 @@ CssPropertyInfo.prototype = { * @return {string} the computed style value for the current property, for the * highlighted element. */ - get value() - { + get value() { if (!this._value && this._cssLogic.computedStyle) { try { - this._value = this._cssLogic.computedStyle.getPropertyValue(this.property); + this._value = + this._cssLogic.computedStyle.getPropertyValue(this.property); } catch (ex) { - Services.console.logStringMessage('Error reading computed style for ' + - this.property); + Services.console.logStringMessage("Error reading computed style for " + + this.property); Services.console.logStringMessage(ex); } } @@ -1738,8 +1700,7 @@ CssPropertyInfo.prototype = { * * @return {number} the number of matched rules. */ - get matchedRuleCount() - { + get matchedRuleCount() { if (!this._matchedSelectors) { this._findMatchedSelectors(); } else if (this.needRefilter) { @@ -1757,8 +1718,7 @@ CssPropertyInfo.prototype = { * @return {array} the list of CssSelectorInfo objects of selectors that match * the highlighted element and its parents. */ - get matchedSelectors() - { + get matchedSelectors() { if (!this._matchedSelectors) { this._findMatchedSelectors(); } else if (this.needRefilter) { @@ -1775,8 +1735,7 @@ CssPropertyInfo.prototype = { * create CssSelectorInfo objects, which we then sort * @private */ - _findMatchedSelectors: function CssPropertyInfo_findMatchedSelectors() - { + _findMatchedSelectors: function() { this._matchedSelectors = []; this._matchedRuleCount = 0; this.needRefilter = false; @@ -1784,14 +1743,13 @@ CssPropertyInfo.prototype = { this._cssLogic.processMatchedSelectors(this._processMatchedSelector, this); // Sort the selectors by how well they match the given element. - this._matchedSelectors.sort(function(aSelectorInfo1, aSelectorInfo2) { - if (aSelectorInfo1.status > aSelectorInfo2.status) { + this._matchedSelectors.sort(function(selectorInfo1, selectorInfo2) { + if (selectorInfo1.status > selectorInfo2.status) { return -1; - } else if (aSelectorInfo2.status > aSelectorInfo1.status) { + } else if (selectorInfo2.status > selectorInfo1.status) { return 1; - } else { - return aSelectorInfo1.compareTo(aSelectorInfo2); } + return selectorInfo1.compareTo(selectorInfo2); }); // Now we know which of the matches is best, we can mark it BEST_MATCH. @@ -1805,19 +1763,18 @@ CssPropertyInfo.prototype = { * Process a matched CssSelector object. * * @private - * @param {CssSelector} aSelector the matched CssSelector object. - * @param {CssLogic.STATUS} aStatus the CssSelector match status. + * @param {CssSelector} selector the matched CssSelector object. + * @param {CssLogic.STATUS} status the CssSelector match status. */ - _processMatchedSelector: function CssPropertyInfo_processMatchedSelector(aSelector, aStatus) - { - let cssRule = aSelector.cssRule; + _processMatchedSelector: function(selector, status) { + let cssRule = selector.cssRule; let value = cssRule.getPropertyValue(this.property); if (value && - (aStatus == CssLogic.STATUS.MATCHED || - (aStatus == CssLogic.STATUS.PARENT_MATCH && + (status == CssLogic.STATUS.MATCHED || + (status == CssLogic.STATUS.PARENT_MATCH && domUtils.isInheritedProperty(this.property)))) { - let selectorInfo = new CssSelectorInfo(aSelector, this.property, value, - aStatus); + let selectorInfo = new CssSelectorInfo(selector, this.property, value, + status); this._matchedSelectors.push(selectorInfo); if (this._cssLogic._passId !== cssRule._passId && cssRule.sheetAllowed) { this._matchedRuleCount++; @@ -1830,13 +1787,12 @@ CssPropertyInfo.prototype = { * changes. This allows for quick filter changes. * @private */ - _refilterSelectors: function CssPropertyInfo_refilterSelectors() - { + _refilterSelectors: function() { let passId = ++this._cssLogic._passId; let ruleCount = 0; - let iterator = function(aSelectorInfo) { - let cssRule = aSelectorInfo.selector.cssRule; + let iterator = function(selectorInfo) { + let cssRule = selectorInfo.selector.cssRule; if (cssRule._passId != passId) { if (cssRule.sheetAllowed) { ruleCount++; @@ -1853,8 +1809,7 @@ CssPropertyInfo.prototype = { this.needRefilter = false; }, - toString: function CssPropertyInfo_toString() - { + toString: function() { return "CssPropertyInfo[" + this.property + "]"; }, }; @@ -1867,18 +1822,20 @@ CssPropertyInfo.prototype = { * objects. The information given by this object blends data coming from the * CssSheet, CssRule and from the CssSelector that own this object. * - * @param {CssSelector} aSelector The CssSelector object for which to present information. - * @param {string} aProperty The property for which information should be retrieved. - * @param {string} aValue The property value from the CssRule that owns the selector. - * @param {CssLogic.STATUS} aStatus The selector match status. + * @param {CssSelector} selector The CssSelector object for which to + * present information. + * @param {string} property The property for which information should + * be retrieved. + * @param {string} value The property value from the CssRule that owns + * the selector. + * @param {CssLogic.STATUS} status The selector match status. * @constructor */ -function CssSelectorInfo(aSelector, aProperty, aValue, aStatus) -{ - this.selector = aSelector; - this.property = aProperty; - this.status = aStatus; - this.value = aValue; +function CssSelectorInfo(selector, property, value, status) { + this.selector = selector; + this.property = property; + this.status = status; + this.value = value; let priority = this.selector.cssRule.getPropertyPriority(this.property); this.important = (priority === "important"); } @@ -1890,8 +1847,7 @@ CssSelectorInfo.prototype = { * * @return {string} the selector source. */ - get source() - { + get source() { return this.selector.source; }, @@ -1902,8 +1858,7 @@ CssSelectorInfo.prototype = { * * @return {string} the source element selector. */ - get sourceElement() - { + get sourceElement() { return this.selector.sourceElement; }, @@ -1913,8 +1868,7 @@ CssSelectorInfo.prototype = { * * @return {string} the address of the CssSelector. */ - get href() - { + get href() { return this.selector.href; }, @@ -1924,8 +1878,7 @@ CssSelectorInfo.prototype = { * @return {boolean} true if the CssSelector comes from element.style, or * false otherwise. */ - get elementStyle() - { + get elementStyle() { return this.selector.elementStyle; }, @@ -1935,8 +1888,7 @@ CssSelectorInfo.prototype = { * @return {object} an object holding specificity information for the current * selector. */ - get specificity() - { + get specificity() { return this.selector.specificity; }, @@ -1946,8 +1898,7 @@ CssSelectorInfo.prototype = { * @return {number} the parent stylesheet index/position in the viewed * document. */ - get sheetIndex() - { + get sheetIndex() { return this.selector.sheetIndex; }, @@ -1957,8 +1908,7 @@ CssSelectorInfo.prototype = { * @return {boolean} true if the parent stylesheet is allowed by the current * sourceFilter, or false otherwise. */ - get sheetAllowed() - { + get sheetAllowed() { return this.selector.sheetAllowed; }, @@ -1968,8 +1918,7 @@ CssSelectorInfo.prototype = { * @return {number} the line of the parent CSSStyleRule in the parent * stylesheet. */ - get ruleLine() - { + get ruleLine() { return this.selector.ruleLine; }, @@ -1979,8 +1928,7 @@ CssSelectorInfo.prototype = { * @return {boolean} true if the selector comes from a browser-provided * stylesheet, or false otherwise. */ - get contentRule() - { + get contentRule() { return this.selector.contentRule; }, @@ -1988,41 +1936,63 @@ CssSelectorInfo.prototype = { * Compare the current CssSelectorInfo instance to another instance, based on * specificity information. * - * @param {CssSelectorInfo} aThat The instance to compare ourselves against. - * @return number -1, 0, 1 depending on how aThat compares with this. + * @param {CssSelectorInfo} that The instance to compare ourselves against. + * @return number -1, 0, 1 depending on how that compares with this. */ - compareTo: function CssSelectorInfo_compareTo(aThat) - { - if (!this.contentRule && aThat.contentRule) return 1; - if (this.contentRule && !aThat.contentRule) return -1; + compareTo: function(that) { + if (!this.contentRule && that.contentRule) { + return 1; + } + if (this.contentRule && !that.contentRule) { + return -1; + } - if (this.elementStyle && !aThat.elementStyle) { - if (!this.important && aThat.important) return 1; - else return -1; + if (this.elementStyle && !that.elementStyle) { + if (!this.important && that.important) { + return 1; + } + return -1; } - if (!this.elementStyle && aThat.elementStyle) { - if (this.important && !aThat.important) return -1; - else return 1; + if (!this.elementStyle && that.elementStyle) { + if (this.important && !that.important) { + return -1; + } + return 1; } - if (this.important && !aThat.important) return -1; - if (aThat.important && !this.important) return 1; + if (this.important && !that.important) { + return -1; + } + if (that.important && !this.important) { + return 1; + } - if (this.specificity > aThat.specificity) return -1; - if (aThat.specificity > this.specificity) return 1; + if (this.specificity > that.specificity) { + return -1; + } + if (that.specificity > this.specificity) { + return 1; + } - if (this.sheetIndex > aThat.sheetIndex) return -1; - if (aThat.sheetIndex > this.sheetIndex) return 1; + if (this.sheetIndex > that.sheetIndex) { + return -1; + } + if (that.sheetIndex > this.sheetIndex) { + return 1; + } - if (this.ruleLine > aThat.ruleLine) return -1; - if (aThat.ruleLine > this.ruleLine) return 1; + if (this.ruleLine > that.ruleLine) { + return -1; + } + if (that.ruleLine > this.ruleLine) { + return 1; + } return 0; }, - toString: function CssSelectorInfo_toString() - { + toString: function() { return this.selector + " -> " + this.value; }, }; diff --git a/devtools/shared/tests/unit/test_prettifyCSS.js b/devtools/shared/tests/unit/test_prettifyCSS.js index 176750be7fafac0cf1f6ef05abca1ad2ee4f176d..f854cc934c9011233a4de7ba9365786cd5b40a55 100644 --- a/devtools/shared/tests/unit/test_prettifyCSS.js +++ b/devtools/shared/tests/unit/test_prettifyCSS.js @@ -7,7 +7,6 @@ const {CssLogic} = require("devtools/shared/styleinspector/css-logic"); - const TESTS = [ { name: "simple test", input: "div { font-family:'Arial Black', Arial, sans-serif; }", @@ -29,7 +28,6 @@ const TESTS = [ { name: "minified with trailing newline", input: "\nbody{background:white;}div{font-size:4em;color:red}span{color:green;}\n", expected: [ - "", "body {", "\tbackground:white;", "}", @@ -43,6 +41,14 @@ const TESTS = [ ] }, + { name: "leading whitespace", + input: "\n div{color: red;}", + expected: [ + "div {", + "\tcolor: red;", + "}" + ] + }, ]; function run_test() { diff --git a/devtools/shared/webconsole/js-property-provider.js b/devtools/shared/webconsole/js-property-provider.js index e19d7501ec7d922aa8e87b537af36af81f08864b..812c0d176ac337ff1a868dc65e95627e38e74c54 100644 --- a/devtools/shared/webconsole/js-property-provider.js +++ b/devtools/shared/webconsole/js-property-provider.js @@ -238,12 +238,18 @@ function JSPropertyProvider(aDbgObject, anEnvironment, aInputValue, aCursor) // We get the rest of the properties recursively starting from the Debugger.Object // that wraps the first property - for (let prop of properties) { - prop = prop.trim(); + for (let i = 0; i < properties.length; i++) { + let prop = properties[i].trim(); if (!prop) { return null; } + // Special case for 'this' since it's not part of the global's properties + // but we want autocompletion to work properly for it + if (prop === "this" && obj === aDbgObject && i === 0) { + continue; + } + if (/\[\d+\]$/.test(prop)) { // The property to autocomplete is a member of array. For example // list[i][j]..[n]. Traverse the array to get the actual element. @@ -263,7 +269,15 @@ function JSPropertyProvider(aDbgObject, anEnvironment, aInputValue, aCursor) return getMatchedProps(obj, matchProp); } - return getMatchedPropsInDbgObject(obj, matchProp); + let matchedProps = getMatchedPropsInDbgObject(obj, matchProp); + if (properties.length !== 0 || obj !== aDbgObject) { + let thisInd = matchedProps.matches.indexOf("this"); + if (thisInd > -1) { + matchedProps.matches.splice(thisInd, 1) + } + } + + return matchedProps; } /** @@ -482,7 +496,17 @@ var DebuggerObjectSupport = { getProperties: function(aObj) { - return aObj.getOwnPropertyNames(); + let names = aObj.getOwnPropertyNames(); + // Include 'this' in results (in sorted order). It will be removed + // in all cases except for the first property request on the global, but + // it needs to be added now so it can be filtered based on string input. + for (let i = 0; i < names.length; i++) { + if (i === names.length - 1 || names[i+1] > "this") { + names.splice(i+1, 0, "this"); + break; + } + } + return names; }, getProperty: function(aObj, aName, aRootObj) diff --git a/devtools/shared/webconsole/test/unit/test_js_property_provider.js b/devtools/shared/webconsole/test/unit/test_js_property_provider.js index d2d001458862802ee3a42216e357784f4e391c1e..e00cec1d0013c2ff01d84437511f6f0a7732959f 100644 --- a/devtools/shared/webconsole/test/unit/test_js_property_provider.js +++ b/devtools/shared/webconsole/test/unit/test_js_property_provider.js @@ -33,8 +33,8 @@ function run_test() { Components.utils.evalInSandbox(testObject, sandbox); Components.utils.evalInSandbox(testHyphenated, sandbox); - let results = JSPropertyProvider(dbgObject, null, "testArray[0]."); do_print("Test that suggestions are given for 'foo[n]' where n is an integer."); + let results = JSPropertyProvider(dbgObject, null, "testArray[0]."); test_has_result(results, "propA"); do_print("Test that suggestions are given for multidimensional arrays."); @@ -49,6 +49,18 @@ function run_test() { results = JSPropertyProvider(dbgObject, null, "[1,2,3,\n4\n]."); test_has_result(results, "indexOf"); + do_print("Test that suggestions are given for 'this'"); + results = JSPropertyProvider(dbgObject, null, "t"); + test_has_result(results, "this"); + + do_print("Test that suggestions are given for 'this.'"); + results = JSPropertyProvider(dbgObject, null, "this."); + test_has_result(results, "testObject"); + + do_print("Test that no suggestions are given for 'this.this'"); + results = JSPropertyProvider(dbgObject, null, "this.this"); + test_has_no_results(results); + do_print("Test that suggestions are given for literal strings."); results = JSPropertyProvider(dbgObject, null, "'foo'."); test_has_result(results, "charAt"); diff --git a/dom/browser-element/BrowserElementPromptService.jsm b/dom/browser-element/BrowserElementPromptService.jsm index 7686ea3ca88b6b204afbb834e3ea08d8e2fdc701..a70ee28f892087392773cd78f7bc42f9d1b883b5 100644 --- a/dom/browser-element/BrowserElementPromptService.jsm +++ b/dom/browser-element/BrowserElementPromptService.jsm @@ -429,20 +429,12 @@ BrowserElementAuthPrompt.prototype = { return [hostname, realm]; }, + /** + * Strip out things like userPass and path for display. + */ _getFormattedHostname : function(uri) { - let scheme = uri.scheme; - let hostname = scheme + "://" + uri.host; - - // If the URI explicitly specified a port, only include it when - // it's not the default. (We never want "http://foo.com:80") - let port = uri.port; - if (port != -1) { - let handler = Services.io.getProtocolHandler(scheme); - if (port != handler.defaultPort) - hostname += ":" + port; - } - return hostname; - } + return uri.scheme + "://" + uri.hostPort; + }, }; diff --git a/mobile/android/base/AppConstants.java.in b/mobile/android/base/AppConstants.java.in index 0a8da2369996f1847fe244f65cf8acc16a9f2823..d9c3cdf57c4c9901b7d2f073bdd58c57cdcbc5b9 100644 --- a/mobile/android/base/AppConstants.java.in +++ b/mobile/android/base/AppConstants.java.in @@ -346,13 +346,6 @@ public class AppConstants { false; //#endif - public static final boolean MOZ_ANDROID_NATIVE_ACCOUNT_UI = -//#ifdef MOZ_ANDROID_NATIVE_ACCOUNT_UI - true; -//#else - false; -//#endif - /** * Target CPU architecture: "armeabi-v7a", "x86, "mips", .. */ diff --git a/mobile/android/base/android-services.mozbuild b/mobile/android/base/android-services.mozbuild index a49d2a6bc43eb22a97c2447d1607caffd072ca4d..8bff31d21ebc057af9239559fe85f45ce7a9d4f4 100644 --- a/mobile/android/base/android-services.mozbuild +++ b/mobile/android/base/android-services.mozbuild @@ -791,7 +791,6 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil 'background/fxa/FxAccount10CreateDelegate.java', 'background/fxa/FxAccount20CreateDelegate.java', 'background/fxa/FxAccount20LoginDelegate.java', - 'background/fxa/FxAccountAgeLockoutHelper.java', 'background/fxa/FxAccountClient.java', 'background/fxa/FxAccountClient10.java', 'background/fxa/FxAccountClient20.java', @@ -852,23 +851,12 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil 'fxa/AccountLoader.java', 'fxa/activities/CustomColorPreference.java', 'fxa/activities/FxAccountAbstractActivity.java', - 'fxa/activities/FxAccountAbstractSetupActivity.java', - 'fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java', - 'fxa/activities/FxAccountConfirmAccountActivity.java', 'fxa/activities/FxAccountConfirmAccountActivityWeb.java', - 'fxa/activities/FxAccountCreateAccountActivity.java', - 'fxa/activities/FxAccountCreateAccountNotAllowedActivity.java', - 'fxa/activities/FxAccountFinishMigratingActivity.java', 'fxa/activities/FxAccountFinishMigratingActivityWeb.java', - 'fxa/activities/FxAccountGetStartedActivity.java', 'fxa/activities/FxAccountGetStartedActivityWeb.java', - 'fxa/activities/FxAccountMigrationFinishedActivity.java', - 'fxa/activities/FxAccountSignInActivity.java', 'fxa/activities/FxAccountStatusActivity.java', 'fxa/activities/FxAccountStatusFragment.java', - 'fxa/activities/FxAccountUpdateCredentialsActivity.java', 'fxa/activities/FxAccountUpdateCredentialsActivityWeb.java', - 'fxa/activities/FxAccountVerifiedAccountActivity.java', 'fxa/activities/FxAccountWebFlowActivity.java', 'fxa/activities/PicassoPreferenceIconTarget.java', 'fxa/authenticator/AccountPickler.java', @@ -905,11 +893,6 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil 'fxa/sync/FxAccountSyncStatusHelper.java', 'fxa/sync/SchedulePolicy.java', 'fxa/SyncStatusListener.java', - 'fxa/tasks/FxAccountCodeResender.java', - 'fxa/tasks/FxAccountCreateAccountTask.java', - 'fxa/tasks/FxAccountSetupTask.java', - 'fxa/tasks/FxAccountSignInTask.java', - 'fxa/tasks/FxAccountUnlockCodeResender.java', 'sync/AlreadySyncingException.java', 'sync/BackoffHandler.java', 'sync/BadRequiredFieldJSONException.java', diff --git a/mobile/android/base/home/RemoteTabsStaticFragment.java b/mobile/android/base/home/RemoteTabsStaticFragment.java index bbf729ac2d75c440e2e30c79b423ac5f63991ae0..54014f4a9346115865b292e8f81e851e84463107 100644 --- a/mobile/android/base/home/RemoteTabsStaticFragment.java +++ b/mobile/android/base/home/RemoteTabsStaticFragment.java @@ -107,8 +107,9 @@ public class RemoteTabsStaticFragment extends HomeFragment implements OnClickLis final EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.noneOf(OnUrlOpenListener.Flags.class); mUrlOpenListener.onUrlOpen(url, flags); } else if (id == R.id.remote_tabs_needs_verification_resend_email) { - // Send a fresh email; this displays a toast, so the user gets feedback. - FirefoxAccounts.resendVerificationEmail(getActivity()); + final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_CONFIRM_ACCOUNT); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); } else if (id == R.id.remote_tabs_needs_verification_help) { // Don't allow switch-to-tab. final EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.noneOf(OnUrlOpenListener.Flags.class); diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd index f11e3558a84df13122867e27cf369a68c7e87e80..d60dd3106414a1da5e4062f5e9f05aa39db87f2d 100644 --- a/mobile/android/base/locales/en-US/android_strings.dtd +++ b/mobile/android/base/locales/en-US/android_strings.dtd @@ -191,6 +191,21 @@ <!ENTITY pref_home_suggested_sites_summary "Display shortcuts to sites on your homepage that we think you might find interesting"> <!ENTITY pref_category_home_homepage "Homepage"> <!ENTITY home_homepage_title "Set a Homepage"> +<!-- Localization note (home_homepage_radio_user_address): The user will see a series of radio + buttons to choose the homepage they'd like to start on. When they click the radio + button for this string, they will use the built-in default Firefox homepage (about:home). --> +<!ENTITY home_homepage_radio_default "&brandShortName; Home"> +<!-- Localization note (home_homepage_radio_user_address): The user will see a series of radio + buttons to choose the homepage they'd like to start on. When they click the radio + button for this string, a text field will appear below the radio button and allow the + user to insert an address of their choice. --> +<!ENTITY home_homepage_radio_user_address "Custom"> +<!-- Localization note (home_homepage_hint_user_address): The user will see a series of + radio buttons to choose the homepage they'd like to start on. When they click a + particular radio button, a text field will appear below the radio button and allow the + user to insert an address of their choice. This string is the hint text to that + text field. --> +<!ENTITY home_homepage_hint_user_address "Enter address or search term"> <!-- Localization note: These are shown in the left sidebar on tablets --> <!ENTITY pref_header_general "General"> @@ -246,6 +261,9 @@ <!ENTITY pref_char_encoding_on "Show menu"> <!ENTITY pref_char_encoding_off "Don\'t show menu"> <!ENTITY pref_clear_private_data "Clear Private Data"> +<!-- Localization note (pref_clear_private_data_now_tablet): This action to clear private data is only shown on tablets. + The action is shown below a header saying "Clear private data"; See pref_clear_private_data --> +<!ENTITY pref_clear_private_data_now_tablet "Clear now"> <!ENTITY pref_clear_on_exit_title3 "Clear private data on exit"> <!ENTITY pref_clear_on_exit_summary2 "&brandShortName; will automatically clear your data whenever you select \u0022Quit\u0022 from the main menu"> <!ENTITY pref_clear_on_exit_dialog_title "Select which data to clear"> diff --git a/mobile/android/base/preferences/SetHomepagePreference.java b/mobile/android/base/preferences/SetHomepagePreference.java index 192553d01e47e4f20392b59c68f363ae662700ff..b93cd06c75e8493cdf551e6d235b2b116242f1aa 100644 --- a/mobile/android/base/preferences/SetHomepagePreference.java +++ b/mobile/android/base/preferences/SetHomepagePreference.java @@ -7,7 +7,6 @@ package org.mozilla.gecko.preferences; import org.mozilla.gecko.AboutPages; import org.mozilla.gecko.GeckoSharedPrefs; import org.mozilla.gecko.R; -import org.mozilla.gecko.Tabs; import android.app.AlertDialog; import android.content.Context; @@ -17,13 +16,23 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.view.inputmethod.InputMethodManager; -import android.widget.CheckBox; -import android.widget.CompoundButton; import android.widget.EditText; +import android.widget.RadioButton; +import android.widget.RadioGroup; public class SetHomepagePreference extends DialogPreference { - SharedPreferences prefs; - EditText homepageTextEdit; + private static final String DEFAULT_HOMEPAGE = AboutPages.HOME; + + private final SharedPreferences prefs; + + private RadioGroup homepageLayout; + private RadioButton defaultRadio; + private RadioButton userAddressRadio; + private EditText homepageEditText; + + // This is the url that 1) was loaded from prefs or, 2) stored + // when the user pressed the "default homepage" checkbox. + private String storedUrl; public SetHomepagePreference(final Context context, final AttributeSet attrs) { super(context, attrs); @@ -44,39 +53,75 @@ public class SetHomepagePreference extends DialogPreference { protected void onBindDialogView(final View view) { super.onBindDialogView(view); - homepageTextEdit = (EditText) view.findViewById(R.id.homepage_url); - homepageTextEdit.requestFocus(); + homepageLayout = (RadioGroup) view.findViewById(R.id.homepage_layout); + defaultRadio = (RadioButton) view.findViewById(R.id.radio_default); + userAddressRadio = (RadioButton) view.findViewById(R.id.radio_user_address); + homepageEditText = (EditText) view.findViewById(R.id.edittext_user_address); + + storedUrl = prefs.getString(GeckoPreferences.PREFS_HOMEPAGE, DEFAULT_HOMEPAGE); - homepageTextEdit.post(new Runnable() { + homepageLayout.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override - public void run() { - InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(homepageTextEdit, InputMethodManager.SHOW_IMPLICIT); - homepageTextEdit.selectAll(); + public void onCheckedChanged(final RadioGroup radioGroup, final int checkedId) { + if (checkedId == R.id.radio_user_address) { + homepageEditText.setVisibility(View.VISIBLE); + openKeyboardAndSelectAll(getContext(), homepageEditText); + } else { + homepageEditText.setVisibility(View.GONE); + } } }); + setUIState(storedUrl); + } - final String url = prefs.getString(GeckoPreferences.PREFS_HOMEPAGE, AboutPages.HOME); - if (!AboutPages.HOME.equals(url)) { - homepageTextEdit.setText(url); + private void setUIState(final String url) { + if (isUrlDefaultHomepage(url)) { + defaultRadio.setChecked(true); + } else { + userAddressRadio.setChecked(true); + homepageEditText.setText(url); } } + private boolean isUrlDefaultHomepage(final String url) { + return TextUtils.isEmpty(url) || DEFAULT_HOMEPAGE.equals(url); + } + + private static void openKeyboardAndSelectAll(final Context context, final View viewToFocus) { + viewToFocus.requestFocus(); + viewToFocus.post(new Runnable() { + @Override + public void run() { + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(viewToFocus, InputMethodManager.SHOW_IMPLICIT); + // android:selectAllOnFocus doesn't work for the initial focus: + // I'm not sure why. We manually selectAll instead. + if (viewToFocus instanceof EditText) { + ((EditText) viewToFocus).selectAll(); + } + } + }); + } + @Override protected void onDialogClosed(final boolean positiveResult) { super.onDialogClosed(positiveResult); if (positiveResult) { final SharedPreferences.Editor editor = prefs.edit(); - String newValue = homepageTextEdit.getText().toString(); - if (AboutPages.HOME.equals(newValue) || TextUtils.isEmpty(newValue)) { + final String homePageEditTextValue = homepageEditText.getText().toString(); + final String newPrefValue; + if (homepageLayout.getCheckedRadioButtonId() == R.id.radio_default || + isUrlDefaultHomepage(homePageEditTextValue)) { + newPrefValue = ""; editor.remove(GeckoPreferences.PREFS_HOMEPAGE); - newValue = ""; } else { - editor.putString(GeckoPreferences.PREFS_HOMEPAGE, newValue); + newPrefValue = homePageEditTextValue; + editor.putString(GeckoPreferences.PREFS_HOMEPAGE, newPrefValue); } editor.apply(); + if (getOnPreferenceChangeListener() != null) { - getOnPreferenceChangeListener().onPreferenceChange(this, newValue); + getOnPreferenceChangeListener().onPreferenceChange(this, newPrefValue); } } } diff --git a/mobile/android/base/resources/layout/preference_set_homepage.xml b/mobile/android/base/resources/layout/preference_set_homepage.xml index aa9d6cf7924ab5c8e1a0f0aaf349c5762ed75a54..d30221a230a80b03065d16a07fd17ec45121deda 100644 --- a/mobile/android/base/resources/layout/preference_set_homepage.xml +++ b/mobile/android/base/resources/layout/preference_set_homepage.xml @@ -2,18 +2,37 @@ <!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingLeft="20dp" - android:paddingRight="20dp" - android:orientation="vertical"> - - <EditText - android:id="@+id/homepage_url" - android:layout_width="fill_parent" +<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/homepage_layout" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:inputType="textUri" - android:layout_marginTop="8dp" /> + android:paddingLeft="20dp" + android:paddingRight="20dp" + android:orientation="vertical"> + + <RadioButton android:id="@+id/radio_default" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/home_homepage_radio_default" + android:textColor="@color/text_and_tabs_tray_grey"/> + + <RadioButton android:id="@+id/radio_user_address" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/home_homepage_radio_user_address" + android:textColor="@color/text_and_tabs_tray_grey"/> + + <!-- RadioGroup is a LinearLayout under the hood, so including this View is fine. + The visibility changes with RadioButton state so we hide it to start. --> + <EditText android:id="@+id/edittext_user_address" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:inputType="textUri" + android:hint="@string/home_homepage_hint_user_address" + android:textColorHint="@color/disabled_grey" + android:visibility="gone"/> -</LinearLayout> +</RadioGroup> diff --git a/mobile/android/base/resources/xml-v11/preference_headers.xml b/mobile/android/base/resources/xml-v11/preference_headers.xml index 2148f18a3bb5d510c3bd82adcfe53c038aa0dd6d..c67551f6001db5ca75cc2fabaa01799c125568a5 100644 --- a/mobile/android/base/resources/xml-v11/preference_headers.xml +++ b/mobile/android/base/resources/xml-v11/preference_headers.xml @@ -44,6 +44,12 @@ android:value="preferences_advanced"/> </header> + <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" + android:title="@string/pref_clear_private_data_now"> + <extra android:name="resource" + android:value="preferences_privacy_clear_tablet"/> + </header> + <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" android:title="@string/pref_header_vendor"> <extra android:name="resource" diff --git a/mobile/android/base/resources/xml-v11/preferences_general_tablet.xml b/mobile/android/base/resources/xml-v11/preferences_general_tablet.xml index be1b29ea24d784ed5a3fe23e2ab4ef7b1c88ed5d..066b980d90a96177715d559f3d083a84c26dad15 100644 --- a/mobile/android/base/resources/xml-v11/preferences_general_tablet.xml +++ b/mobile/android/base/resources/xml-v11/preferences_general_tablet.xml @@ -8,7 +8,6 @@ Changes to preferences should be mirrored to preferences_general.xml. --> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:gecko="http://schemas.android.com/apk/res-auto" android:title="@string/pref_category_general" android:enabled="false"> @@ -41,15 +40,5 @@ android:summary="@string/pref_open_external_urls_privately_summary" android:defaultValue="false" /> - <org.mozilla.gecko.preferences.PrivateDataPreference - android:key="android.not_a_preference.privacy.clear" - android:title="@string/pref_clear_private_data_now" - android:persistent="true" - android:positiveButtonText="@string/button_clear_data" - gecko:entries="@array/pref_private_data_entries" - gecko:entryValues="@array/pref_private_data_values" - gecko:entryKeys="@array/pref_private_data_keys" - gecko:initialValues="@array/pref_private_data_defaults" /> - </PreferenceScreen> diff --git a/mobile/android/base/resources/xml-v11/preferences_privacy_clear_tablet.xml b/mobile/android/base/resources/xml-v11/preferences_privacy_clear_tablet.xml new file mode 100644 index 0000000000000000000000000000000000000000..49cc895c4c0585f0b6ce240a6f268e6b9e09e286 --- /dev/null +++ b/mobile/android/base/resources/xml-v11/preferences_privacy_clear_tablet.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public +- License, v. 2.0. If a copy of the MPL was not distributed with this +- file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:gecko="http://schemas.android.com/apk/res-auto"> + <org.mozilla.gecko.preferences.PrivateDataPreference + android:key="android.not_a_preference.privacy.clear" + android:title="@string/pref_clear_private_data_now_tablet" + android:persistent="true" + android:positiveButtonText="@string/button_clear_data" + gecko:entries="@array/pref_private_data_entries" + gecko:entryValues="@array/pref_private_data_values" + gecko:entryKeys="@array/pref_private_data_keys" + gecko:initialValues="@array/pref_private_data_defaults" /> +</PreferenceScreen> diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in index 56ed4bb3586ab49fc2760b06668bb81da206fdf2..c9d04a81554bfd8951791e6d238a7341531313ce 100644 --- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -184,6 +184,9 @@ <string name="pref_home_suggested_sites_summary">&pref_home_suggested_sites_summary;</string> <string name="pref_category_home_homepage">&pref_category_home_homepage;</string> <string name="home_homepage_title">&home_homepage_title;</string> + <string name="home_homepage_radio_default">&home_homepage_radio_default;</string> + <string name="home_homepage_radio_user_address">&home_homepage_radio_user_address;</string> + <string name="home_homepage_hint_user_address">&home_homepage_hint_user_address;</string> <string name="pref_header_general">&pref_header_general;</string> <string name="pref_header_search">&pref_header_search;</string> @@ -224,6 +227,7 @@ <string name="pref_char_encoding_on">&pref_char_encoding_on;</string> <string name="pref_char_encoding_off">&pref_char_encoding_off;</string> <string name="pref_clear_private_data_now">&pref_clear_private_data;</string> + <string name="pref_clear_private_data_now_tablet">&pref_clear_private_data_now_tablet;</string> <string name="pref_clear_on_exit_title">&pref_clear_on_exit_title3;</string> <string name="pref_clear_on_exit_summary2">&pref_clear_on_exit_summary2;</string> <string name="pref_clear_on_exit_dialog_title">&pref_clear_on_exit_dialog_title;</string> diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 2cbd4e9ae19ab03cfca8f4ebb372b0573d6c1cbf..536506b33b92a21e210ad1fa314cf9143008532f 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -459,7 +459,7 @@ var BrowserApp = { Messaging.addListener(this.getHistory.bind(this), "Session:GetHistory"); function showFullScreenWarning() { - NativeWindow.toast.show(Strings.browser.GetStringFromName("alertFullScreenToast"), "short"); + Snackbars.show(Strings.browser.GetStringFromName("alertFullScreenToast"), Snackbars.LENGTH_SHORT); } window.addEventListener("fullscreen", function() { @@ -563,20 +563,18 @@ var BrowserApp = { InitLater(() => AccessFu.attach(window), window, "AccessFu"); } - if (!AppConstants.MOZ_ANDROID_NATIVE_ACCOUNT_UI) { - // We can't delay registering WebChannel listeners: if the first page is - // about:accounts, which can happen when starting the Firefox Account flow - // from the first run experience, or via the Firefox Account Status - // Activity, we can and do miss messages from the fxa-content-server. - // However, we never allow suitably restricted profiles from listening to - // fxa-content-server messages. - if (ParentalControls.isAllowed(ParentalControls.MODIFY_ACCOUNTS)) { - console.log("browser.js: loading Firefox Accounts WebChannel"); - Cu.import("resource://gre/modules/FxAccountsWebChannel.jsm"); - EnsureFxAccountsWebChannel(); - } else { - console.log("browser.js: not loading Firefox Accounts WebChannel; this profile cannot connect to Firefox Accounts."); - } + // We can't delay registering WebChannel listeners: if the first page is + // about:accounts, which can happen when starting the Firefox Account flow + // from the first run experience, or via the Firefox Account Status + // Activity, we can and do miss messages from the fxa-content-server. + // However, we never allow suitably restricted profiles from listening to + // fxa-content-server messages. + if (ParentalControls.isAllowed(ParentalControls.MODIFY_ACCOUNTS)) { + console.log("browser.js: loading Firefox Accounts WebChannel"); + Cu.import("resource://gre/modules/FxAccountsWebChannel.jsm"); + EnsureFxAccountsWebChannel(); + } else { + console.log("browser.js: not loading Firefox Accounts WebChannel; this profile cannot connect to Firefox Accounts."); } // Notify Java that Gecko has loaded. @@ -1267,7 +1265,7 @@ var BrowserApp = { // Calling this will update the state in BrowserApp after a tab has been // closed in the Java UI. - _handleTabClosed: function _handleTabClosed(aTab, aShowUndoToast) { + _handleTabClosed: function _handleTabClosed(aTab, aShowUndoSnackbar) { if (aTab == this.selectedTab) this.selectedTab = null; @@ -1277,8 +1275,8 @@ var BrowserApp = { evt.initUIEvent("TabClose", true, false, window, tabIndex); aTab.browser.dispatchEvent(evt); - if (aShowUndoToast) { - // Get a title for the undo close toast. Fall back to the URL if there is no title. + if (aShowUndoSnackbar) { + // Get a title for the undo close snackbar. Fall back to the URL if there is no title. let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); let closedTabData = ss.getClosedTabs(window)[0]; @@ -3085,7 +3083,7 @@ var NativeWindow = { _copyStringToDefaultClipboard: function(aString) { let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper); clipboard.copyString(aString); - NativeWindow.toast.show(Strings.browser.GetStringFromName("selectionHelper.textCopied"), "short"); + Snackbars.show(Strings.browser.GetStringFromName("selectionHelper.textCopied"), Snackbars.LENGTH_SHORT); }, _stripScheme: function(aString) { @@ -5877,7 +5875,7 @@ var XPInstallObserver = { switch (aTopic) { case "addon-install-started": - NativeWindow.toast.show(strings.GetStringFromName("alertAddonsDownloading"), "short"); + Snackbars.show(strings.GetStringFromName("alertAddonsDownloading"), Snackbars.LENGTH_SHORT); break; case "addon-install-disabled": { if (!tab) @@ -6920,7 +6918,7 @@ var SearchEngines = { Services.search.addEngine(engine.url, Ci.nsISearchEngine.DATA_XML, engine.iconURL, false, { onSuccess: function() { // Display a toast confirming addition of new search engine. - NativeWindow.toast.show(Strings.browser.formatStringFromName("alertSearchEngineAddedToast", [engine.title], 1), "long"); + Snackbars.show(Strings.browser.formatStringFromName("alertSearchEngineAddedToast", [engine.title], 1), Snackbars.LENGTH_SHORT); }, onError: function(aCode) { @@ -6934,7 +6932,7 @@ var SearchEngines = { errorMessage = "alertSearchEngineErrorToast"; } - NativeWindow.toast.show(Strings.browser.formatStringFromName(errorMessage, [engine.title], 1), "long"); + Snackbars.show(Strings.browser.formatStringFromName(errorMessage, [engine.title], 1), Snackbars.LENGTH_SHORT); } }); }, @@ -7015,7 +7013,7 @@ var SearchEngines = { name = title.value + " " + i; Services.search.addEngineWithDetails(name, favicon, null, null, method, formURL); - NativeWindow.toast.show(Strings.browser.formatStringFromName("alertSearchEngineAddedToast", [name], 1), "long"); + Snackbars.show(Strings.browser.formatStringFromName("alertSearchEngineAddedToast", [name], 1), Snackbars.LENGTH_SHORT); let engine = Services.search.getEngineByName(name); engine.wrappedJSObject._queryCharset = charset; for (let i = 0; i < formData.length; ++i) { diff --git a/mobile/android/chrome/jar.mn b/mobile/android/chrome/jar.mn index 74b7b10fdad76ca8ef4b1b53b36aa8ebfbf19ff8..c555eb2e51fea90fb19bcb26161a82b5eab9d871 100644 --- a/mobile/android/chrome/jar.mn +++ b/mobile/android/chrome/jar.mn @@ -60,10 +60,8 @@ chrome.jar: content/aboutDevices.xhtml (content/aboutDevices.xhtml) content/aboutDevices.js (content/aboutDevices.js) #endif -#ifndef MOZ_ANDROID_NATIVE_ACCOUNT_UI content/aboutAccounts.xhtml (content/aboutAccounts.xhtml) content/aboutAccounts.js (content/aboutAccounts.js) -#endif content/aboutLogins.xhtml (content/aboutLogins.xhtml) content/aboutLogins.js (content/aboutLogins.js) #ifdef NIGHTLY_BUILD diff --git a/mobile/android/components/AboutRedirector.js b/mobile/android/components/AboutRedirector.js index 6b544258cf403c170d36570679de498cd2519e01..bc53b426678ee165d4bbed7c5dc056e35bb910cf 100644 --- a/mobile/android/components/AboutRedirector.js +++ b/mobile/android/components/AboutRedirector.js @@ -73,7 +73,11 @@ var modules = { uri: "chrome://browser/content/aboutLogins.xhtml", privileged: true }, -} + accounts: { + uri: "chrome://browser/content/aboutAccounts.xhtml", + privileged: true + }, +}; if (AppConstants.MOZ_SERVICES_HEALTHREPORT) { modules['healthreport'] = { @@ -87,12 +91,6 @@ if (AppConstants.MOZ_DEVICES) { privileged: true }; } -if (!AppConstants.MOZ_ANDROID_NATIVE_ACCOUNT_UI) { - modules['accounts'] = { - uri: "chrome://browser/content/aboutAccounts.xhtml", - privileged: true - }; -} function AboutRedirector() {} AboutRedirector.prototype = { diff --git a/mobile/android/components/LoginManagerPrompter.js b/mobile/android/components/LoginManagerPrompter.js index 1eddcb424e2431cf851b2f98b9ae438663e4850e..70fa3687118ea7c5a5004c8ea40905bcdf99d8ab 100644 --- a/mobile/android/components/LoginManagerPrompter.js +++ b/mobile/android/components/LoginManagerPrompter.js @@ -400,38 +400,6 @@ LoginManagerPrompter.prototype = { } return username.replace(/['"]/g, ""); }, - - /* - * _getFormattedHostname - * - * The aURI parameter may either be a string uri, or an nsIURI instance. - * - * Returns the hostname to use in a nsILoginInfo object (for example, - * "http://example.com"). - */ - _getFormattedHostname : function (aURI) { - var uri; - if (aURI instanceof Ci.nsIURI) { - uri = aURI; - } else { - uri = Services.io.newURI(aURI, null, null); - } - var scheme = uri.scheme; - - var hostname = scheme + "://" + uri.host; - - // If the URI explicitly specified a port, only include it when - // it's not the default. (We never want "http://foo.com:80") - let port = uri.port; - if (port != -1) { - var handler = Services.io.getProtocolHandler(scheme); - if (port != handler.defaultPort) - hostname += ":" + port; - } - - return hostname; - }, - }; // end of LoginManagerPrompter implementation diff --git a/mobile/android/components/MobileComponents.manifest b/mobile/android/components/MobileComponents.manifest index 018357bdbdde9e6b49baa100b4da1bf2c3c0e466..81498bf310681a0897032b538c49ebe216a8247d 100644 --- a/mobile/android/components/MobileComponents.manifest +++ b/mobile/android/components/MobileComponents.manifest @@ -20,9 +20,7 @@ contract @mozilla.org/network/protocol/about;1?what=blocked {322ba47e-7047-4f71- #ifdef MOZ_DEVICES contract @mozilla.org/network/protocol/about;1?what=devices {322ba47e-7047-4f71-aebf-cb7d69325cd9} #endif -#ifndef MOZ_ANDROID_NATIVE_ACCOUNT_UI contract @mozilla.org/network/protocol/about;1?what=accounts {322ba47e-7047-4f71-aebf-cb7d69325cd9} -#endif contract @mozilla.org/network/protocol/about;1?what=logins {322ba47e-7047-4f71-aebf-cb7d69325cd9} # DirectoryProvider.js diff --git a/mobile/android/components/PromptService.js b/mobile/android/components/PromptService.js index c9b5c4a3a466d1c1803df3af7938920f17cba657..2a2c3d29338daaa569df886fb60d62411106572b 100644 --- a/mobile/android/components/PromptService.js +++ b/mobile/android/components/PromptService.js @@ -798,19 +798,11 @@ var PromptUtils = { aAuthInfo.password = password; }, + /** + * Strip out things like userPass and path for display. + */ getFormattedHostname : function pu_getFormattedHostname(uri) { - let scheme = uri.scheme; - let hostname = scheme + "://" + uri.host; - - // If the URI explicitly specified a port, only include it when - // it's not the default. (We never want "http://foo.com:80") - let port = uri.port; - if (port != -1) { - let handler = Services.io.getProtocolHandler(scheme); - if (port != handler.defaultPort) - hostname += ":" + port; - } - return hostname; + return uri.scheme + "://" + uri.hostPort; }, fireDialogEvent: function(aDomWin, aEventName) { diff --git a/mobile/android/locales/jar.mn b/mobile/android/locales/jar.mn index 37bd4ca387ed537c8f1f7ba4c8fe9ac2b75580c5..1989ae4d6c0733a6b7e2549bac271db6cefc8ca0 100644 --- a/mobile/android/locales/jar.mn +++ b/mobile/android/locales/jar.mn @@ -7,10 +7,8 @@ @AB_CD@.jar: % locale browser @AB_CD@ %locale/@AB_CD@/browser/ locale/@AB_CD@/browser/about.dtd (%chrome/about.dtd) -#ifndef MOZ_ANDROID_NATIVE_ACCOUNT_UI locale/@AB_CD@/browser/aboutAccounts.dtd (%chrome/aboutAccounts.dtd) locale/@AB_CD@/browser/aboutAccounts.properties (%chrome/aboutAccounts.properties) -#endif locale/@AB_CD@/browser/aboutAddons.dtd (%chrome/aboutAddons.dtd) locale/@AB_CD@/browser/aboutAddons.properties (%chrome/aboutAddons.properties) #ifdef MOZ_DEVICES diff --git a/mobile/android/services/manifests/FxAccountAndroidManifest_activities.xml.in b/mobile/android/services/manifests/FxAccountAndroidManifest_activities.xml.in index 90095c5ef395c482563b57cf50f4a182f4c267be..22f409dbf05415852c2848ad446bf3046bf9dc75 100644 --- a/mobile/android/services/manifests/FxAccountAndroidManifest_activities.xml.in +++ b/mobile/android/services/manifests/FxAccountAndroidManifest_activities.xml.in @@ -18,106 +18,6 @@ </intent-filter> </activity> - <activity - android:theme="@style/FxAccountTheme" - android:label="@string/sync_app_name" - android:clearTaskOnLaunch="true" - android:taskAffinity="@ANDROID_PACKAGE_NAME@.FXA" - android:name="org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity" - android:configChanges="locale|layoutDirection" - android:windowSoftInputMode="adjustResize"> - <!-- Adding a launcher will make this activity appear on the - Apps screen, which we only want when testing. --> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <!-- <category android:name="android.intent.category.LAUNCHER" /> --> - </intent-filter> -#ifdef MOZ_ANDROID_NATIVE_ACCOUNT_UI - <intent-filter> - <action android:name="@ANDROID_PACKAGE_NAME@.ACTION_FXA_GET_STARTED"/> - <category android:name="android.intent.category.DEFAULT"/> - </intent-filter> -#endif - </activity> - - <activity - android:theme="@style/FxAccountTheme" - android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateAccountActivity" - android:configChanges="locale|layoutDirection" - android:windowSoftInputMode="adjustResize"> - </activity> - - <activity - android:theme="@style/FxAccountTheme" - android:name="org.mozilla.gecko.fxa.activities.FxAccountConfirmAccountActivity" - android:configChanges="locale|layoutDirection" - android:noHistory="true" - android:windowSoftInputMode="adjustResize"> -#ifdef MOZ_ANDROID_NATIVE_ACCOUNT_UI - <intent-filter> - <action android:name="@ANDROID_PACKAGE_NAME@.ACTION_FXA_CONFIRM_ACCOUNT"/> - <category android:name="android.intent.category.DEFAULT"/> - </intent-filter> -#endif - </activity> - - <activity - android:theme="@style/FxAccountTheme" - android:name="org.mozilla.gecko.fxa.activities.FxAccountSignInActivity" - android:configChanges="locale|layoutDirection" - android:windowSoftInputMode="adjustResize"> - </activity> - - <activity - android:theme="@style/FxAccountTheme" - android:name="org.mozilla.gecko.fxa.activities.FxAccountVerifiedAccountActivity" - android:configChanges="locale|layoutDirection" - android:noHistory="true" - android:windowSoftInputMode="adjustResize"> - </activity> - - <activity - android:theme="@style/FxAccountTheme" - android:name="org.mozilla.gecko.fxa.activities.FxAccountUpdateCredentialsActivity" - android:configChanges="locale|layoutDirection" - android:windowSoftInputMode="adjustResize"> -#ifdef MOZ_ANDROID_NATIVE_ACCOUNT_UI - <intent-filter> - <action android:name="@ANDROID_PACKAGE_NAME@.ACTION_FXA_UPDATE_CREDENTIALS"/> - <category android:name="android.intent.category.DEFAULT"/> - </intent-filter> -#endif - </activity> - - <activity - android:theme="@style/FxAccountTheme" - android:name="org.mozilla.gecko.fxa.activities.FxAccountFinishMigratingActivity" - android:configChanges="locale|layoutDirection" - android:windowSoftInputMode="adjustResize"> -#ifdef MOZ_ANDROID_NATIVE_ACCOUNT_UI - <intent-filter> - <action android:name="@ANDROID_PACKAGE_NAME@.ACTION_FXA_FINISH_MIGRATING"/> - <category android:name="android.intent.category.DEFAULT"/> - </intent-filter> -#endif - </activity> - - <activity - android:theme="@style/FxAccountTheme" - android:name="org.mozilla.gecko.fxa.activities.FxAccountMigrationFinishedActivity" - android:configChanges="locale|layoutDirection" - android:noHistory="true" - android:windowSoftInputMode="adjustResize"> - </activity> - - <activity - android:theme="@style/FxAccountTheme" - android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateAccountNotAllowedActivity" - android:configChanges="locale|layoutDirection" - android:noHistory="true" - android:windowSoftInputMode="adjustResize"> - </activity> - <receiver android:name="org.mozilla.gecko.fxa.receivers.FxAccountDeletedReceiver" android:permission="@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@.permission.PER_ACCOUNT_TYPE"> @@ -134,7 +34,6 @@ </intent-filter> </receiver> -#ifndef MOZ_ANDROID_NATIVE_ACCOUNT_UI <activity android:exported="false" android:name="org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivityWeb"> @@ -170,4 +69,3 @@ <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> -#endif diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountAgeLockoutHelper.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountAgeLockoutHelper.java deleted file mode 100644 index 4b4ac22f3c33c470f4c11df65960da26bed0080a..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountAgeLockoutHelper.java +++ /dev/null @@ -1,137 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.background.fxa; - -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Locale; - -import org.mozilla.gecko.fxa.FxAccountConstants; - -/** - * Utility to manage COPPA age verification requirements. - * <p> - * A user who fails an age verification check when trying to create an account - * is denied the ability to make an account for a period of time. We refer to - * this state as being "locked out". - * <p> - * For now we maintain "locked out" state as a static variable. In the future we - * might need to persist this state across process restarts, so we'll force - * consumers to create an instance of this class. Then, we can drop in a class - * backed by shared preferences. - */ -public class FxAccountAgeLockoutHelper { - private static final String LOG_TAG = FxAccountAgeLockoutHelper.class.getSimpleName(); - - protected static long ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK = 0L; - - public static synchronized boolean isLockedOut(long elapsedRealtime) { - if (ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK == 0L) { - // We never failed, so we're not locked out. - return false; - } - - // Otherwise, find out how long it's been since we last failed. - long millsecondsSinceLastFailedAgeCheck = elapsedRealtime - ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK; - boolean isLockedOut = millsecondsSinceLastFailedAgeCheck < FxAccountConstants.MINIMUM_TIME_TO_WAIT_AFTER_AGE_CHECK_FAILED_IN_MILLISECONDS; - FxAccountUtils.pii(LOG_TAG, "Checking if locked out: it's been " + millsecondsSinceLastFailedAgeCheck + "ms " + - "since last lockout, so " + (isLockedOut ? "yes." : "no.")); - return isLockedOut; - } - - public static synchronized void lockOut(long elapsedRealtime) { - FxAccountUtils.pii(LOG_TAG, "Locking out at time: " + elapsedRealtime); - ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK = Math.max(elapsedRealtime, ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK); - } - - /** - * Return true if the given year is the magic year. - * <p> - * The <i>magic year</i> is the calendar year when the user is the minimum age - * older. That is, for part of the magic year the user is younger than the age - * limit and for part of the magic year the user is older than the age limit. - * - * @param yearOfBirth - * @return true if <code>yearOfBirth</code> is the magic year. - */ - public static boolean isMagicYear(int yearOfBirth) { - final Calendar cal = Calendar.getInstance(); - final int thisYear = cal.get(Calendar.YEAR); - return (thisYear - yearOfBirth) == FxAccountConstants.MINIMUM_AGE_TO_CREATE_AN_ACCOUNT; - } - - /** - * Return true if the age of somebody born in - * <code>dayOfBirth/zeroBasedMonthOfBirth/yearOfBirth</code> is old enough to - * create an account. - * - * @param dayOfBirth - * @param zeroBasedMonthOfBirth - * @param yearOfBirth - * @return true if somebody born in - * <code>dayOfBirth/zeroBasedMonthOfBirth/yearOfBirth</code> is old enough. - */ - public static boolean passesAgeCheck(final int dayOfBirth, final int zeroBasedMonthOfBirth, final int yearOfBirth) { - final Calendar latestBirthday = Calendar.getInstance(); - final int y = latestBirthday.get(Calendar.YEAR); - final int m = latestBirthday.get(Calendar.MONTH); - final int d = latestBirthday.get(Calendar.DAY_OF_MONTH); - latestBirthday.clear(); - latestBirthday.set(y - FxAccountConstants.MINIMUM_AGE_TO_CREATE_AN_ACCOUNT, m, d); - - // Go back one second, so that the exact same birthday and latestBirthday satisfy birthday <= latestBirthday. - latestBirthday.add(Calendar.SECOND, 1); - - final Calendar birthday = Calendar.getInstance(); - birthday.clear(); - birthday.set(yearOfBirth, zeroBasedMonthOfBirth, dayOfBirth); - - boolean oldEnough = birthday.before(latestBirthday); - - if (FxAccountUtils.LOG_PERSONAL_INFORMATION) { - final StringBuilder message = new StringBuilder(); - final SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()); - message.append("Age check "); - message.append(oldEnough ? "passes" : "fails"); - message.append(": birthday is "); - message.append(sdf.format(birthday.getTime())); - message.append("; latest birthday is "); - message.append(sdf.format(latestBirthday.getTime())); - message.append(" (Y/M/D)."); - FxAccountUtils.pii(LOG_TAG, message.toString()); - } - - return oldEnough; - } - - /** - * Custom function for UI use only. - */ - public static boolean passesAgeCheck(int dayOfBirth, int zeroBaseMonthOfBirth, String yearText, String[] yearItems) { - if (yearText == null) { - throw new IllegalArgumentException("yearText must not be null"); - } - if (yearItems == null) { - throw new IllegalArgumentException("yearItems must not be null"); - } - if (!Arrays.asList(yearItems).contains(yearText)) { - // This should never happen, but let's be careful. - FxAccountUtils.pii(LOG_TAG, "Failed age check: year text was not found in item list."); - return false; - } - Integer yearOfBirth; - try { - yearOfBirth = Integer.valueOf(yearText, 10); - } catch (NumberFormatException e) { - // Any non-numbers in the list are ranges (and we say as much to - // translators in the resource file), so these people pass the age check. - FxAccountUtils.pii(LOG_TAG, "Passed age check: year text was found in item list but was not a number."); - return true; - } - - return passesAgeCheck(dayOfBirth, zeroBaseMonthOfBirth, yearOfBirth); - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FirefoxAccounts.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FirefoxAccounts.java index 5917d585bcc452187219bdb179130591c202b48b..4dddd6d78cb4e45fb09129a83b7ecc5a70e3a370 100644 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FirefoxAccounts.java +++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FirefoxAccounts.java @@ -17,7 +17,6 @@ import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; import org.mozilla.gecko.fxa.login.State; import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter; import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; -import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender; import org.mozilla.gecko.sync.ThreadPool; import org.mozilla.gecko.sync.Utils; @@ -26,7 +25,6 @@ import android.accounts.AccountManager; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; -import android.os.AsyncTask; import android.os.Bundle; /** @@ -303,26 +301,4 @@ public class FirefoxAccounts { final String LOCALE = Utils.getLanguageTag(locale); return res.getString(R.string.fxaccount_link_old_firefox, VERSION, OS, LOCALE); } - - /** - * Resends the account verification email, and displays an appropriate - * toast on both send success and failure. Note that because the underlying implementation - * uses {@link AsyncTask}, the provided context must be UI-capable, and this - * method called from the UI thread (see - * {@link org.mozilla.gecko.fxa.tasks.FxAccountCodeResender#resendCode(Context, AndroidFxAccount)} - * for more). - * - * @param context a UI-capable Android context. - * @return true if an account exists, false otherwise. - */ - public static boolean resendVerificationEmail(final Context context) { - final Account account = getFirefoxAccount(context); - if (account == null) { - return false; - } - - final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account); - FxAccountCodeResender.resendCode(context, fxAccount); - return true; - } } diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountAbstractActivity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountAbstractActivity.java index 0cdde5e8b54396bbf2b39cd9b4ba0c43edd440c7..fc8cbf0dab98151cf49d393fffdb83065076e3bd 100644 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountAbstractActivity.java +++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountAbstractActivity.java @@ -7,42 +7,30 @@ package org.mozilla.gecko.fxa.activities; import android.accounts.Account; import android.app.Activity; import android.content.Intent; -import android.os.SystemClock; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.TextView; + import org.mozilla.gecko.Locales.LocaleAwareActivity; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper; import org.mozilla.gecko.fxa.FirefoxAccounts; import org.mozilla.gecko.fxa.FxAccountConstants; -import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; -import org.mozilla.gecko.sync.setup.activities.ActivityUtils; public abstract class FxAccountAbstractActivity extends LocaleAwareActivity { private static final String LOG_TAG = FxAccountAbstractActivity.class.getSimpleName(); protected final boolean cannotResumeWhenAccountsExist; protected final boolean cannotResumeWhenNoAccountsExist; - protected final boolean cannotResumeWhenLockedOut; public static final int CAN_ALWAYS_RESUME = 0; public static final int CANNOT_RESUME_WHEN_ACCOUNTS_EXIST = 1 << 0; public static final int CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST = 1 << 1; - public static final int CANNOT_RESUME_WHEN_LOCKED_OUT = 1 << 2; public FxAccountAbstractActivity(int resume) { super(); this.cannotResumeWhenAccountsExist = 0 != (resume & CANNOT_RESUME_WHEN_ACCOUNTS_EXIST); this.cannotResumeWhenNoAccountsExist = 0 != (resume & CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST); - this.cannotResumeWhenLockedOut = 0 != (resume & CANNOT_RESUME_WHEN_LOCKED_OUT); } /** * Many Firefox Accounts activities shouldn't display if an account already - * exists or if account creation is locked out due to an age verification - * check failing (getting started, create account, sign in). This function - * redirects as appropriate. + * exists. This function redirects as appropriate. * * @return true if redirected. */ @@ -58,14 +46,6 @@ public abstract class FxAccountAbstractActivity extends LocaleAwareActivity { return true; } } - if (cannotResumeWhenLockedOut) { - if (FxAccountAgeLockoutHelper.isLockedOut(SystemClock.elapsedRealtime())) { - this.setResult(RESULT_CANCELED); - launchActivity(FxAccountCreateAccountNotAllowedActivity.class); - finish(); - return true; - } - } return false; } @@ -97,64 +77,4 @@ public abstract class FxAccountAbstractActivity extends LocaleAwareActivity { startActivity(intent); finish(); } - - /** - * Helper to find view or error if it is missing. - * - * @param id of view to find. - * @param description to print in error. - * @return non-null <code>View</code> instance. - */ - public View ensureFindViewById(View v, int id, String description) { - View view; - if (v != null) { - view = v.findViewById(id); - } else { - view = findViewById(id); - } - if (view == null) { - String message = "Could not find view " + description + "."; - Logger.error(LOG_TAG, message); - throw new RuntimeException(message); - } - return view; - } - - public void linkifyTextViews(View view, int[] textViews) { - for (int id : textViews) { - TextView textView; - if (view != null) { - textView = (TextView) view.findViewById(id); - } else { - textView = (TextView) findViewById(id); - } - - if (textView == null) { - Logger.warn(LOG_TAG, "Could not process links for view with id " + id + "."); - continue; - } - - ActivityUtils.linkifyTextView(textView, false); - } - } - - protected void launchActivityOnClick(final View view, final Class<? extends Activity> activityClass) { - view.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - FxAccountAbstractActivity.this.launchActivity(activityClass); - } - }); - } - - /** - * Helper to fetch (unique) Android Firefox Account if one exists, or return null. - */ - protected AndroidFxAccount getAndroidFxAccount() { - Account account = FirefoxAccounts.getFirefoxAccount(this); - if (account == null) { - return null; - } - return new AndroidFxAccount(this, account); - } } diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountAbstractSetupActivity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountAbstractSetupActivity.java deleted file mode 100644 index 4bf4e53c90d33d1bb216ff548410007bacc90f86..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountAbstractSetupActivity.java +++ /dev/null @@ -1,620 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.activities; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.mozilla.gecko.AppConstants; -import org.mozilla.gecko.R; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; -import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; -import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; -import org.mozilla.gecko.background.fxa.FxAccountUtils; -import org.mozilla.gecko.background.fxa.PasswordStretcher; -import org.mozilla.gecko.background.fxa.QuickPasswordStretcher; -import org.mozilla.gecko.fxa.FxAccountConstants; -import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; -import org.mozilla.gecko.fxa.login.Engaged; -import org.mozilla.gecko.fxa.login.State; -import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.ProgressDisplay; -import org.mozilla.gecko.fxa.tasks.FxAccountUnlockCodeResender; -import org.mozilla.gecko.sync.ExtendedJSONObject; -import org.mozilla.gecko.sync.SyncConfiguration; -import org.mozilla.gecko.sync.Utils; -import org.mozilla.gecko.sync.setup.Constants; -import org.mozilla.gecko.sync.setup.activities.ActivityUtils; -import org.mozilla.gecko.util.ColorUtils; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.animation.LayoutTransition; -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.text.Editable; -import android.text.Spannable; -import android.text.TextWatcher; -import android.text.method.LinkMovementMethod; -import android.text.method.PasswordTransformationMethod; -import android.text.method.SingleLineTransformationMethod; -import android.text.style.ClickableSpan; -import android.util.Patterns; -import android.view.KeyEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnFocusChangeListener; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.AutoCompleteTextView; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.TextView.OnEditorActionListener; - -abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractActivity implements ProgressDisplay { - public static final String EXTRA_EMAIL = "email"; - public static final String EXTRA_PASSWORD = "password"; - public static final String EXTRA_PASSWORD_SHOWN = "password_shown"; - public static final String EXTRA_YEAR = "year"; - public static final String EXTRA_MONTH = "month"; - public static final String EXTRA_DAY = "day"; - public static final String EXTRA_EXTRAS = "extras"; - - public static final String JSON_KEY_AUTH = "auth"; - public static final String JSON_KEY_SERVICES = "services"; - public static final String JSON_KEY_SYNC = "sync"; - public static final String JSON_KEY_PROFILE = "profile"; - - public FxAccountAbstractSetupActivity() { - super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST | CANNOT_RESUME_WHEN_LOCKED_OUT); - } - - protected FxAccountAbstractSetupActivity(int resume) { - super(resume); - } - - private static final String LOG_TAG = FxAccountAbstractSetupActivity.class.getSimpleName(); - - // By default, any custom server configuration is only shown when the account - // is configured to use a custom server. - private static final boolean ALWAYS_SHOW_CUSTOM_SERVER_LAYOUT = false; - - protected int minimumPasswordLength = 8; - - protected AutoCompleteTextView emailEdit; - protected EditText passwordEdit; - protected Button showPasswordButton; - protected TextView remoteErrorTextView; - protected Button button; - protected ProgressBar progressBar; - - private String authServerEndpoint; - private String syncServerEndpoint; - private String profileServerEndpoint; - - protected String getAuthServerEndpoint() { - return authServerEndpoint; - } - - protected String getTokenServerEndpoint() { - return syncServerEndpoint; - } - - protected String getProfileServerEndpoint() { - return profileServerEndpoint; - } - - protected void createShowPasswordButton() { - showPasswordButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - boolean isShown = passwordEdit.getTransformationMethod() instanceof SingleLineTransformationMethod; - setPasswordButtonShown(!isShown); - } - }); - } - - @SuppressWarnings("deprecation") - protected void setPasswordButtonShown(boolean shouldShow) { - // Changing input type loses position in edit text; let's try to maintain it. - int start = passwordEdit.getSelectionStart(); - int stop = passwordEdit.getSelectionEnd(); - - if (!shouldShow) { - passwordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance()); - showPasswordButton.setText(R.string.fxaccount_password_show); - showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_show_background)); - showPasswordButton.setTextColor(ColorUtils.getColor(this, R.color.fxaccount_password_show_textcolor)); - } else { - passwordEdit.setTransformationMethod(SingleLineTransformationMethod.getInstance()); - showPasswordButton.setText(R.string.fxaccount_password_hide); - showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_hide_background)); - showPasswordButton.setTextColor(ColorUtils.getColor(this, R.color.fxaccount_password_hide_textcolor)); - } - passwordEdit.setSelection(start, stop); - } - - protected void linkifyPolicy() { - TextView policyView = (TextView) ensureFindViewById(null, R.id.policy, "policy links"); - final String linkTerms = getString(R.string.fxaccount_link_tos); - final String linkPrivacy = getString(R.string.fxaccount_link_pn); - final String linkedTOS = "<a href=\"" + linkTerms + "\">" + getString(R.string.fxaccount_policy_linktos) + "</a>"; - final String linkedPN = "<a href=\"" + linkPrivacy + "\">" + getString(R.string.fxaccount_policy_linkprivacy) + "</a>"; - policyView.setText(getString(R.string.fxaccount_create_account_policy_text, linkedTOS, linkedPN)); - final boolean underlineLinks = true; - ActivityUtils.linkifyTextView(policyView, underlineLinks); - } - - protected void hideRemoteError() { - if (AppConstants.Versions.feature11Plus) { - // On v11+, we remove the view entirely, which triggers a smooth - // animation. - remoteErrorTextView.setVisibility(View.GONE); - } else { - // On earlier versions, we just hide the error. - remoteErrorTextView.setVisibility(View.INVISIBLE); - } - } - - protected void showRemoteError(Exception e, int defaultResourceId) { - if (e instanceof IOException) { - remoteErrorTextView.setText(R.string.fxaccount_remote_error_COULD_NOT_CONNECT); - } else if (e instanceof FxAccountClientRemoteException) { - showClientRemoteException((FxAccountClientRemoteException) e); - } else { - remoteErrorTextView.setText(defaultResourceId); - } - Logger.warn(LOG_TAG, "Got exception; showing error message: " + remoteErrorTextView.getText().toString(), e); - remoteErrorTextView.setVisibility(View.VISIBLE); - } - - protected void showClientRemoteException(final FxAccountClientRemoteException e) { - if (!e.isAccountLocked()) { - remoteErrorTextView.setText(e.getErrorMessageStringResource()); - return; - } - - // This horrible bit of special-casing is because we want this error message - // to contain a clickable, extra chunk of text, but we don't want to pollute - // the exception class with Android specifics. - final int messageId = e.getErrorMessageStringResource(); - final int clickableId = R.string.fxaccount_resend_unlock_code_button_label; - final Spannable span = Utils.interpolateClickableSpan(this, messageId, clickableId, new ClickableSpan() { - @Override - public void onClick(View widget) { - // It would be best to capture the email address sent to the server - // and use it here, but this will do for now. If the user modifies - // the email address entered, the error text is hidden, so sending a - // changed email address would be the result of an unusual race. - final String email = emailEdit.getText().toString(); - byte[] emailUTF8 = null; - try { - emailUTF8 = email.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - // It's okay, we'll fail in the code resender. - } - FxAccountUnlockCodeResender.resendUnlockCode(FxAccountAbstractSetupActivity.this, getAuthServerEndpoint(), emailUTF8); - } - }); - remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance()); - remoteErrorTextView.setText(span); - } - - protected void addListeners() { - TextChangedListener textChangedListener = new TextChangedListener(); - EditorActionListener editorActionListener = new EditorActionListener(); - FocusChangeListener focusChangeListener = new FocusChangeListener(); - - emailEdit.addTextChangedListener(textChangedListener); - emailEdit.setOnEditorActionListener(editorActionListener); - emailEdit.setOnFocusChangeListener(focusChangeListener); - passwordEdit.addTextChangedListener(textChangedListener); - passwordEdit.setOnEditorActionListener(editorActionListener); - passwordEdit.setOnFocusChangeListener(focusChangeListener); - } - - protected class FocusChangeListener implements OnFocusChangeListener { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - return; - } - updateButtonState(); - } - } - - protected class EditorActionListener implements OnEditorActionListener { - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - updateButtonState(); - return false; - } - } - - protected class TextChangedListener implements TextWatcher { - @Override - public void afterTextChanged(Editable s) { - updateButtonState(); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // Do nothing. - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - // Do nothing. - } - } - - protected boolean shouldButtonBeEnabled() { - final String email = emailEdit.getText().toString(); - final String password = passwordEdit.getText().toString(); - - boolean enabled = - (email.length() > 0) && - Patterns.EMAIL_ADDRESS.matcher(email).matches() && - (password.length() >= minimumPasswordLength); - return enabled; - } - - protected boolean updateButtonState() { - boolean enabled = shouldButtonBeEnabled(); - if (!enabled) { - // The user needs to do something before you can interact with the button; - // presumably that interaction will fix whatever error is shown. - hideRemoteError(); - } - if (enabled != button.isEnabled()) { - Logger.debug(LOG_TAG, (enabled ? "En" : "Dis") + "abling button."); - button.setEnabled(enabled); - } - return enabled; - } - - @Override - public void showProgress() { - progressBar.setVisibility(View.VISIBLE); - button.setVisibility(View.INVISIBLE); - } - - @Override - public void dismissProgress() { - progressBar.setVisibility(View.INVISIBLE); - button.setVisibility(View.VISIBLE); - } - - public Intent makeSuccessIntent(String email, LoginResponse result) { - Intent successIntent; - if (result.verified) { - successIntent = new Intent(this, FxAccountVerifiedAccountActivity.class); - } else { - successIntent = new Intent(this, FxAccountConfirmAccountActivity.class); - } - // Per http://stackoverflow.com/a/8992365, this triggers a known bug with - // the soft keyboard not being shown for the started activity. Why, Android, why? - successIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - return successIntent; - } - - protected abstract class AddAccountDelegate implements RequestDelegate<LoginResponse> { - public final String email; - public final PasswordStretcher passwordStretcher; - public final String serverURI; - public final Map<String, Boolean> selectedEngines; - public final Map<String, Boolean> authoritiesToSyncAutomaticallyMap; - - public AddAccountDelegate(String email, PasswordStretcher passwordStretcher, String serverURI) { - this(email, passwordStretcher, serverURI, null, AndroidFxAccount.DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP); - } - - public AddAccountDelegate(String email, PasswordStretcher passwordStretcher, String serverURI, Map<String, Boolean> selectedEngines, Map<String, Boolean> authoritiesToSyncAutomaticallyMap) { - if (email == null) { - throw new IllegalArgumentException("email must not be null"); - } - if (passwordStretcher == null) { - throw new IllegalArgumentException("passwordStretcher must not be null"); - } - if (serverURI == null) { - throw new IllegalArgumentException("serverURI must not be null"); - } - if (authoritiesToSyncAutomaticallyMap == null) { - throw new IllegalArgumentException("authoritiesToSyncAutomaticallyMap must not be null"); - } - this.email = email; - this.passwordStretcher = passwordStretcher; - this.serverURI = serverURI; - // selectedEngines can be null, which means don't write - // userSelectedEngines to prefs. This makes any created meta/global record - // have the default set of engines to sync. - this.selectedEngines = selectedEngines; - // authoritiesToSyncAutomaticallymap cannot be null. - this.authoritiesToSyncAutomaticallyMap = authoritiesToSyncAutomaticallyMap; - } - - @Override - public void handleSuccess(LoginResponse result) { - Logger.info(LOG_TAG, "Got success response; adding Android account."); - - // We're on the UI thread, but it's okay to create the account here. - AndroidFxAccount fxAccount; - try { - final String profile = Constants.DEFAULT_PROFILE; - final String tokenServerURI = getTokenServerEndpoint(); - final String profileServerURI = getProfileServerEndpoint(); - // It is crucial that we use the email address provided by the server - // (rather than whatever the user entered), because the user's keys are - // wrapped and salted with the initial email they provided to - // /create/account. Of course, we want to pass through what the user - // entered locally as much as possible, so we create the Android account - // with their entered email address, etc. - // The passwordStretcher should have seen this email address before, so - // we shouldn't be calculating the expensive stretch twice. - byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(result.remoteEmail.getBytes("UTF-8")); - byte[] unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW); - State state = new Engaged(email, result.uid, result.verified, unwrapkB, result.sessionToken, result.keyFetchToken); - fxAccount = AndroidFxAccount.addAndroidAccount(getApplicationContext(), - email, - profile, - serverURI, - tokenServerURI, - profileServerURI, - state, - this.authoritiesToSyncAutomaticallyMap); - if (fxAccount == null) { - throw new RuntimeException("Could not add Android account."); - } - - if (selectedEngines != null) { - Logger.info(LOG_TAG, "User has selected engines; storing to prefs."); - SyncConfiguration.storeSelectedEnginesToPrefs(fxAccount.getSyncPrefs(), selectedEngines); - } - } catch (Exception e) { - handleError(e); - return; - } - - // For great debugging. - if (FxAccountUtils.LOG_PERSONAL_INFORMATION) { - fxAccount.dump(); - } - - // The GetStarted activity has called us and needs to return a result to the authenticator. - final Intent intent = new Intent(); - intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, email); - intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE); - // intent.putExtra(AccountManager.KEY_AUTHTOKEN, accountType); - setResult(RESULT_OK, intent); - - // Show success activity depending on verification status. - Intent successIntent = makeSuccessIntent(email, result); - startActivity(successIntent); - finish(); - } - } - - /** - * Factory function that produces a new PasswordStretcher instance. - * - * @return PasswordStretcher instance. - */ - protected PasswordStretcher makePasswordStretcher(String password) { - return new QuickPasswordStretcher(password); - } - - protected abstract static class GetAccountsAsyncTask extends AsyncTask<Void, Void, Account[]> { - protected final Context context; - - public GetAccountsAsyncTask(Context context) { - super(); - this.context = context; - } - - @Override - protected Account[] doInBackground(Void... params) { - return AccountManager.get(context).getAccounts(); - } - } - - /** - * This updates UI, so needs to be done on the foreground thread. - */ - protected void populateEmailAddressAutocomplete(Account[] accounts) { - // First a set, since we don't want repeats. - final Set<String> emails = new HashSet<String>(); - for (Account account : accounts) { - if (!Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) { - continue; - } - emails.add(account.name); - } - - // And then sorted in alphabetical order. - final String[] sortedEmails = emails.toArray(new String[emails.size()]); - Arrays.sort(sortedEmails); - - final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, sortedEmails); - emailEdit.setAdapter(adapter); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @SuppressLint("NewApi") - protected void maybeEnableAnimations() { - // On v11+, we animate the error display being added and removed. This saves - // us some vertical space when we start the activity. - if (AppConstants.Versions.feature11Plus) { - final ViewGroup container = (ViewGroup) remoteErrorTextView.getParent(); - container.setLayoutTransition(new LayoutTransition()); - } - } - - protected void updateFromIntentExtras() { - // Only set email/password in onCreate; we don't want to overwrite edited values onResume. - if (getIntent() != null && getIntent().getExtras() != null) { - Bundle bundle = getIntent().getExtras(); - emailEdit.setText(bundle.getString(EXTRA_EMAIL)); - passwordEdit.setText(bundle.getString(EXTRA_PASSWORD)); - setPasswordButtonShown(bundle.getBoolean(EXTRA_PASSWORD_SHOWN, false)); - } - - // This sets defaults as well as extracting from extras, so it's not conditional. - updateServersFromIntentExtras(getIntent()); - - if (FxAccountUtils.LOG_PERSONAL_INFORMATION) { - FxAccountUtils.pii(LOG_TAG, "Using auth server: " + authServerEndpoint); - FxAccountUtils.pii(LOG_TAG, "Using sync server: " + syncServerEndpoint); - FxAccountUtils.pii(LOG_TAG, "Using profile server: " + profileServerEndpoint); - } - - updateCustomServerView(); - } - - @Override - public void onResume() { - super.onResume(); - - // Getting Accounts accesses databases on disk, so needs to be done on a - // background thread. - final GetAccountsAsyncTask task = new GetAccountsAsyncTask(this) { - @Override - public void onPostExecute(Account[] accounts) { - populateEmailAddressAutocomplete(accounts); - } - }; - task.execute(); - } - - protected Bundle makeExtrasBundle(String email, String password) { - final Bundle bundle = new Bundle(); - - // Pass through any extras that we were started with. - if (getIntent() != null && getIntent().getExtras() != null) { - bundle.putAll(getIntent().getExtras()); - } - - // Overwrite with current settings. - if (email == null) { - email = emailEdit.getText().toString(); - } - if (password == null) { - password = passwordEdit.getText().toString(); - } - bundle.putString(EXTRA_EMAIL, email); - bundle.putString(EXTRA_PASSWORD, password); - - boolean isPasswordShown = passwordEdit.getTransformationMethod() instanceof SingleLineTransformationMethod; - bundle.putBoolean(EXTRA_PASSWORD_SHOWN, isPasswordShown); - - return bundle; - } - - protected void startActivityInstead(Class<?> cls, int requestCode, Bundle extras) { - Intent intent = new Intent(this, cls); - if (extras != null) { - intent.putExtras(extras); - } - // Per http://stackoverflow.com/a/8992365, this triggers a known bug with - // the soft keyboard not being shown for the started activity. Why, Android, why? - intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - startActivityForResult(intent, requestCode); - } - - protected void updateServersFromIntentExtras(Intent intent) { - // Start with defaults. - this.authServerEndpoint = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT; - this.syncServerEndpoint = FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT; - this.profileServerEndpoint = FxAccountConstants.DEFAULT_PROFILE_SERVER_ENDPOINT; - - if (intent == null) { - Logger.warn(LOG_TAG, "Intent is null; ignoring and using default servers."); - return; - } - - final String extrasString = intent.getStringExtra(EXTRA_EXTRAS); - - if (extrasString == null) { - return; - } - - final ExtendedJSONObject extras; - final ExtendedJSONObject services; - try { - extras = new ExtendedJSONObject(extrasString); - services = extras.getObject(JSON_KEY_SERVICES); - } catch (Exception e) { - Logger.warn(LOG_TAG, "Got exception parsing extras; ignoring and using default servers."); - return; - } - - String authServer = extras.getString(JSON_KEY_AUTH); - String syncServer = services == null ? null : services.getString(JSON_KEY_SYNC); - String profileServer = services == null ? null : services.getString(JSON_KEY_PROFILE); - - if (authServer != null) { - this.authServerEndpoint = authServer; - } - if (syncServer != null) { - this.syncServerEndpoint = syncServer; - } - if (profileServer != null) { - this.profileServerEndpoint = profileServer; - } - - if (FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(syncServerEndpoint) && - !FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(authServerEndpoint)) { - // We really don't want to hard-code assumptions about server - // configurations into client code in such a way that if and when the - // situation is relaxed, the client code stops valid usage. Instead, we - // warn. This configuration should present itself as an auth exception at - // Sync time. - Logger.warn(LOG_TAG, "Mozilla's Sync token servers only works with Mozilla's auth servers. Sync will likely be mis-configured."); - } - } - - protected void updateCustomServerView() { - final boolean shouldShow = - ALWAYS_SHOW_CUSTOM_SERVER_LAYOUT || - !FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(authServerEndpoint) || - !FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(syncServerEndpoint); - - if (!shouldShow) { - setCustomServerViewVisibility(View.GONE); - return; - } - - final TextView authServerView = (TextView) ensureFindViewById(null, R.id.account_server_summary, "account server"); - final TextView syncServerView = (TextView) ensureFindViewById(null, R.id.sync_server_summary, "Sync server"); - authServerView.setText(authServerEndpoint); - syncServerView.setText(syncServerEndpoint); - - setCustomServerViewVisibility(View.VISIBLE); - } - - protected void setCustomServerViewVisibility(int visibility) { - ensureFindViewById(null, R.id.account_server_layout, "account server layout").setVisibility(visibility); - ensureFindViewById(null, R.id.sync_server_layout, "sync server layout").setVisibility(visibility); - } - - protected Map<String, String> getQueryParameters() { - final Map<String, String> queryParameters = new HashMap<>(); - queryParameters.put("service", "sync"); - return queryParameters; - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java deleted file mode 100644 index 84dd445f679886943cea5600fa4f61832fb23d3f..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java +++ /dev/null @@ -1,186 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.activities; - -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.AutoCompleteTextView; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ProgressBar; -import android.widget.TextView; -import org.mozilla.gecko.R; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.background.common.telemetry.TelemetryWrapper; -import org.mozilla.gecko.background.fxa.FxAccountClient; -import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; -import org.mozilla.gecko.background.fxa.FxAccountClient20; -import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; -import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; -import org.mozilla.gecko.background.fxa.FxAccountUtils; -import org.mozilla.gecko.background.fxa.PasswordStretcher; -import org.mozilla.gecko.fxa.FirefoxAccounts; -import org.mozilla.gecko.fxa.FxAccountConstants; -import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; -import org.mozilla.gecko.fxa.login.Engaged; -import org.mozilla.gecko.fxa.login.State; -import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask; -import org.mozilla.gecko.sync.setup.activities.ActivityUtils; -import org.mozilla.gecko.sync.telemetry.TelemetryContract; - -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -/** - * Abstract activity which displays a screen for updating the local password. - */ -public abstract class FxAccountAbstractUpdateCredentialsActivity extends FxAccountAbstractSetupActivity { - protected static final String LOG_TAG = FxAccountAbstractUpdateCredentialsActivity.class.getSimpleName(); - - protected AndroidFxAccount fxAccount; - - protected final int layoutResourceId; - - public FxAccountAbstractUpdateCredentialsActivity(int layoutResourceId) { - // We want to share code with the other setup activities, but this activity - // doesn't create a new Android Account, it modifies an existing one. If you - // manage to get an account, and somehow be locked out too, we'll let you - // update it. - super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST); - this.layoutResourceId = layoutResourceId; - } - - /** - * {@inheritDoc} - */ - @Override - public void onCreate(Bundle icicle) { - Logger.debug(LOG_TAG, "onCreate(" + icicle + ")"); - - super.onCreate(icicle); - setContentView(layoutResourceId); - - emailEdit = (AutoCompleteTextView) ensureFindViewById(null, R.id.email, "email edit"); - passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit"); - showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button"); - remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view"); - button = (Button) ensureFindViewById(null, R.id.button, "update credentials"); - progressBar = (ProgressBar) ensureFindViewById(null, R.id.progress, "progress bar"); - - minimumPasswordLength = 1; // Minimal restriction on passwords entered to sign in. - createButton(); - addListeners(); - updateButtonState(); - createShowPasswordButton(); - - emailEdit.setEnabled(false); - - TextView view = (TextView) findViewById(R.id.forgot_password_link); - ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password); - - updateFromIntentExtras(); - maybeEnableAnimations(); - } - - protected class UpdateCredentialsDelegate implements RequestDelegate<LoginResponse> { - public final String email; - public final String serverURI; - public final PasswordStretcher passwordStretcher; - - public UpdateCredentialsDelegate(String email, PasswordStretcher passwordStretcher, String serverURI) { - this.email = email; - this.serverURI = serverURI; - this.passwordStretcher = passwordStretcher; - } - - @Override - public void handleError(Exception e) { - showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error); - } - - @Override - public void handleFailure(FxAccountClientRemoteException e) { - if (e.isUpgradeRequired()) { - Logger.error(LOG_TAG, "Got upgrade required from remote server; transitioning Firefox Account to Doghouse state."); - final State state = fxAccount.getState(); - fxAccount.setState(state.makeDoghouseState()); - // The status activity will say that the user needs to upgrade. - redirectToAction(FxAccountConstants.ACTION_FXA_STATUS); - return; - } - showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error); - } - - @Override - public void handleSuccess(LoginResponse result) { - Logger.info(LOG_TAG, "Got success signing in."); - - if (fxAccount == null) { - this.handleError(new IllegalStateException("fxAccount must not be null")); - return; - } - - byte[] unwrapkB; - try { - // It is crucial that we use the email address provided by the server - // (rather than whatever the user entered), because the user's keys are - // wrapped and salted with the initial email they provided to - // /create/account. Of course, we want to pass through what the user - // entered locally as much as possible. - byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(result.remoteEmail.getBytes("UTF-8")); - unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW); - } catch (Exception e) { - this.handleError(e); - return; - } - fxAccount.setState(new Engaged(email, result.uid, result.verified, unwrapkB, result.sessionToken, result.keyFetchToken)); - fxAccount.requestSync(FirefoxAccounts.FORCE); - - // For great debugging. - if (FxAccountUtils.LOG_PERSONAL_INFORMATION) { - fxAccount.dump(); - } - - setResult(RESULT_OK); - - // Maybe show success activity. - final Intent successIntent = makeSuccessIntent(email, result); - if (successIntent != null) { - startActivity(successIntent); - } - finish(); - - TelemetryWrapper.addToHistogram(TelemetryContract.SYNC11_MIGRATIONS_COMPLETED, 1); - } - } - - public void updateCredentials(String email, String password) { - String serverURI = fxAccount.getAccountServerURI(); - Executor executor = Executors.newSingleThreadExecutor(); - FxAccountClient client = new FxAccountClient20(serverURI, executor); - PasswordStretcher passwordStretcher = makePasswordStretcher(password); - try { - hideRemoteError(); - RequestDelegate<LoginResponse> delegate = new UpdateCredentialsDelegate(email, passwordStretcher, serverURI); - new FxAccountSignInTask(this, this, email, passwordStretcher, client, getQueryParameters(), delegate).execute(); - } catch (Exception e) { - Logger.warn(LOG_TAG, "Got exception updating credentials for account.", e); - showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error); - } - } - - protected void createButton() { - button.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - final String email = emailEdit.getText().toString(); - final String password = passwordEdit.getText().toString(); - updateCredentials(email, password); - } - }); - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountConfirmAccountActivity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountConfirmAccountActivity.java deleted file mode 100644 index 870f2f5f764f5d8a2c6b75f412ddff071770af4d..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountConfirmAccountActivity.java +++ /dev/null @@ -1,172 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.activities; - -import android.accounts.Account; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.TextView; -import org.mozilla.gecko.R; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.fxa.FirefoxAccounts; -import org.mozilla.gecko.fxa.FxAccountConstants; -import org.mozilla.gecko.fxa.SyncStatusListener; -import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; -import org.mozilla.gecko.fxa.login.Engaged; -import org.mozilla.gecko.fxa.login.State; -import org.mozilla.gecko.fxa.login.State.Action; -import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; -import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender; -import org.mozilla.gecko.sync.setup.activities.ActivityUtils; - -/** - * Activity which displays account created successfully screen to the user, and - * starts them on the email verification path. - */ -public class FxAccountConfirmAccountActivity extends FxAccountAbstractActivity implements OnClickListener { - private static final String LOG_TAG = FxAccountConfirmAccountActivity.class.getSimpleName(); - - // Set in onCreate. - protected TextView verificationLinkTextView; - protected View resendLink; - protected View changeEmail; - - // Set in onResume. - protected AndroidFxAccount fxAccount; - - protected final InnerSyncStatusDelegate syncStatusDelegate = new InnerSyncStatusDelegate(); - - public FxAccountConfirmAccountActivity() { - super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST); - } - - /** - * {@inheritDoc} - */ - @Override - public void onCreate(Bundle icicle) { - Logger.debug(LOG_TAG, "onCreate(" + icicle + ")"); - - super.onCreate(icicle); - setContentView(R.layout.fxaccount_confirm_account); - - verificationLinkTextView = (TextView) ensureFindViewById(null, R.id.verification_link_text, "verification link text"); - resendLink = ensureFindViewById(null, R.id.resend_confirmation_email_link, "resend confirmation email link"); - resendLink.setOnClickListener(this); - changeEmail = ensureFindViewById(null, R.id.change_confirmation_email_link, "change confirmation email address"); - changeEmail.setOnClickListener(this); - - View backToBrowsingButton = ensureFindViewById(null, R.id.button, "back to browsing button"); - backToBrowsingButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - ActivityUtils.openURLInFennec(v.getContext(), null); - setResult(Activity.RESULT_OK); - finish(); - } - }); - } - - @Override - public void onResume() { - super.onResume(); - this.fxAccount = getAndroidFxAccount(); - if (fxAccount == null) { - Logger.warn(LOG_TAG, "Could not get Firefox Account."); - setResult(RESULT_CANCELED); - finish(); - return; - } - - FxAccountSyncStatusHelper.getInstance().startObserving(syncStatusDelegate); - - refresh(); - - fxAccount.requestSync(FirefoxAccounts.NOW); - } - - @Override - public void onPause() { - super.onPause(); - FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusDelegate); - - if (fxAccount != null) { - fxAccount.requestSync(FirefoxAccounts.SOON); - } - } - - protected class InnerSyncStatusDelegate implements SyncStatusListener { - protected final Runnable refreshRunnable = new Runnable() { - @Override - public void run() { - refresh(); - } - }; - - @Override - public Context getContext() { - return FxAccountConfirmAccountActivity.this; - } - - @Override - public Account getAccount() { - return fxAccount.getAndroidAccount(); - } - - @Override - public void onSyncStarted() { - Logger.info(LOG_TAG, "Got sync started message; ignoring."); - } - - @Override - public void onSyncFinished() { - if (fxAccount == null) { - return; - } - Logger.info(LOG_TAG, "Got sync finished message; refreshing."); - runOnUiThread(refreshRunnable); - } - } - - protected void refresh() { - final State state = fxAccount.getState(); - final Action neededAction = state.getNeededAction(); - switch (neededAction) { - case NeedsVerification: - // This is what we're here to handle. - break; - default: - // We're not in the right place! Redirect to status. - Logger.warn(LOG_TAG, "No need to verify Firefox Account that needs action " + neededAction.toString() + - " (in state " + state.getStateLabel() + ")."); - setResult(RESULT_CANCELED); - redirectToAction(FxAccountConstants.ACTION_FXA_STATUS); - return; - } - - final String email = fxAccount.getEmail(); - final String text = getResources().getString(R.string.fxaccount_confirm_account_verification_link, email); - verificationLinkTextView.setText(text); - - boolean resendLinkShouldBeEnabled = ((Engaged) state).getSessionToken() != null; - resendLink.setEnabled(resendLinkShouldBeEnabled); - resendLink.setClickable(resendLinkShouldBeEnabled); - } - - @Override - public void onClick(View v) { - if (v.equals(resendLink)) { - FxAccountCodeResender.resendCode(this, fxAccount); - } else if (v.equals(changeEmail)) { - final Account account = fxAccount.getAndroidAccount(); - Intent intent = new Intent(this, FxAccountGetStartedActivity.class); - FxAccountStatusActivity.maybeDeleteAndroidAccount(this, account, intent); - } - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountCreateAccountActivity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountCreateAccountActivity.java deleted file mode 100644 index 2c6a326aab5b4c60ed482ad45fca110fb3e80c56..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountCreateAccountActivity.java +++ /dev/null @@ -1,538 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.activities; - -import java.util.Calendar; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -import org.mozilla.gecko.AppConstants; -import org.mozilla.gecko.R; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper; -import org.mozilla.gecko.background.fxa.FxAccountClient; -import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; -import org.mozilla.gecko.background.fxa.FxAccountClient20; -import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; -import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; -import org.mozilla.gecko.background.fxa.FxAccountUtils; -import org.mozilla.gecko.background.fxa.PasswordStretcher; -import org.mozilla.gecko.db.BrowserContract; -import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; -import org.mozilla.gecko.fxa.tasks.FxAccountCreateAccountTask; -import org.mozilla.gecko.sync.Utils; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.os.SystemClock; -import android.text.Spannable; -import android.text.method.LinkMovementMethod; -import android.text.style.ClickableSpan; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.AutoCompleteTextView; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.TextView; - -/** - * Activity which displays create account screen to the user. - */ -public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivity { - protected static final String LOG_TAG = FxAccountCreateAccountActivity.class.getSimpleName(); - - private static final int CHILD_REQUEST_CODE = 2; - - protected String[] yearItems; - protected String[] monthItems; - protected String[] dayItems; - protected EditText yearEdit; - protected EditText monthEdit; - protected EditText dayEdit; - protected CheckBox chooseCheckBox; - protected View monthDaycombo; - - protected Map<String, Boolean> selectedEngines; - protected final Map<String, Boolean> authoritiesToSyncAutomaticallyMap = - new HashMap<String, Boolean>(AndroidFxAccount.DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP); - - /** - * {@inheritDoc} - */ - @Override - public void onCreate(Bundle icicle) { - Logger.debug(LOG_TAG, "onCreate(" + icicle + ")"); - - super.onCreate(icicle); - setContentView(R.layout.fxaccount_create_account); - - emailEdit = (AutoCompleteTextView) ensureFindViewById(null, R.id.email, "email edit"); - passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit"); - showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button"); - yearEdit = (EditText) ensureFindViewById(null, R.id.year_edit, "year edit"); - monthEdit = (EditText) ensureFindViewById(null, R.id.month_edit, "month edit"); - dayEdit = (EditText) ensureFindViewById(null, R.id.day_edit, "day edit"); - monthDaycombo = ensureFindViewById(null, R.id.month_day_combo, "month day combo"); - remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view"); - button = (Button) ensureFindViewById(null, R.id.button, "create account button"); - progressBar = (ProgressBar) ensureFindViewById(null, R.id.progress, "progress bar"); - chooseCheckBox = (CheckBox) ensureFindViewById(null, R.id.choose_what_to_sync_checkbox, "choose what to sync check box"); - selectedEngines = new HashMap<String, Boolean>(); - - createCreateAccountButton(); - createYearEdit(); - addListeners(); - updateButtonState(); - createShowPasswordButton(); - linkifyPolicy(); - createChooseCheckBox(); - initializeMonthAndDayValues(); - - View signInInsteadLink = ensureFindViewById(null, R.id.sign_in_instead_link, "sign in instead link"); - signInInsteadLink.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - final Bundle extras = makeExtrasBundle(null, null); - startActivityInstead(FxAccountSignInActivity.class, CHILD_REQUEST_CODE, extras); - } - }); - - updateFromIntentExtras(); - maybeEnableAnimations(); - } - - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - updateMonthAndDayFromBundle(savedInstanceState); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - updateBundleWithMonthAndDay(outState); - } - - @Override - protected Bundle makeExtrasBundle(String email, String password) { - final Bundle extras = super.makeExtrasBundle(email, password); - extras.putString(EXTRA_YEAR, yearEdit.getText().toString()); - updateBundleWithMonthAndDay(extras); - return extras; - } - - @Override - protected void updateFromIntentExtras() { - super.updateFromIntentExtras(); - - if (getIntent() != null) { - yearEdit.setText(getIntent().getStringExtra(EXTRA_YEAR)); - updateMonthAndDayFromBundle(getIntent().getExtras() != null ? getIntent().getExtras() : new Bundle()); - } - } - - private void updateBundleWithMonthAndDay(final Bundle bundle) { - if (monthEdit.getTag() != null) { - bundle.putInt(EXTRA_MONTH, (Integer) monthEdit.getTag()); - } - if (dayEdit.getTag() != null) { - bundle.putInt(EXTRA_DAY, (Integer) dayEdit.getTag()); - } - } - - private void updateMonthAndDayFromBundle(final Bundle extras) { - final Integer zeroBasedMonthIndex = (Integer) extras.get(EXTRA_MONTH); - final Integer oneBasedDayIndex = (Integer) extras.get(EXTRA_DAY); - maybeEnableMonthAndDayButtons(); - - if (zeroBasedMonthIndex != null) { - monthEdit.setText(monthItems[zeroBasedMonthIndex]); - monthEdit.setTag(Integer.valueOf(zeroBasedMonthIndex)); - createDayEdit(zeroBasedMonthIndex); - - if (oneBasedDayIndex != null && dayItems != null) { - dayEdit.setText(dayItems[oneBasedDayIndex - 1]); - dayEdit.setTag(Integer.valueOf(oneBasedDayIndex)); - } - } else { - monthEdit.setText(""); - dayEdit.setText(""); - } - updateButtonState(); - } - - @Override - protected void showClientRemoteException(final FxAccountClientRemoteException e) { - if (!e.isAccountAlreadyExists()) { - super.showClientRemoteException(e); - return; - } - - // This horrible bit of special-casing is because we want this error message to - // contain a clickable, extra chunk of text, but we don't want to pollute - // the exception class with Android specifics. - final int messageId = e.getErrorMessageStringResource(); - final int clickableId = R.string.fxaccount_sign_in_button_label; - - final Spannable span = Utils.interpolateClickableSpan(this, messageId, clickableId, new ClickableSpan() { - @Override - public void onClick(View widget) { - // Pass through the email address that already existed. - String email = e.body.getString("email"); - if (email == null) { - email = emailEdit.getText().toString(); - } - final String password = passwordEdit.getText().toString(); - - final Bundle extras = makeExtrasBundle(email, password); - startActivityInstead(FxAccountSignInActivity.class, CHILD_REQUEST_CODE, extras); - } - }); - remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance()); - remoteErrorTextView.setText(span); - } - - /** - * We might have switched to the SignIn activity; if that activity - * succeeds, feed its result back to the authenticator. - */ - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - Logger.debug(LOG_TAG, "onActivityResult: " + requestCode); - if (requestCode != CHILD_REQUEST_CODE || resultCode != RESULT_OK) { - super.onActivityResult(requestCode, resultCode, data); - return; - } - this.setResult(resultCode, data); - this.finish(); - } - - /** - * Return years to display in picker. - * - * @return 1990 or earlier, 1991, 1992, up to five years before current year. - * (So, if it is currently 2014, up to 2009.) - */ - protected String[] getYearItems() { - int year = Calendar.getInstance().get(Calendar.YEAR); - LinkedList<String> years = new LinkedList<String>(); - years.add(getResources().getString(R.string.fxaccount_create_account_1990_or_earlier)); - for (int i = 1991; i <= year - 5; i++) { - years.add(Integer.toString(i)); - } - return years.toArray(new String[years.size()]); - } - - protected void createYearEdit() { - yearItems = getYearItems(); - - yearEdit.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - android.content.DialogInterface.OnClickListener listener = new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - yearEdit.setText(yearItems[which]); - maybeEnableMonthAndDayButtons(); - updateButtonState(); - } - }; - final AlertDialog dialog = new AlertDialog.Builder(FxAccountCreateAccountActivity.this) - .setTitle(R.string.fxaccount_create_account_year_of_birth) - .setItems(yearItems, listener) - .setIcon(R.drawable.icon) - .create(); - - dialog.show(); - } - }); - } - - private void initializeMonthAndDayValues() { - // Hide Month and day pickers - monthDaycombo.setVisibility(View.GONE); - dayEdit.setEnabled(false); - - // Populate month names. - final Calendar calendar = Calendar.getInstance(); - final Map<String, Integer> monthNamesMap = calendar.getDisplayNames(Calendar.MONTH, Calendar.LONG, Locale.getDefault()); - monthItems = new String[monthNamesMap.size()]; - for (Map.Entry<String, Integer> entry : monthNamesMap.entrySet()) { - monthItems[entry.getValue()] = entry.getKey(); - } - createMonthEdit(); - } - - protected void createMonthEdit() { - monthEdit.setText(""); - monthEdit.setTag(null); - monthEdit.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - android.content.DialogInterface.OnClickListener listener = new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - monthEdit.setText(monthItems[which]); - monthEdit.setTag(Integer.valueOf(which)); - createDayEdit(which); - updateButtonState(); - } - }; - final AlertDialog dialog = new AlertDialog.Builder(FxAccountCreateAccountActivity.this) - .setTitle(R.string.fxaccount_create_account_month_of_birth) - .setItems(monthItems, listener) - .setIcon(R.drawable.icon) - .create(); - dialog.show(); - } - }); - } - - protected void createDayEdit(final int monthIndex) { - dayEdit.setText(""); - dayEdit.setTag(null); - dayEdit.setEnabled(true); - - String yearText = yearEdit.getText().toString(); - Integer birthYear; - try { - birthYear = Integer.parseInt(yearText); - } catch (NumberFormatException e) { - // Ideal this should never happen. - Logger.debug(LOG_TAG, "Exception while parsing year value" + e); - return; - } - - Calendar c = Calendar.getInstance(); - c.set(birthYear, monthIndex, 1); - LinkedList<String> days = new LinkedList<String>(); - for (int i = c.getActualMinimum(Calendar.DAY_OF_MONTH); i <= c.getActualMaximum(Calendar.DAY_OF_MONTH); i++) { - days.add(Integer.toString(i)); - } - dayItems = days.toArray(new String[days.size()]); - - dayEdit.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - android.content.DialogInterface.OnClickListener listener = new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dayEdit.setText(dayItems[which]); - dayEdit.setTag(Integer.valueOf(which + 1)); // Days are 1-based. - updateButtonState(); - } - }; - final AlertDialog dialog = new AlertDialog.Builder(FxAccountCreateAccountActivity.this) - .setTitle(R.string.fxaccount_create_account_day_of_birth) - .setItems(dayItems, listener) - .setIcon(R.drawable.icon) - .create(); - dialog.show(); - } - }); - } - - private void maybeEnableMonthAndDayButtons() { - Integer yearOfBirth = null; - try { - yearOfBirth = Integer.valueOf(yearEdit.getText().toString(), 10); - } catch (NumberFormatException e) { - Logger.debug(LOG_TAG, "Year text is not a number; assuming year is a range and that user is old enough."); - } - - // Check if the selected year is the magic year. - if (yearOfBirth == null || !FxAccountAgeLockoutHelper.isMagicYear(yearOfBirth)) { - // Year/Dec/31 is the latest birthday in the selected year, corresponding - // to the youngest person. - monthEdit.setTag(Integer.valueOf(11)); - dayEdit.setTag(Integer.valueOf(31)); - return; - } - - // Show month and date field. - yearEdit.setVisibility(View.GONE); - monthDaycombo.setVisibility(View.VISIBLE); - monthEdit.setTag(null); - dayEdit.setTag(null); - } - - public void createAccount(String email, String password, Map<String, Boolean> engines, Map<String, Boolean> authoritiesToSyncAutomaticallyMap) { - String serverURI = getAuthServerEndpoint(); - PasswordStretcher passwordStretcher = makePasswordStretcher(password); - // This delegate creates a new Android account on success, opens the - // appropriate "success!" activity, and finishes this activity. - RequestDelegate<LoginResponse> delegate = new AddAccountDelegate(email, passwordStretcher, serverURI, engines, authoritiesToSyncAutomaticallyMap) { - @Override - public void handleError(Exception e) { - showRemoteError(e, R.string.fxaccount_create_account_unknown_error); - } - - @Override - public void handleFailure(FxAccountClientRemoteException e) { - showRemoteError(e, R.string.fxaccount_create_account_unknown_error); - } - }; - - Executor executor = Executors.newSingleThreadExecutor(); - FxAccountClient client = new FxAccountClient20(serverURI, executor); - try { - hideRemoteError(); - new FxAccountCreateAccountTask(this, this, email, passwordStretcher, client, getQueryParameters(), delegate).execute(); - } catch (Exception e) { - showRemoteError(e, R.string.fxaccount_create_account_unknown_error); - } - } - - @Override - protected boolean shouldButtonBeEnabled() { - return super.shouldButtonBeEnabled() && - (yearEdit.length() > 0) && - (monthEdit.getTag() != null) && - (dayEdit.getTag() != null); - } - - protected void createCreateAccountButton() { - button.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (!updateButtonState()) { - return; - } - final String email = emailEdit.getText().toString(); - final String password = passwordEdit.getText().toString(); - final int dayOfBirth = (Integer) dayEdit.getTag(); - final int zeroBasedMonthOfBirth = (Integer) monthEdit.getTag(); - // Only include selected engines if the user currently has the option checked. - final Map<String, Boolean> engines = chooseCheckBox.isChecked() - ? selectedEngines - : null; - // Only include authorities if the user currently has the option checked. - final Map<String, Boolean> authoritiesMap = chooseCheckBox.isChecked() - ? authoritiesToSyncAutomaticallyMap - : AndroidFxAccount.DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP; - if (FxAccountAgeLockoutHelper.passesAgeCheck(dayOfBirth, zeroBasedMonthOfBirth, yearEdit.getText().toString(), yearItems)) { - FxAccountUtils.pii(LOG_TAG, "Passed age check."); - createAccount(email, password, engines, authoritiesMap); - } else { - FxAccountUtils.pii(LOG_TAG, "Failed age check!"); - FxAccountAgeLockoutHelper.lockOut(SystemClock.elapsedRealtime()); - setResult(RESULT_CANCELED); - launchActivity(FxAccountCreateAccountNotAllowedActivity.class); - finish(); - } - } - }); - } - - /** - * The "Choose what to sync" checkbox pops up a multi-choice dialog when it is - * unchecked. It toggles to unchecked from checked. - */ - protected void createChooseCheckBox() { - final int INDEX_BOOKMARKS = 0; - final int INDEX_HISTORY = 1; - final int INDEX_TABS = 2; - final int INDEX_PASSWORDS = 3; - final int INDEX_READING_LIST = 4; // Only valid if reading list is enabled. - final int NUMBER_OF_ENGINES; - if (AppConstants.MOZ_ANDROID_READING_LIST_SERVICE) { - NUMBER_OF_ENGINES = 5; - } else { - NUMBER_OF_ENGINES = 4; - } - - final String items[] = new String[NUMBER_OF_ENGINES]; - final boolean checkedItems[] = new boolean[NUMBER_OF_ENGINES]; - items[INDEX_BOOKMARKS] = getResources().getString(R.string.fxaccount_status_bookmarks); - items[INDEX_HISTORY] = getResources().getString(R.string.fxaccount_status_history); - items[INDEX_TABS] = getResources().getString(R.string.fxaccount_status_tabs); - items[INDEX_PASSWORDS] = getResources().getString(R.string.fxaccount_status_passwords); - if (AppConstants.MOZ_ANDROID_READING_LIST_SERVICE) { - items[INDEX_READING_LIST] = getResources().getString(R.string.fxaccount_status_reading_list); - } - // Default to everything checked. - for (int i = 0; i < NUMBER_OF_ENGINES; i++) { - checkedItems[i] = true; - } - - final DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (which != DialogInterface.BUTTON_POSITIVE) { - Logger.debug(LOG_TAG, "onClick: not button positive, unchecking."); - chooseCheckBox.setChecked(false); - return; - } - // We only check the box on success. - Logger.debug(LOG_TAG, "onClick: button positive, checking."); - chooseCheckBox.setChecked(true); - // And then remember for future use. - ListView selectionsList = ((AlertDialog) dialog).getListView(); - for (int i = 0; i < NUMBER_OF_ENGINES; i++) { - checkedItems[i] = selectionsList.isItemChecked(i); - } - selectedEngines.put("bookmarks", checkedItems[INDEX_BOOKMARKS]); - selectedEngines.put("history", checkedItems[INDEX_HISTORY]); - selectedEngines.put("tabs", checkedItems[INDEX_TABS]); - selectedEngines.put("passwords", checkedItems[INDEX_PASSWORDS]); - if (AppConstants.MOZ_ANDROID_READING_LIST_SERVICE) { - authoritiesToSyncAutomaticallyMap.put(BrowserContract.READING_LIST_AUTHORITY, checkedItems[INDEX_READING_LIST]); - } - FxAccountUtils.pii(LOG_TAG, "Updating selectedEngines: " + selectedEngines.toString()); - FxAccountUtils.pii(LOG_TAG, "Updating authorities: " + authoritiesToSyncAutomaticallyMap.toString()); - } - }; - - final DialogInterface.OnMultiChoiceClickListener multiChoiceClickListener = new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialog, int which, boolean isChecked) { - // Display multi-selection clicks in UI. - ListView selectionsList = ((AlertDialog) dialog).getListView(); - selectionsList.setItemChecked(which, isChecked); - } - }; - - final AlertDialog dialog = new AlertDialog.Builder(this) - .setTitle(R.string.fxaccount_create_account_choose_what_to_sync) - .setIcon(R.drawable.icon) - .setMultiChoiceItems(items, checkedItems, multiChoiceClickListener) - .setPositiveButton(android.R.string.ok, clickListener) - .setNegativeButton(android.R.string.cancel, clickListener) - .create(); - - dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - Logger.debug(LOG_TAG, "onCancel: unchecking."); - chooseCheckBox.setChecked(false); - } - }); - - chooseCheckBox.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - // There appears to be no way to stop Android interpreting the click - // first. So, if the user clicked on an unchecked box, it's checked by - // the time we get here. - if (!chooseCheckBox.isChecked()) { - Logger.debug(LOG_TAG, "onClick: was checked, not showing dialog."); - return; - } - Logger.debug(LOG_TAG, "onClick: was unchecked, showing dialog."); - dialog.show(); - } - }); - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountCreateAccountNotAllowedActivity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountCreateAccountNotAllowedActivity.java deleted file mode 100644 index 0cb55affaa0483f35f0b5c2557fb8358a419df47..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountCreateAccountNotAllowedActivity.java +++ /dev/null @@ -1,36 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.activities; - -import org.mozilla.gecko.R; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.sync.setup.activities.ActivityUtils; - -import android.os.Bundle; -import android.widget.TextView; - -/** - * Activity which displays sign up/sign in screen to the user. - */ -public class FxAccountCreateAccountNotAllowedActivity extends FxAccountAbstractActivity { - protected static final String LOG_TAG = FxAccountCreateAccountNotAllowedActivity.class.getSimpleName(); - - public FxAccountCreateAccountNotAllowedActivity() { - super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST); - } - - /** - * {@inheritDoc} - */ - @Override - public void onCreate(Bundle icicle) { - Logger.debug(LOG_TAG, "onCreate(" + icicle + ")"); - - super.onCreate(icicle); - setContentView(R.layout.fxaccount_create_account_not_allowed); - TextView view = (TextView) findViewById(R.id.learn_more_link); - ActivityUtils.linkTextView(view, R.string.fxaccount_account_create_not_allowed_learn_more, R.string.fxaccount_link_create_not_allowed); - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountFinishMigratingActivity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountFinishMigratingActivity.java deleted file mode 100644 index 4dd0f03370b6e5650a7e042f035db63020c3b380..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountFinishMigratingActivity.java +++ /dev/null @@ -1,63 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.activities; - -import java.util.Map; - -import org.mozilla.gecko.R; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; -import org.mozilla.gecko.fxa.login.State; -import org.mozilla.gecko.fxa.login.State.StateLabel; - -import android.content.Intent; - -/** - * Activity which displays a screen for inputting the password and finishing - * migrating to Firefox Accounts / Sync 1.5. - */ -public class FxAccountFinishMigratingActivity extends FxAccountAbstractUpdateCredentialsActivity { - protected static final String LOG_TAG = FxAccountFinishMigratingActivity.class.getSimpleName(); - - public FxAccountFinishMigratingActivity() { - super(R.layout.fxaccount_finish_migrating); - } - - @Override - public void onResume() { - super.onResume(); - this.fxAccount = getAndroidFxAccount(); - if (fxAccount == null) { - Logger.warn(LOG_TAG, "Could not get Firefox Account."); - setResult(RESULT_CANCELED); - finish(); - return; - } - final State state = fxAccount.getState(); - if (state.getStateLabel() != StateLabel.MigratedFromSync11) { - Logger.warn(LOG_TAG, "Cannot finish migrating from Firefox Account in state: " + state.getStateLabel()); - setResult(RESULT_CANCELED); - finish(); - return; - } - emailEdit.setText(fxAccount.getEmail()); - } - - @Override - public Intent makeSuccessIntent(String email, LoginResponse result) { - final Intent successIntent = new Intent(this, FxAccountMigrationFinishedActivity.class); - // Per http://stackoverflow.com/a/8992365, this triggers a known bug with - // the soft keyboard not being shown for the started activity. Why, Android, why? - successIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - return successIntent; - } - - @Override - protected Map<String, String> getQueryParameters() { - final Map<String, String> queryParameters = super.getQueryParameters(); - queryParameters.put("migration", "sync11"); - return queryParameters; - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountGetStartedActivity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountGetStartedActivity.java deleted file mode 100644 index e10bc3b9d5f5b19693d069e06b1f74484e34c5a3..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountGetStartedActivity.java +++ /dev/null @@ -1,171 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.activities; - -import java.util.Locale; - -import org.mozilla.gecko.Locales; -import org.mozilla.gecko.R; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper; -import org.mozilla.gecko.background.fxa.FxAccountUtils; -import org.mozilla.gecko.fxa.FirefoxAccounts; -import org.mozilla.gecko.fxa.FxAccountConstants; -import org.mozilla.gecko.sync.setup.activities.ActivityUtils; - -import android.accounts.AccountAuthenticatorActivity; -import android.content.Intent; -import android.os.Bundle; -import android.os.SystemClock; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.TranslateAnimation; -import android.widget.TextView; - -/** - * Activity which displays sign up/sign in screen to the user. - */ -public class FxAccountGetStartedActivity extends AccountAuthenticatorActivity { - protected static final String LOG_TAG = FxAccountGetStartedActivity.class.getSimpleName(); - - private static final int CHILD_REQUEST_CODE = 1; - - /** - * {@inheritDoc} - */ - @Override - public void onCreate(Bundle icicle) { - Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG); - Logger.debug(LOG_TAG, "onCreate(" + icicle + ")"); - - Locales.initializeLocale(getApplicationContext()); - - super.onCreate(icicle); - - setContentView(R.layout.fxaccount_get_started); - - linkifyOldFirefoxLink(); - - View button = findViewById(R.id.get_started_button); - button.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - Bundle extras = null; // startFlow accepts null. - if (getIntent() != null) { - extras = getIntent().getExtras(); - } - startFlow(extras); - } - }); - - animateIconIn(); - } - - /** - * Float the icon up, starting from below and moving up to its final layout - * position. Also, fade the icon in. - * <p> - * We animate relative to the size of the icon rather than from the bottom of - * the containing view for two reasons: first, the distance from bottom could - * be large on tablets; two, animating with absolute values requires that - * measurement has happened first, which requires a (sometimes buggy) - * onPreDrawListener. - */ - protected void animateIconIn() { - final AlphaAnimation a = new AlphaAnimation(0.0f, 1.0f); - final TranslateAnimation t = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, - Animation.RELATIVE_TO_SELF, 2.0f, Animation.RELATIVE_TO_SELF, 0.0f); - - final AnimationSet animationSet = new AnimationSet(true); - animationSet.setDuration(150 * 7); // Straight outta... fxa-content-server. - animationSet.addAnimation(a); - animationSet.addAnimation(t); - - final View iconView = findViewById(R.id.icon); - iconView.startAnimation(animationSet); - } - - protected void startFlow(Bundle extras) { - final Intent intent = new Intent(this, FxAccountCreateAccountActivity.class); - // Per http://stackoverflow.com/a/8992365, this triggers a known bug with - // the soft keyboard not being shown for the started activity. Why, Android, why? - intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - if (extras != null) { - intent.putExtras(extras); - } - startActivityForResult(intent, CHILD_REQUEST_CODE); - } - - @Override - public void onResume() { - super.onResume(); - - Intent intent = null; - if (FxAccountAgeLockoutHelper.isLockedOut(SystemClock.elapsedRealtime())) { - intent = new Intent(this, FxAccountCreateAccountNotAllowedActivity.class); - } else if (FirefoxAccounts.firefoxAccountsExist(this)) { - intent = new Intent(this, FxAccountStatusActivity.class); - } - - if (intent != null) { - this.setAccountAuthenticatorResult(null); - setResult(RESULT_CANCELED); - // Per http://stackoverflow.com/a/8992365, this triggers a known bug with - // the soft keyboard not being shown for the started activity. Why, Android, why? - intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - this.startActivity(intent); - this.finish(); - } - - // If we've been launched with extras (namely custom server URLs), continue - // past go and collect 200 dollars. If we ever get back here (for example, - // if the user hits the back button), forget that we had extras entirely, so - // that we don't enter a loop. - Bundle extras = null; - if (getIntent() != null) { - extras = getIntent().getExtras(); - } - if (extras != null && extras.containsKey(FxAccountAbstractSetupActivity.EXTRA_EXTRAS)) { - getIntent().replaceExtras(Bundle.EMPTY); - startFlow((Bundle) extras.clone()); - return; - } - } - - /** - * We started the CreateAccount activity for a result; this returns it to the - * authenticator. - */ - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - Logger.debug(LOG_TAG, "onActivityResult: " + requestCode + ", " + resultCode); - if (requestCode != CHILD_REQUEST_CODE) { - super.onActivityResult(requestCode, resultCode, data); - return; - } - - this.setResult(requestCode, data); - if (data != null) { - this.setAccountAuthenticatorResult(data.getExtras()); - - // We want to drop ourselves off the back stack if the user successfully - // created or signed in to an account. We can easily determine this by - // checking for the presence of response data. - this.finish(); - } - } - - protected void linkifyOldFirefoxLink() { - TextView oldFirefox = (TextView) findViewById(R.id.old_firefox); - String text = getResources().getString(R.string.fxaccount_getting_started_old_firefox); - final String url = FirefoxAccounts.getOldSyncUpgradeURL(getResources(), Locale.getDefault()); - FxAccountUtils.pii(LOG_TAG, "Old Firefox url is: " + url); // Don't want to leak locale in particular. - ActivityUtils.linkTextView(oldFirefox, text, url); - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountMigrationFinishedActivity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountMigrationFinishedActivity.java deleted file mode 100644 index 26bbcf7280253eca558a556f9bd4fa73aa1859e5..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountMigrationFinishedActivity.java +++ /dev/null @@ -1,67 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.activities; - -import org.mozilla.gecko.R; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; -import org.mozilla.gecko.fxa.login.State; -import org.mozilla.gecko.fxa.login.State.Action; -import org.mozilla.gecko.sync.setup.activities.ActivityUtils; - -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; - -/** - * Activity which displays "Upgrade finished" success screen. - */ -public class FxAccountMigrationFinishedActivity extends FxAccountAbstractActivity { - private static final String LOG_TAG = FxAccountMigrationFinishedActivity.class.getSimpleName(); - - protected AndroidFxAccount fxAccount; - - public FxAccountMigrationFinishedActivity() { - super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST); - } - - /** - * {@inheritDoc} - */ - @Override - public void onCreate(Bundle icicle) { - Logger.debug(LOG_TAG, "onCreate(" + icicle + ")"); - - super.onCreate(icicle); - setContentView(R.layout.fxaccount_migration_finished); - } - - @Override - public void onResume() { - super.onResume(); - this.fxAccount = getAndroidFxAccount(); - if (fxAccount == null) { - Logger.warn(LOG_TAG, "Could not get Firefox Account."); - setResult(RESULT_CANCELED); - finish(); - return; - } - final State state = fxAccount.getState(); - if (state.getNeededAction() == Action.NeedsFinishMigrating) { - Logger.warn(LOG_TAG, "Firefox Account needs to finish migrating; not displaying migration finished activity."); - setResult(RESULT_CANCELED); - finish(); - return; - } - - final View backToBrowsingButton = ensureFindViewById(null, R.id.button, "back to browsing button"); - backToBrowsingButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - ActivityUtils.openURLInFennec(v.getContext(), null); - } - }); - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountSignInActivity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountSignInActivity.java deleted file mode 100644 index eed7a1b567ad07f4381faa039681ad880299a8a6..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountSignInActivity.java +++ /dev/null @@ -1,131 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.activities; - -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -import org.mozilla.gecko.R; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.background.fxa.FxAccountClient; -import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; -import org.mozilla.gecko.background.fxa.FxAccountClient20; -import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; -import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; -import org.mozilla.gecko.background.fxa.PasswordStretcher; -import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask; -import org.mozilla.gecko.sync.setup.activities.ActivityUtils; - -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.AutoCompleteTextView; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ProgressBar; -import android.widget.TextView; - -/** - * Activity which displays sign in screen to the user. - */ -public class FxAccountSignInActivity extends FxAccountAbstractSetupActivity { - protected static final String LOG_TAG = FxAccountSignInActivity.class.getSimpleName(); - - private static final int CHILD_REQUEST_CODE = 3; - - /** - * {@inheritDoc} - */ - @Override - public void onCreate(Bundle icicle) { - Logger.debug(LOG_TAG, "onCreate(" + icicle + ")"); - - super.onCreate(icicle); - setContentView(R.layout.fxaccount_sign_in); - - emailEdit = (AutoCompleteTextView) ensureFindViewById(null, R.id.email, "email edit"); - passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit"); - showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button"); - remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view"); - button = (Button) ensureFindViewById(null, R.id.button, "sign in button"); - progressBar = (ProgressBar) ensureFindViewById(null, R.id.progress, "progress bar"); - - minimumPasswordLength = 1; // Minimal restriction on passwords entered to sign in. - createSignInButton(); - addListeners(); - updateButtonState(); - createShowPasswordButton(); - linkifyPolicy(); - - View createAccountInsteadLink = ensureFindViewById(null, R.id.create_account_link, "create account instead link"); - createAccountInsteadLink.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - final Bundle extras = makeExtrasBundle(null, null); - startActivityInstead(FxAccountCreateAccountActivity.class, CHILD_REQUEST_CODE, extras); - } - }); - - updateFromIntentExtras(); - maybeEnableAnimations(); - - TextView view = (TextView) findViewById(R.id.forgot_password_link); - ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password); - } - - /** - * We might have switched to the CreateAccount activity; if that activity - * succeeds, feed its result back to the authenticator. - */ - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - Logger.debug(LOG_TAG, "onActivityResult: " + requestCode); - if (requestCode != CHILD_REQUEST_CODE || resultCode != RESULT_OK) { - super.onActivityResult(requestCode, resultCode, data); - return; - } - this.setResult(resultCode, data); - this.finish(); - } - - public void signIn(String email, String password) { - String serverURI = getAuthServerEndpoint(); - PasswordStretcher passwordStretcher = makePasswordStretcher(password); - // This delegate creates a new Android account on success, opens the - // appropriate "success!" activity, and finishes this activity. - RequestDelegate<LoginResponse> delegate = new AddAccountDelegate(email, passwordStretcher, serverURI) { - @Override - public void handleError(Exception e) { - showRemoteError(e, R.string.fxaccount_sign_in_unknown_error); - } - - @Override - public void handleFailure(FxAccountClientRemoteException e) { - showRemoteError(e, R.string.fxaccount_sign_in_unknown_error); - } - }; - - Executor executor = Executors.newSingleThreadExecutor(); - FxAccountClient client = new FxAccountClient20(serverURI, executor); - try { - hideRemoteError(); - new FxAccountSignInTask(this, this, email, passwordStretcher, client, getQueryParameters(), delegate).execute(); - } catch (Exception e) { - showRemoteError(e, R.string.fxaccount_sign_in_unknown_error); - } - } - - protected void createSignInButton() { - button.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - final String email = emailEdit.getText().toString(); - final String password = passwordEdit.getText().toString(); - signIn(email, password); - } - }); - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusFragment.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusFragment.java index 46bbcd1cd62080b60a050dc1bfbdb6fc4f91739b..e3cce22e90a7cac451ab9d2c2a67671fd7306943 100644 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusFragment.java +++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusFragment.java @@ -38,7 +38,6 @@ import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; import org.mozilla.gecko.fxa.login.Married; import org.mozilla.gecko.fxa.login.State; import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; -import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender; import org.mozilla.gecko.sync.ExtendedJSONObject; import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate; import org.mozilla.gecko.sync.SyncConfiguration; @@ -167,9 +166,6 @@ public class FxAccountStatusFragment accountCategory = (PreferenceCategory) ensureFindPreference("signed_in_as_category"); profilePreference = ensureFindPreference("profile"); manageAccountPreference = ensureFindPreference("manage_account"); - if (AppConstants.MOZ_ANDROID_NATIVE_ACCOUNT_UI) { - accountCategory.removePreference(manageAccountPreference); - } authServerPreference = ensureFindPreference("auth_server"); removeAccountPreference = ensureFindPreference("remove_account"); @@ -235,14 +231,11 @@ public class FxAccountStatusFragment @Override public boolean onPreferenceClick(Preference preference) { if (preference == profilePreference) { - if (!AppConstants.MOZ_ANDROID_NATIVE_ACCOUNT_UI) { - // There is no native equivalent, bind the click action to fire an intent. - ActivityUtils.openURLInFennec(getActivity().getApplicationContext(), "about:accounts?action=avatar"); - } + ActivityUtils.openURLInFennec(getActivity().getApplicationContext(), "about:accounts?action=avatar"); + return true; } if (preference == manageAccountPreference) { - // There's no native equivalent, so no need to re-direct through an Intent filter. ActivityUtils.openURLInFennec(getActivity().getApplicationContext(), "about:accounts?action=manage"); return true; } @@ -255,10 +248,6 @@ public class FxAccountStatusFragment if (preference == needsPasswordPreference) { final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_UPDATE_CREDENTIALS); intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES); - final Bundle extras = getExtrasForAccount(); - if (extras != null) { - intent.putExtras(extras); - } // Per http://stackoverflow.com/a/8992365, this triggers a known bug with // the soft keyboard not being shown for the started activity. Why, Android, why? intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); @@ -270,10 +259,6 @@ public class FxAccountStatusFragment if (preference == needsFinishMigratingPreference) { final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_FINISH_MIGRATING); intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES); - final Bundle extras = getExtrasForAccount(); - if (extras != null) { - intent.putExtras(extras); - } // Per http://stackoverflow.com/a/8992365, this triggers a known bug with // the soft keyboard not being shown for the started activity. Why, Android, why? intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); @@ -283,10 +268,6 @@ public class FxAccountStatusFragment } if (preference == needsVerificationPreference) { - if (AppConstants.MOZ_ANDROID_NATIVE_ACCOUNT_UI) { - FxAccountCodeResender.resendCode(getActivity().getApplicationContext(), fxAccount); - } - final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_CONFIRM_ACCOUNT); // Per http://stackoverflow.com/a/8992365, this triggers a known bug with // the soft keyboard not being shown for the started activity. Why, Android, why? @@ -320,18 +301,6 @@ public class FxAccountStatusFragment return false; } - protected Bundle getExtrasForAccount() { - final Bundle extras = new Bundle(); - final ExtendedJSONObject o = new ExtendedJSONObject(); - o.put(FxAccountAbstractSetupActivity.JSON_KEY_AUTH, fxAccount.getAccountServerURI()); - final ExtendedJSONObject services = new ExtendedJSONObject(); - services.put(FxAccountAbstractSetupActivity.JSON_KEY_SYNC, fxAccount.getTokenServerURI()); - services.put(FxAccountAbstractSetupActivity.JSON_KEY_PROFILE, fxAccount.getProfileServerURI()); - o.put(FxAccountAbstractSetupActivity.JSON_KEY_SERVICES, services); - extras.putString(FxAccountAbstractSetupActivity.EXTRA_EXTRAS, o.toJSONString()); - return extras; - } - protected void setCheckboxesEnabled(boolean enabled) { bookmarksPreference.setEnabled(enabled); historyPreference.setEnabled(enabled); diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountUpdateCredentialsActivity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountUpdateCredentialsActivity.java deleted file mode 100644 index 1a3ce4a06545170c2aa5176d406e3587166bf393..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountUpdateCredentialsActivity.java +++ /dev/null @@ -1,52 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.activities; - -import org.mozilla.gecko.R; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; -import org.mozilla.gecko.fxa.login.State; -import org.mozilla.gecko.fxa.login.State.StateLabel; - -import android.content.Intent; - -/** - * Activity which displays a screen for updating the local password. - */ -public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractUpdateCredentialsActivity { - protected static final String LOG_TAG = FxAccountUpdateCredentialsActivity.class.getSimpleName(); - - public FxAccountUpdateCredentialsActivity() { - super(R.layout.fxaccount_update_credentials); - } - - @Override - public void onResume() { - super.onResume(); - this.fxAccount = getAndroidFxAccount(); - if (fxAccount == null) { - Logger.warn(LOG_TAG, "Could not get Firefox Account."); - setResult(RESULT_CANCELED); - finish(); - return; - } - final State state = fxAccount.getState(); - if (state.getStateLabel() != StateLabel.Separated) { - Logger.warn(LOG_TAG, "Cannot update credentials from Firefox Account in state: " + state.getStateLabel()); - setResult(RESULT_CANCELED); - finish(); - return; - } - emailEdit.setText(fxAccount.getEmail()); - } - - @Override - public Intent makeSuccessIntent(String email, LoginResponse result) { - // We don't show anything after updating credentials. The updating Activity - // sets its result to OK and the user is returned to the previous task, - // which is often the Status Activity. - return null; - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountVerifiedAccountActivity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountVerifiedAccountActivity.java deleted file mode 100644 index 8e23e9062c109117e05f95451b0a67822e8ea6bc..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountVerifiedAccountActivity.java +++ /dev/null @@ -1,66 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.activities; - -import org.mozilla.gecko.R; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; -import org.mozilla.gecko.fxa.login.State; -import org.mozilla.gecko.sync.setup.activities.ActivityUtils; - -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; - -/** - * Activity which displays "Account verified" success screen. - */ -public class FxAccountVerifiedAccountActivity extends FxAccountAbstractActivity { - private static final String LOG_TAG = FxAccountVerifiedAccountActivity.class.getSimpleName(); - - protected AndroidFxAccount fxAccount; - - public FxAccountVerifiedAccountActivity() { - super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST); - } - - /** - * {@inheritDoc} - */ - @Override - public void onCreate(Bundle icicle) { - Logger.debug(LOG_TAG, "onCreate(" + icicle + ")"); - - super.onCreate(icicle); - setContentView(R.layout.fxaccount_account_verified); - } - - @Override - public void onResume() { - super.onResume(); - this.fxAccount = getAndroidFxAccount(); - if (fxAccount == null) { - Logger.warn(LOG_TAG, "Could not get Firefox Account."); - setResult(RESULT_CANCELED); - finish(); - return; - } - State state = fxAccount.getState(); - if (!state.verified) { - Logger.warn(LOG_TAG, "Firefox Account is not verified; not displaying verified account activity."); - setResult(RESULT_CANCELED); - finish(); - return; - } - - View backToBrowsingButton = ensureFindViewById(null, R.id.button, "back to browsing button"); - backToBrowsingButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - ActivityUtils.openURLInFennec(v.getContext(), null); - } - }); - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/tasks/FxAccountCodeResender.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/tasks/FxAccountCodeResender.java deleted file mode 100644 index d98e97f0804cecd75d0f82625bbefb28628bf0c7..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/tasks/FxAccountCodeResender.java +++ /dev/null @@ -1,109 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.tasks; - -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -import org.mozilla.gecko.R; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.background.fxa.FxAccountClient; -import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; -import org.mozilla.gecko.background.fxa.FxAccountClient20; -import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; -import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; -import org.mozilla.gecko.fxa.login.Engaged; - -import android.content.Context; -import android.os.AsyncTask; -import android.widget.Toast; - -/** - * A helper class that provides a simple interface for requesting - * a Firefox Account verification email to be resent. - */ -public class FxAccountCodeResender { - private static final String LOG_TAG = FxAccountCodeResender.class.getSimpleName(); - - private static class FxAccountResendCodeTask extends FxAccountSetupTask<Void> { - protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName(); - - protected final byte[] sessionToken; - - public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient client, RequestDelegate<Void> delegate) { - super(context, null, client, null, delegate); - this.sessionToken = sessionToken; - } - - @Override - protected InnerRequestDelegate<Void> doInBackground(Void... arg0) { - try { - client.resendCode(sessionToken, innerDelegate); - latch.await(); - return innerDelegate; - } catch (Exception e) { - Logger.error(LOG_TAG, "Got exception signing in.", e); - delegate.handleError(e); - } - return null; - } - } - - private static class ResendCodeDelegate implements RequestDelegate<Void> { - public final Context context; - - public ResendCodeDelegate(Context context) { - this.context = context; - } - - @Override - public void handleError(Exception e) { - Logger.warn(LOG_TAG, "Got exception requesting fresh confirmation link; ignoring.", e); - Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_not_sent, Toast.LENGTH_LONG).show(); - } - - @Override - public void handleFailure(FxAccountClientRemoteException e) { - handleError(e); - } - - @Override - public void handleSuccess(Void result) { - Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_sent, Toast.LENGTH_SHORT).show(); - } - } - - /** - * Resends the account verification email, and displays an appropriate - * toast on both send success and failure. Note that because the underlying implementation - * uses {@link AsyncTask}, the provided context must be UI-capable and - * this method called from the UI thread. - * - * Note that it may actually be possible to run this (and the {@link AsyncTask}) method - * from a background thread - but this hasn't been tested. - * - * @param context A UI-capable Android context. - * @param fxAccount The Firefox Account to resend the code to. - */ - public static void resendCode(Context context, AndroidFxAccount fxAccount) { - RequestDelegate<Void> delegate = new ResendCodeDelegate(context); - - byte[] sessionToken; - try { - sessionToken = ((Engaged) fxAccount.getState()).getSessionToken(); - } catch (Exception e) { - delegate.handleError(e); - return; - } - if (sessionToken == null) { - delegate.handleError(new IllegalStateException("sessionToken should not be null")); - return; - } - - Executor executor = Executors.newSingleThreadExecutor(); - FxAccountClient client = new FxAccountClient20(fxAccount.getAccountServerURI(), executor); - new FxAccountResendCodeTask(context, sessionToken, client, delegate).execute(); - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/tasks/FxAccountCreateAccountTask.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/tasks/FxAccountCreateAccountTask.java deleted file mode 100644 index cf1c74e9490d4e589c7a2331d8089bfe5e0baf2a..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/tasks/FxAccountCreateAccountTask.java +++ /dev/null @@ -1,42 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.tasks; - -import java.io.UnsupportedEncodingException; -import java.util.Map; - -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.background.fxa.FxAccountClient; -import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; -import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; -import org.mozilla.gecko.background.fxa.PasswordStretcher; - -import android.content.Context; - -public class FxAccountCreateAccountTask extends FxAccountSetupTask<LoginResponse> { - private static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName(); - - protected final byte[] emailUTF8; - protected final PasswordStretcher passwordStretcher; - - public FxAccountCreateAccountTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, Map<String, String> queryParameters, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException { - super(context, progressDisplay, client, queryParameters, delegate); - this.emailUTF8 = email.getBytes("UTF-8"); - this.passwordStretcher = passwordStretcher; - } - - @Override - protected InnerRequestDelegate<LoginResponse> doInBackground(Void... arg0) { - try { - client.createAccountAndGetKeys(emailUTF8, passwordStretcher, queryParameters, innerDelegate); - latch.await(); - return innerDelegate; - } catch (Exception e) { - Logger.error(LOG_TAG, "Got exception logging in.", e); - delegate.handleError(e); - } - return null; - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/tasks/FxAccountSetupTask.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/tasks/FxAccountSetupTask.java deleted file mode 100644 index c5c9c429c288faf5662d30f01cbd0c1cfe32e83a..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/tasks/FxAccountSetupTask.java +++ /dev/null @@ -1,121 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.tasks; - -import java.util.Map; -import java.util.concurrent.CountDownLatch; - -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.background.fxa.FxAccountClient; -import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; -import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; -import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.InnerRequestDelegate; - -import android.content.Context; -import android.os.AsyncTask; - -/** - * An <code>AsyncTask</code> wrapper around signing up for, and signing in to, a - * Firefox Account. - * <p> - * It's strange to add explicit blocking to callback-threading code, but we do - * it here to take advantage of Android's built in support for background work. - * We really want to avoid making a threading mistake that brings down the whole - * process. - */ -public abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestDelegate<T>> { - private static final String LOG_TAG = FxAccountSetupTask.class.getSimpleName(); - - public interface ProgressDisplay { - public void showProgress(); - public void dismissProgress(); - } - - protected final Context context; - protected final FxAccountClient client; - protected final ProgressDisplay progressDisplay; - - // Initialized lazily. - protected byte[] quickStretchedPW; - - // AsyncTask's are one-time-use, so final members are fine. - protected final CountDownLatch latch = new CountDownLatch(1); - protected final InnerRequestDelegate<T> innerDelegate = new InnerRequestDelegate<T>(latch); - - protected final Map<String, String> queryParameters; - - protected final RequestDelegate<T> delegate; - - public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient client, Map<String, String> queryParameters, RequestDelegate<T> delegate) { - this.context = context; - this.client = client; - this.delegate = delegate; - this.progressDisplay = progressDisplay; - this.queryParameters = queryParameters; - } - - @Override - protected void onPreExecute() { - if (progressDisplay != null) { - progressDisplay.showProgress(); - } - } - - @Override - protected void onPostExecute(InnerRequestDelegate<T> result) { - if (progressDisplay != null) { - progressDisplay.dismissProgress(); - } - - // We are on the UI thread, and need to invoke these callbacks here to allow UI updating. - if (innerDelegate.failure != null) { - delegate.handleFailure(innerDelegate.failure); - } else if (innerDelegate.exception != null) { - delegate.handleError(innerDelegate.exception); - } else { - delegate.handleSuccess(result.response); - } - } - - @Override - protected void onCancelled(InnerRequestDelegate<T> result) { - if (progressDisplay != null) { - progressDisplay.dismissProgress(); - } - delegate.handleError(new IllegalStateException("Task was cancelled.")); - } - - protected static class InnerRequestDelegate<T> implements RequestDelegate<T> { - protected final CountDownLatch latch; - public T response; - public Exception exception; - public FxAccountClientRemoteException failure; - - protected InnerRequestDelegate(CountDownLatch latch) { - this.latch = latch; - } - - @Override - public void handleError(Exception e) { - Logger.error(LOG_TAG, "Got exception."); - this.exception = e; - latch.countDown(); - } - - @Override - public void handleFailure(FxAccountClientRemoteException e) { - Logger.warn(LOG_TAG, "Got failure."); - this.failure = e; - latch.countDown(); - } - - @Override - public void handleSuccess(T result) { - Logger.info(LOG_TAG, "Got success."); - this.response = result; - latch.countDown(); - } - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/tasks/FxAccountSignInTask.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/tasks/FxAccountSignInTask.java deleted file mode 100644 index dd5ae0d5e937309e6a7206c2413e2b8db8993d88..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/tasks/FxAccountSignInTask.java +++ /dev/null @@ -1,42 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.tasks; - -import java.io.UnsupportedEncodingException; -import java.util.Map; - -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.background.fxa.FxAccountClient; -import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; -import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; -import org.mozilla.gecko.background.fxa.PasswordStretcher; - -import android.content.Context; - -public class FxAccountSignInTask extends FxAccountSetupTask<LoginResponse> { - protected static final String LOG_TAG = FxAccountSignInTask.class.getSimpleName(); - - protected final byte[] emailUTF8; - protected final PasswordStretcher passwordStretcher; - - public FxAccountSignInTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, Map<String, String> queryParameters, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException { - super(context, progressDisplay, client, queryParameters, delegate); - this.emailUTF8 = email.getBytes("UTF-8"); - this.passwordStretcher = passwordStretcher; - } - - @Override - protected InnerRequestDelegate<LoginResponse> doInBackground(Void... arg0) { - try { - client.loginAndGetKeys(emailUTF8, passwordStretcher, queryParameters, innerDelegate); - latch.await(); - return innerDelegate; - } catch (Exception e) { - Logger.error(LOG_TAG, "Got exception signing in.", e); - delegate.handleError(e); - } - return null; - } -} diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/tasks/FxAccountUnlockCodeResender.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/tasks/FxAccountUnlockCodeResender.java deleted file mode 100644 index 0f32f3e22272cad6c1ee4e16a3148a0e2e69edbe..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/tasks/FxAccountUnlockCodeResender.java +++ /dev/null @@ -1,105 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.tasks; - -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -import org.mozilla.gecko.R; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.background.fxa.FxAccountClient; -import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; -import org.mozilla.gecko.background.fxa.FxAccountClient20; -import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; - -import android.content.Context; -import android.os.AsyncTask; -import android.widget.Toast; - -/** - * A helper class that provides a simple interface for requesting a Firefox - * Account unlock account email be (re)-sent. - */ -public class FxAccountUnlockCodeResender { - private static final String LOG_TAG = FxAccountUnlockCodeResender.class.getSimpleName(); - - private static class FxAccountUnlockCodeTask extends FxAccountSetupTask<Void> { - protected static final String LOG_TAG = FxAccountUnlockCodeTask.class.getSimpleName(); - - protected final byte[] emailUTF8; - - public FxAccountUnlockCodeTask(Context context, byte[] emailUTF8, FxAccountClient client, RequestDelegate<Void> delegate) { - super(context, null, client, null, delegate); - this.emailUTF8 = emailUTF8; - } - - @Override - protected InnerRequestDelegate<Void> doInBackground(Void... arg0) { - try { - client.resendUnlockCode(emailUTF8, innerDelegate); - latch.await(); - return innerDelegate; - } catch (Exception e) { - Logger.error(LOG_TAG, "Got exception signing in.", e); - delegate.handleError(e); - } - return null; - } - } - - private static class ResendUnlockCodeDelegate implements RequestDelegate<Void> { - public final Context context; - - public ResendUnlockCodeDelegate(Context context) { - this.context = context; - } - - @Override - public void handleError(Exception e) { - Logger.warn(LOG_TAG, "Got exception requesting fresh unlock code; ignoring.", e); - Toast.makeText(context, R.string.fxaccount_unlock_code_not_sent, Toast.LENGTH_LONG).show(); - } - - @Override - public void handleFailure(FxAccountClientRemoteException e) { - handleError(e); - } - - @Override - public void handleSuccess(Void result) { - Toast.makeText(context, R.string.fxaccount_unlock_code_sent, Toast.LENGTH_SHORT).show(); - } - } - - /** - * Resends the account unlock email, and displays an appropriate toast on both - * send success and failure. Note that because the underlying implementation - * uses {@link AsyncTask}, the provided context must be UI-capable and this - * method called from the UI thread. - * - * Note that it may actually be possible to run this (and the - * {@link AsyncTask}) method from a background thread - but this hasn't been - * tested. - * - * @param context - * A UI-capable Android context. - * @param authServerURI - * to send request to. - * @param emailUTF8 - * bytes of email address identifying account; null indicates a local failure. - */ - public static void resendUnlockCode(Context context, String authServerURI, byte[] emailUTF8) { - RequestDelegate<Void> delegate = new ResendUnlockCodeDelegate(context); - - if (emailUTF8 == null) { - delegate.handleError(new IllegalArgumentException("emailUTF8 must not be null")); - return; - } - - final Executor executor = Executors.newSingleThreadExecutor(); - final FxAccountClient client = new FxAccountClient20(authServerURI, executor); - new FxAccountUnlockCodeTask(context, emailUTF8, client, delegate).execute(); - } -} diff --git a/mobile/android/services/src/main/res/drawable-hdpi/fxaccount_checkbox.png b/mobile/android/services/src/main/res/drawable-hdpi/fxaccount_checkbox.png deleted file mode 100755 index ef9f9ea16a2416ac05eb7740ed70d2e01bd469b3..0000000000000000000000000000000000000000 Binary files a/mobile/android/services/src/main/res/drawable-hdpi/fxaccount_checkbox.png and /dev/null differ diff --git a/mobile/android/services/src/main/res/drawable-hdpi/fxaccount_ddarrow_inactive.png b/mobile/android/services/src/main/res/drawable-hdpi/fxaccount_ddarrow_inactive.png deleted file mode 100644 index c8b4cbbcb1ba897630bdbcbf02feb0751aa9da0a..0000000000000000000000000000000000000000 Binary files a/mobile/android/services/src/main/res/drawable-hdpi/fxaccount_ddarrow_inactive.png and /dev/null differ diff --git a/mobile/android/services/src/main/res/drawable-hdpi/fxaccount_intro.png b/mobile/android/services/src/main/res/drawable-hdpi/fxaccount_intro.png deleted file mode 100644 index 635343180c9fac0fd15cb6e61ddef15fc81e0f97..0000000000000000000000000000000000000000 Binary files a/mobile/android/services/src/main/res/drawable-hdpi/fxaccount_intro.png and /dev/null differ diff --git a/mobile/android/services/src/main/res/drawable-hdpi/fxaccount_mail.png b/mobile/android/services/src/main/res/drawable-hdpi/fxaccount_mail.png deleted file mode 100755 index 36540e5f3564baded73dab080cbb9c46f85f9184..0000000000000000000000000000000000000000 Binary files a/mobile/android/services/src/main/res/drawable-hdpi/fxaccount_mail.png and /dev/null differ diff --git a/mobile/android/services/src/main/res/drawable-v12/fxaccount_password_active.xml b/mobile/android/services/src/main/res/drawable-v12/fxaccount_password_active.xml deleted file mode 100644 index 053a2536e43dbccc5dcea9fbb2a106807468d050..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable-v12/fxaccount_password_active.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid - android:color="@android:color/white" /> - <stroke - android:width="@dimen/fxaccount_stroke_width" - android:color="@color/fxaccount_input_borderActive" /> - <corners - android:radius="@dimen/fxaccount_corner_radius" - android:topLeftRadius="@dimen/fxaccount_corner_radius" - android:topRightRadius="0dp" - android:bottomLeftRadius="@dimen/fxaccount_corner_radius" - android:bottomRightRadius="0dp" /> -</shape> diff --git a/mobile/android/services/src/main/res/drawable-v12/fxaccount_password_button_hide_active.xml b/mobile/android/services/src/main/res/drawable-v12/fxaccount_password_button_hide_active.xml deleted file mode 100644 index 99832adebd625df29c9870116f78c301e0492836..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable-v12/fxaccount_password_button_hide_active.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid - android:color="@color/fxaccount_password_hide_backgroundcolor" /> - <stroke - android:width="@dimen/fxaccount_stroke_width" - android:color="@color/fxaccount_input_borderActive" /> - <corners - android:radius="@dimen/fxaccount_corner_radius" - android:topLeftRadius="0dp" - android:topRightRadius="@dimen/fxaccount_corner_radius" - android:bottomLeftRadius="0dp" - android:bottomRightRadius="@dimen/fxaccount_corner_radius" /> -</shape> diff --git a/mobile/android/services/src/main/res/drawable-v12/fxaccount_password_button_show_active.xml b/mobile/android/services/src/main/res/drawable-v12/fxaccount_password_button_show_active.xml deleted file mode 100644 index 76b7550d7be1761540c05ff7ebfbc981ae4d1a8c..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable-v12/fxaccount_password_button_show_active.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid - android:color="@color/fxaccount_password_show_backgroundcolor" /> - <stroke - android:width="@dimen/fxaccount_stroke_width" - android:color="@color/fxaccount_input_borderActive" /> - <corners - android:radius="@dimen/fxaccount_corner_radius" - android:topLeftRadius="0dp" - android:topRightRadius="@dimen/fxaccount_corner_radius" - android:bottomLeftRadius="0dp" - android:bottomRightRadius="@dimen/fxaccount_corner_radius" /> -</shape> diff --git a/mobile/android/services/src/main/res/drawable-v12/fxaccount_password_inactive.xml b/mobile/android/services/src/main/res/drawable-v12/fxaccount_password_inactive.xml deleted file mode 100644 index 397b12c1bb6f42115792e00bd8739738bdf2612c..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable-v12/fxaccount_password_inactive.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid - android:color="@android:color/white" /> - <stroke - android:width="@dimen/fxaccount_stroke_width" - android:color="@color/fxaccount_input_borderInactive" /> - <corners - android:radius="@dimen/fxaccount_corner_radius" - android:topLeftRadius="@dimen/fxaccount_corner_radius" - android:topRightRadius="0dp" - android:bottomLeftRadius="@dimen/fxaccount_corner_radius" - android:bottomRightRadius="0dp" /> -</shape> diff --git a/mobile/android/services/src/main/res/drawable-xhdpi/fxaccount_intro.png b/mobile/android/services/src/main/res/drawable-xhdpi/fxaccount_intro.png deleted file mode 100644 index 5385d6b6fd0fbf253a905564bc73b2d86565b07a..0000000000000000000000000000000000000000 Binary files a/mobile/android/services/src/main/res/drawable-xhdpi/fxaccount_intro.png and /dev/null differ diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_button_background.xml b/mobile/android/services/src/main/res/drawable/fxaccount_button_background.xml deleted file mode 100644 index f59ba3828313b3fed3d0dc29bd8499b281538772..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_button_background.xml +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_pressed="true" - android:drawable="@drawable/fxaccount_button_background_pressed" /> - <item android:state_enabled="false" - android:drawable="@drawable/fxaccount_button_background_disabled" /> - <item android:state_enabled="true" - android:drawable="@drawable/fxaccount_button_background_enabled" /> -</selector> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_button_background_disabled.xml b/mobile/android/services/src/main/res/drawable/fxaccount_button_background_disabled.xml deleted file mode 100644 index c9b897397121c7d776eeb327833a88edc93ddb20..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_button_background_disabled.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle" > - <solid android:color="@color/fxaccount_button_background_inactive" /> - <corners - android:radius="@dimen/fxaccount_corner_radius" /> -</shape> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_button_background_enabled.xml b/mobile/android/services/src/main/res/drawable/fxaccount_button_background_enabled.xml deleted file mode 100644 index 17b0f80cebab14aa9207308a98df8da58009ada8..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_button_background_enabled.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle" > - <solid android:color="@color/fxaccount_button_background_active" /> - <corners - android:radius="@dimen/fxaccount_corner_radius" /> -</shape> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_button_background_pressed.xml b/mobile/android/services/src/main/res/drawable/fxaccount_button_background_pressed.xml deleted file mode 100644 index 84d3b08a1d0c86e97b1dc5f1b53941d153992ade..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_button_background_pressed.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle" > - <solid android:color="@color/fxaccount_button_background_hit" /> - <corners - android:radius="@dimen/fxaccount_corner_radius" /> -</shape> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_button_color.xml b/mobile/android/services/src/main/res/drawable/fxaccount_button_color.xml deleted file mode 100644 index aa2a4674547dddc85b6408e65ce00b32f7fcf1c8..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_button_color.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@color/fxaccount_button_textColor" /> -</selector> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_checkbox_textcolor.xml b/mobile/android/services/src/main/res/drawable/fxaccount_checkbox_textcolor.xml deleted file mode 100644 index 12d2f931de09899a80e3f4183d0ed665a93e58e9..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_checkbox_textcolor.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" android:color="@color/fxaccount_input_textColor_inactive" /> - <item android:state_pressed="true" android:color="@color/fxaccount_input_textColor_pressed" /> - <item android:color="@color/fxaccount_input_textColor" /> -</selector> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_linkitem_textcolor.xml b/mobile/android/services/src/main/res/drawable/fxaccount_linkitem_textcolor.xml deleted file mode 100644 index 739b4fd2ad47f2032a43e57ea2a36dd74761a51a..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_linkitem_textcolor.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" android:color="@color/fxaccount_link_textColor_inactive" /> - <item android:state_pressed="true" android:color="@color/fxaccount_link_textColor_pressed" /> - <item android:color="@color/fxaccount_link_textColor" /> -</selector> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_password_active.xml b/mobile/android/services/src/main/res/drawable/fxaccount_password_active.xml deleted file mode 100644 index 1756783960a799bd429437a6475db0d6ad858b5b..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_password_active.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid - android:color="@android:color/white" /> - <stroke - android:width="@dimen/fxaccount_stroke_width" - android:color="@color/fxaccount_input_borderActive" /> - <!-- On Android pre v12/3.0/Gingerbread, bottom left and bottom - right are swapped. These values correct this bug; the resources - that don't need correction are in res/drawable-v12. --> - <corners - android:radius="@dimen/fxaccount_corner_radius" - android:topLeftRadius="@dimen/fxaccount_corner_radius" - android:topRightRadius="0dp" - android:bottomLeftRadius="0dp" - android:bottomRightRadius="@dimen/fxaccount_corner_radius" /> -</shape> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_password_background.xml b/mobile/android/services/src/main/res/drawable/fxaccount_password_background.xml deleted file mode 100644 index 226556d1477572de23c2e57f00dc51784842c86a..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_password_background.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item - android:state_focused="false" - android:drawable="@drawable/fxaccount_password_inactive" /> - <item - android:drawable="@drawable/fxaccount_password_active" /> -</selector> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_password_button_hide_active.xml b/mobile/android/services/src/main/res/drawable/fxaccount_password_button_hide_active.xml deleted file mode 100644 index c9dfaaed730a7212cf3bdfbd4e24ca8528e2ceab..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_password_button_hide_active.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid - android:color="@color/fxaccount_password_hide_backgroundcolor" /> - <stroke - android:width="@dimen/fxaccount_stroke_width" - android:color="@color/fxaccount_input_borderActive" /> - <!-- On Android pre v12/3.0/Gingerbread, bottom left and bottom - right are swapped. These values correct this bug; the resources - that don't need correction are in res/drawable-v12. --> - <corners - android:radius="@dimen/fxaccount_corner_radius" - android:topLeftRadius="0dp" - android:topRightRadius="@dimen/fxaccount_corner_radius" - android:bottomRightRadius="0dp" - android:bottomLeftRadius="@dimen/fxaccount_corner_radius" /> -</shape> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_password_button_hide_background.xml b/mobile/android/services/src/main/res/drawable/fxaccount_password_button_hide_background.xml deleted file mode 100644 index db9c588a5f41d544bdb95cf0a340e298ae4a8fcc..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_password_button_hide_background.xml +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item - android:drawable="@drawable/fxaccount_password_button_hide_active" /> -</selector> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_password_button_show_active.xml b/mobile/android/services/src/main/res/drawable/fxaccount_password_button_show_active.xml deleted file mode 100644 index d5ca81e484e64e78d09b12ecb666df3ec8003d46..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_password_button_show_active.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid - android:color="@color/fxaccount_password_show_backgroundcolor" /> - <stroke - android:width="@dimen/fxaccount_stroke_width" - android:color="@color/fxaccount_input_borderActive" /> - <!-- On Android pre v12/3.0/Gingerbread, bottom left and bottom - right are swapped. These values correct this bug; the resources - that don't need correction are in res/drawable-v12. --> - <corners - android:radius="@dimen/fxaccount_corner_radius" - android:topLeftRadius="0dp" - android:topRightRadius="@dimen/fxaccount_corner_radius" - android:bottomRightRadius="0dp" - android:bottomLeftRadius="@dimen/fxaccount_corner_radius" /> -</shape> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_password_button_show_background.xml b/mobile/android/services/src/main/res/drawable/fxaccount_password_button_show_background.xml deleted file mode 100644 index a99ac85e1e51483de12bcf4ae0f1e8441b268a38..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_password_button_show_background.xml +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item - android:drawable="@drawable/fxaccount_password_button_show_active" /> -</selector> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_password_inactive.xml b/mobile/android/services/src/main/res/drawable/fxaccount_password_inactive.xml deleted file mode 100644 index 6612270106524fda92e5e44d0cc06ef14e0cee34..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_password_inactive.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid - android:color="@android:color/white" /> - <stroke - android:width="@dimen/fxaccount_stroke_width" - android:color="@color/fxaccount_input_borderInactive" /> - <!-- On Android pre v12/3.0/Gingerbread, bottom left and bottom - right are swapped. These values correct this bug; the resources - that don't need correction are in res/drawable-v12. --> - <corners - android:radius="@dimen/fxaccount_corner_radius" - android:topLeftRadius="@dimen/fxaccount_corner_radius" - android:topRightRadius="0dp" - android:bottomLeftRadius="0dp" - android:bottomRightRadius="@dimen/fxaccount_corner_radius" /> -</shape> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_textfield_active.xml b/mobile/android/services/src/main/res/drawable/fxaccount_textfield_active.xml deleted file mode 100644 index 598ca8ca7e27cc54644272b0444f531c77a1a122..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_textfield_active.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid - android:color="@android:color/white" /> - <stroke - android:width="@dimen/fxaccount_stroke_width" - android:color="@color/fxaccount_input_borderActive" /> - <corners - android:radius="@dimen/fxaccount_corner_radius" /> -</shape> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_textfield_background.xml b/mobile/android/services/src/main/res/drawable/fxaccount_textfield_background.xml deleted file mode 100644 index 860e8bb50f3184cb96158d6925a7a688a12f3ac2..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_textfield_background.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item - android:state_focused="false" - android:drawable="@drawable/fxaccount_textfield_inactive" /> - <item - android:drawable="@drawable/fxaccount_textfield_active" /> -</selector> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_textfield_inactive.xml b/mobile/android/services/src/main/res/drawable/fxaccount_textfield_inactive.xml deleted file mode 100644 index 4abe526f109b2af7dfc9a262a2c02f96c6ac1670..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_textfield_inactive.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid - android:color="@android:color/white" /> - <stroke - android:width="@dimen/fxaccount_stroke_width" - android:color="@color/fxaccount_input_borderInactive" /> - <corners - android:radius="@dimen/fxaccount_corner_radius" /> -</shape> diff --git a/mobile/android/services/src/main/res/drawable/fxaccount_textview_error_background.xml b/mobile/android/services/src/main/res/drawable/fxaccount_textview_error_background.xml deleted file mode 100644 index 9862337af42640db4942a7e4c62d8942d010aab1..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/drawable/fxaccount_textview_error_background.xml +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle" > - <solid android:color="@color/fxaccount_error" /> -</shape> diff --git a/mobile/android/services/src/main/res/layout/fxaccount_account_verified.xml b/mobile/android/services/src/main/res/layout/fxaccount_account_verified.xml deleted file mode 100644 index c72d58cbefb048356ea9dd09572d998cd01503bb..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/layout/fxaccount_account_verified.xml +++ /dev/null @@ -1,46 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. ---> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:fillViewport="true" > - - <LinearLayout style="@style/FxAccountMiddle" > - - <TextView - style="@style/FxAccountHeaderItem" - android:text="@string/fxaccount_account_verified_sub_header" > - </TextView> - - <ImageView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="45dp" - android:contentDescription="@string/fxaccount_empty_contentDescription" - android:src="@drawable/fxaccount_checkbox" > - </ImageView> - - <TextView - style="@style/FxAccountTextItem" - android:layout_marginBottom="40dp" - android:text="@string/fxaccount_account_verified_description" - android:textSize="18sp" /> - - <Button - android:id="@+id/button" - style="@style/FxAccountButton" - android:text="@string/fxaccount_back_to_browsing" /> - - <LinearLayout style="@style/FxAccountSpacer" /> - - <ImageView - style="@style/FxAccountIcon" - android:contentDescription="@string/fxaccount_empty_contentDescription" /> - </LinearLayout> - -</ScrollView> diff --git a/mobile/android/services/src/main/res/layout/fxaccount_confirm_account.xml b/mobile/android/services/src/main/res/layout/fxaccount_confirm_account.xml deleted file mode 100644 index 6df9f319047736f1ff36c6baee394e1653160133..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/layout/fxaccount_confirm_account.xml +++ /dev/null @@ -1,60 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. ---> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:fillViewport="true" > - - <LinearLayout style="@style/FxAccountMiddle" > - - <LinearLayout style="@style/FxAccountSpacer" /> - - <TextView - style="@style/FxAccountHeaderItem" - android:text="@string/fxaccount_confirm_account_header" > - </TextView> - - <ImageView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="40dp" - android:background="@android:color/transparent" - android:contentDescription="@string/fxaccount_empty_contentDescription" - android:src="@drawable/fxaccount_mail" > - </ImageView> - - <TextView - android:id="@+id/verification_link_text" - style="@style/FxAccountTextItem" - android:layout_marginBottom="40dp" - android:text="@string/fxaccount_confirm_account_verification_link" - android:textSize="18sp" /> - - <Button - android:id="@+id/button" - style="@style/FxAccountButton" - android:text="@string/fxaccount_back_to_browsing" /> - - <TextView - android:id="@+id/resend_confirmation_email_link" - style="@style/FxAccountLinkItem" - android:text="@string/fxaccount_confirm_account_resend_email" /> - - <TextView - android:id="@+id/change_confirmation_email_link" - style="@style/FxAccountLinkItem" - android:text="@string/fxaccount_confirm_account_change_email" /> - - <LinearLayout style="@style/FxAccountSpacer" /> - - <ImageView - style="@style/FxAccountIcon" - android:contentDescription="@string/fxaccount_empty_contentDescription" /> - </LinearLayout> - -</ScrollView> diff --git a/mobile/android/services/src/main/res/layout/fxaccount_create_account.xml b/mobile/android/services/src/main/res/layout/fxaccount_create_account.xml deleted file mode 100644 index 9185b6c1710d5da4e37bde413ea695a4cb6a6a89..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/layout/fxaccount_create_account.xml +++ /dev/null @@ -1,116 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. ---> - -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:fillViewport="true" > - - <LinearLayout - android:id="@+id/create_account_view" - style="@style/FxAccountMiddle" > - - <LinearLayout style="@style/FxAccountSpacer" /> - - <TextView - style="@style/FxAccountHeaderItem" - android:text="@string/fxaccount_create_account_header" /> - - <include layout="@layout/fxaccount_custom_server_view" /> - - <TextView - android:id="@+id/remote_error" - style="@style/FxAccountErrorItem" /> - - <include layout="@layout/fxaccount_email_password_view" /> - - <TextView - style="@style/FxAccountTextItem" - android:text="@string/fxaccount_create_account_password_length_restriction" - android:textColor="@color/fxaccount_textColorSubdued" - android:textSize="12sp" /> - - <!-- Per http://stackoverflow.com/questions/2359176/android-edittext-onclicklistener, not allowing focus allows us to highjack the click. --> - - <EditText - android:id="@+id/year_edit" - style="@style/FxAccountEditItem" - android:drawableRight="@drawable/fxaccount_ddarrow_inactive" - android:focusable="false" - android:hint="@string/fxaccount_create_account_year_of_birth" - android:inputType="none" /> - - <LinearLayout - android:id="@+id/month_day_combo" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:weightSum="1" > - - <EditText - android:id="@+id/month_edit" - style="@style/FxAccountEditItem" - android:layout_marginRight="10dp" - android:drawableRight="@drawable/fxaccount_ddarrow_inactive" - android:focusable="false" - android:layout_weight="0.5" - android:maxLength="3" - android:hint="@string/fxaccount_create_account_month_of_birth" - android:inputType="none" /> - - <EditText - android:id="@+id/day_edit" - style="@style/FxAccountEditItem" - android:layout_marginLeft="10dp" - android:drawableRight="@drawable/fxaccount_ddarrow_inactive" - android:focusable="false" - android:layout_weight="0.5" - android:hint="@string/fxaccount_create_account_day_of_birth" - android:inputType="none" /> - </LinearLayout> - - <RelativeLayout - style="@style/FxAccountButtonLayout" > - - <ProgressBar - android:id="@+id/progress" - style="@style/FxAccountProgress" /> - - <Button - android:id="@+id/button" - style="@style/FxAccountProgressButton" - android:text="@string/fxaccount_create_account_button" /> - </RelativeLayout> - - <TextView - android:id="@+id/sign_in_instead_link" - style="@style/FxAccountLinkItem" - android:focusable="true" - android:layout_marginBottom="0dp" - android:text="@string/fxaccount_create_account_sign_in_instead" /> - - <CheckBox - android:id="@+id/choose_what_to_sync_checkbox" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="10dp" - android:text="@string/fxaccount_create_account_choose_what_to_sync" /> - - <TextView - android:id="@+id/policy" - style="@style/FxAccountPolicyItem" - android:text="@string/fxaccount_create_account_policy_text" /> - - <LinearLayout style="@style/FxAccountSpacer" /> - - <ImageView - style="@style/FxAccountIcon" - android:contentDescription="@string/fxaccount_empty_contentDescription" /> - </LinearLayout> - -</ScrollView> diff --git a/mobile/android/services/src/main/res/layout/fxaccount_create_account_not_allowed.xml b/mobile/android/services/src/main/res/layout/fxaccount_create_account_not_allowed.xml deleted file mode 100644 index 5efc077eefb1e20ecb3f01fdacab906a91f00886..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/layout/fxaccount_create_account_not_allowed.xml +++ /dev/null @@ -1,42 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. ---> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:fillViewport="true" > - - <LinearLayout - android:id="@+id/create_account_not_allowed_view" - style="@style/FxAccountMiddle" > - - <LinearLayout style="@style/FxAccountSpacer" /> - - <TextView - style="@style/FxAccountHeaderItem" - android:text="@string/fxaccount_account_create_not_allowed" > - </TextView> - - <TextView - style="@style/FxAccountTextItem" - android:layout_marginBottom="45dp" - android:layout_marginTop="45dp" - android:text="@string/fxaccount_account_create_not_allowed_you_must_meet_certain_age_requirements" > - </TextView> - - <TextView - android:id="@+id/learn_more_link" - style="@style/FxAccountLinkifiedItem" - android:text="@string/fxaccount_account_create_not_allowed_learn_more" /> - - <LinearLayout style="@style/FxAccountSpacer" /> - - <ImageView - style="@style/FxAccountIcon" - android:contentDescription="@string/fxaccount_empty_contentDescription" /> - </LinearLayout> - -</ScrollView> diff --git a/mobile/android/services/src/main/res/layout/fxaccount_custom_server_view.xml b/mobile/android/services/src/main/res/layout/fxaccount_custom_server_view.xml deleted file mode 100644 index b135207475c796b9afa10257e70bf58c5eda586d..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/layout/fxaccount_custom_server_view.xml +++ /dev/null @@ -1,116 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. ---> - -<merge xmlns:android="http://schemas.android.com/apk/res/android" > - - <LinearLayout - android:id="@+id/account_server_layout" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="10dp" - android:background="@color/fxaccount_error_preference_backgroundcolor" - android:gravity="center_vertical" - android:minHeight="?android:attr/listPreferredItemHeight" > - - <ImageView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:contentDescription="@string/fxaccount_empty_contentDescription" - android:gravity="center" - android:minWidth="48dip" - android:padding="10dip" - android:src="@drawable/fxaccount_sync_error" /> - - <LinearLayout - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:orientation="vertical" - android:paddingBottom="6dip" - android:paddingRight="10dip" - android:paddingTop="6dip" > - - <TextView - android:id="@+id/account_server_title" - style="@style/FxAccountTextItem" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="0dp" - android:gravity="center_vertical" - android:text="@string/fxaccount_custom_server_account_title" > - </TextView> - - <TextView - android:id="@+id/account_server_summary" - style="@style/FxAccountTextItem" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="0dp" - android:ellipsize="middle" - android:focusable="true" - android:focusableInTouchMode="true" - android:singleLine="true" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textSize="12sp" /> - </LinearLayout> - </LinearLayout> - - <LinearLayout - android:id="@+id/sync_server_layout" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="10dp" - android:background="@color/fxaccount_error_preference_backgroundcolor" - android:gravity="center_vertical" - android:minHeight="?android:attr/listPreferredItemHeight" > - - <ImageView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:contentDescription="@string/fxaccount_empty_contentDescription" - android:gravity="center" - android:minWidth="48dip" - android:padding="10dip" - android:src="@drawable/fxaccount_sync_error" /> - - <LinearLayout - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:orientation="vertical" - android:paddingBottom="6dip" - android:paddingRight="10dip" - android:paddingTop="6dip" > - - <TextView - android:id="@+id/sync_server_title" - style="@style/FxAccountTextItem" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="0dp" - android:gravity="center_vertical" - android:text="@string/fxaccount_custom_server_sync_title" > - </TextView> - - <TextView - android:id="@+id/sync_server_summary" - style="@style/FxAccountTextItem" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="0dp" - android:ellipsize="marquee" - android:focusable="true" - android:focusableInTouchMode="true" - android:singleLine="true" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textSize="12sp" /> - </LinearLayout> - </LinearLayout> - -</merge> diff --git a/mobile/android/services/src/main/res/layout/fxaccount_email_password_view.xml b/mobile/android/services/src/main/res/layout/fxaccount_email_password_view.xml deleted file mode 100644 index fd398a3eee753a31345dd51e48bb48b658990852..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/layout/fxaccount_email_password_view.xml +++ /dev/null @@ -1,88 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. ---> - -<merge xmlns:android="http://schemas.android.com/apk/res/android" > - - <LinearLayout - android:id="@+id/email_password_view" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:orientation="vertical" > - - <AutoCompleteTextView - android:id="@+id/email" - style="@style/FxAccountEditItem" - android:layout_marginBottom="10dp" - android:completionThreshold="2" - android:ems="10" - android:hint="@string/fxaccount_email_hint" - android:inputType="textEmailAddress" > - - <requestFocus /> - </AutoCompleteTextView> - - <LinearLayout - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="10dp" - android:orientation="horizontal" > - - <EditText - android:id="@+id/password" - style="@style/FxAccountEditItem" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_marginBottom="0dp" - android:background="@drawable/fxaccount_password_background" - android:ems="10" - android:hint="@string/fxaccount_password_hint" - android:inputType="textPassword" /> - - <!-- For the following, I beg forgiveness. The show/hide button is a - toggle button; its text depends on its state. The text for each - state could be a different length. We want to maintain the - button's width regardless of its state. To achieve this, we - size the actual button to its container, and include two - invisible (but present for layout purposes) buttons, one of - each state. The container wraps the larger of the two dummy - buttons; the actual button sizes to the container; and we're - happy. Be thankful there are not three buttons! --> - <FrameLayout - android:layout_width="wrap_content" - android:layout_height="fill_parent" - android:layout_weight="0" - android:orientation="horizontal" > - - <Button - android:id="@+id/show_password" - style="@style/FxAccountShowHidePasswordButton" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:text="@string/fxaccount_password_show" > - </Button> - - <Button - style="@style/FxAccountShowHidePasswordButton" - android:layout_width="wrap_content" - android:layout_height="fill_parent" - android:text="@string/fxaccount_password_show" - android:visibility="invisible" > - </Button> - - <Button - style="@style/FxAccountShowHidePasswordButton" - android:layout_width="wrap_content" - android:layout_height="fill_parent" - android:text="@string/fxaccount_password_hide" - android:visibility="invisible" > - </Button> - </FrameLayout> - </LinearLayout> - </LinearLayout> - -</merge> diff --git a/mobile/android/services/src/main/res/layout/fxaccount_finish_migrating.xml b/mobile/android/services/src/main/res/layout/fxaccount_finish_migrating.xml deleted file mode 100644 index 31fdb1c312ec8f491aec0b3c32e7bc657a41a152..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/layout/fxaccount_finish_migrating.xml +++ /dev/null @@ -1,59 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. ---> - -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:fillViewport="true" > - - <LinearLayout - android:id="@+id/update_credentials_view" - style="@style/FxAccountMiddle" > - - <LinearLayout style="@style/FxAccountSpacer" /> - - <TextView - style="@style/FxAccountHeaderItem" - android:text="@string/fxaccount_finish_migrating_header" /> - - <include layout="@layout/fxaccount_custom_server_view" /> - - <TextView - android:id="@+id/remote_error" - style="@style/FxAccountErrorItem" /> - - <include layout="@layout/fxaccount_email_password_view" /> - - <TextView - style="@style/FxAccountTextItem" - android:text="@string/fxaccount_finish_migrating_description" /> - - <RelativeLayout style="@style/FxAccountButtonLayout" > - - <ProgressBar - android:id="@+id/progress" - style="@style/FxAccountProgress" /> - - <Button - android:id="@+id/button" - style="@style/FxAccountProgressButton" - android:text="@string/fxaccount_finish_migrating_button_label" /> - </RelativeLayout> - - <TextView - android:id="@+id/forgot_password_link" - style="@style/FxAccountLinkifiedItem" - android:text="@string/fxaccount_sign_in_forgot_password" /> - - <LinearLayout style="@style/FxAccountSpacer" /> - - <ImageView - style="@style/FxAccountIcon" - android:contentDescription="@string/fxaccount_empty_contentDescription" /> - </LinearLayout> - -</ScrollView> diff --git a/mobile/android/services/src/main/res/layout/fxaccount_get_started.xml b/mobile/android/services/src/main/res/layout/fxaccount_get_started.xml deleted file mode 100644 index 795c5f12c7fe59f5b140ad8cd015ed23fce5a136..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/layout/fxaccount_get_started.xml +++ /dev/null @@ -1,59 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. ---> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:fillViewport="true" > - - <!-- In order to animate the icon from the bottom of the screen, - the icon needs to pass over the padding. --> - <LinearLayout - android:id="@+id/intro_view" - android:clipToPadding="false" - style="@style/FxAccountMiddle" > - - <LinearLayout style="@style/FxAccountSpacer" /> - - <TextView - style="@style/FxAccountHeaderItem" - android:text="@string/fxaccount_getting_started_welcome_to_sync" > - </TextView> - - <ImageView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginTop="20dp" - android:layout_marginBottom="20dp" - android:contentDescription="@string/fxaccount_empty_contentDescription" - android:src="@drawable/fxaccount_intro" > - </ImageView> - - <TextView - style="@style/FxAccountTextItem" - android:text="@string/fxaccount_getting_started_description" > - </TextView> - - <Button - android:id="@+id/get_started_button" - style="@style/FxAccountButton" - android:text="@string/fxaccount_getting_started_get_started" /> - - <TextView - android:id="@+id/old_firefox" - style="@style/FxAccountLinkifiedItem" - android:text="@string/fxaccount_getting_started_old_firefox" /> - - <LinearLayout style="@style/FxAccountSpacer" /> - - <ImageView - android:id="@+id/icon" - style="@style/FxAccountIcon" - android:contentDescription="@string/fxaccount_empty_contentDescription" /> - </LinearLayout> - -</ScrollView> diff --git a/mobile/android/services/src/main/res/layout/fxaccount_migration_finished.xml b/mobile/android/services/src/main/res/layout/fxaccount_migration_finished.xml deleted file mode 100644 index cf5a9c7f00fd93cd1454d6e5e6e1d047663e7992..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/layout/fxaccount_migration_finished.xml +++ /dev/null @@ -1,46 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. ---> -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:fillViewport="true" > - - <LinearLayout style="@style/FxAccountMiddle" > - - <TextView - style="@style/FxAccountHeaderItem" - android:text="@string/fxaccount_migration_finished_header" > - </TextView> - - <ImageView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="45dp" - android:contentDescription="@string/fxaccount_empty_contentDescription" - android:src="@drawable/fxaccount_checkbox" > - </ImageView> - - <TextView - style="@style/FxAccountTextItem" - android:layout_marginBottom="40dp" - android:text="@string/fxaccount_migration_finished_description" - android:textSize="18sp" /> - - <Button - android:id="@+id/button" - style="@style/FxAccountButton" - android:text="@string/fxaccount_back_to_browsing" /> - - <LinearLayout style="@style/FxAccountSpacer" /> - - <ImageView - style="@style/FxAccountIcon" - android:contentDescription="@string/fxaccount_empty_contentDescription" /> - </LinearLayout> - -</ScrollView> diff --git a/mobile/android/services/src/main/res/layout/fxaccount_sign_in.xml b/mobile/android/services/src/main/res/layout/fxaccount_sign_in.xml deleted file mode 100644 index 7e7e2ea30025eb4dbe2041e6c7b8ca9ec2590d49..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/layout/fxaccount_sign_in.xml +++ /dev/null @@ -1,79 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. ---> - -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:fillViewport="true" > - - <LinearLayout - android:id="@+id/sign_in_view" - style="@style/FxAccountMiddle" > - - <LinearLayout style="@style/FxAccountSpacer" /> - - <TextView - style="@style/FxAccountHeaderItem" - android:text="@string/fxaccount_sign_in_sub_header" /> - - <include layout="@layout/fxaccount_custom_server_view" /> - - <TextView - android:id="@+id/remote_error" - style="@style/FxAccountErrorItem" /> - - <include layout="@layout/fxaccount_email_password_view" /> - - <RelativeLayout style="@style/FxAccountButtonLayout" - android:layout_marginTop="10dp" > - - <ProgressBar - android:id="@+id/progress" - style="@style/FxAccountProgress" /> - - <Button - android:id="@+id/button" - style="@style/FxAccountProgressButton" - android:text="@string/fxaccount_sign_in_button_label" /> - </RelativeLayout> - - <LinearLayout - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="10dp" - android:orientation="horizontal" > - - <TextView - android:id="@+id/forgot_password_link" - style="@style/FxAccountLinkifiedItem" - android:layout_gravity="left" - android:layout_weight="1" - android:gravity="left" - android:text="@string/fxaccount_sign_in_forgot_password" /> - - <TextView - android:id="@+id/create_account_link" - style="@style/FxAccountLinkItem" - android:layout_gravity="right" - android:layout_weight="1" - android:gravity="right" - android:text="@string/fxaccount_sign_in_create_account_instead" /> - </LinearLayout> - - <TextView - android:id="@+id/policy" - style="@style/FxAccountPolicyItem" - android:text="@string/fxaccount_create_account_policy_text" /> - - <LinearLayout style="@style/FxAccountSpacer" /> - - <ImageView - style="@style/FxAccountIcon" - android:contentDescription="@string/fxaccount_empty_contentDescription" /> - </LinearLayout> - -</ScrollView> diff --git a/mobile/android/services/src/main/res/layout/fxaccount_update_credentials.xml b/mobile/android/services/src/main/res/layout/fxaccount_update_credentials.xml deleted file mode 100644 index 8df72396a282a208d57c8bae7b1fd557b0fa2696..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/layout/fxaccount_update_credentials.xml +++ /dev/null @@ -1,55 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. ---> - -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:fillViewport="true" > - - <LinearLayout - android:id="@+id/update_credentials_view" - style="@style/FxAccountMiddle" > - - <LinearLayout style="@style/FxAccountSpacer" /> - - <TextView - style="@style/FxAccountHeaderItem" - android:text="@string/fxaccount_update_credentials_header" /> - - <include layout="@layout/fxaccount_custom_server_view" /> - - <TextView - android:id="@+id/remote_error" - style="@style/FxAccountErrorItem" /> - - <include layout="@layout/fxaccount_email_password_view" /> - - <RelativeLayout style="@style/FxAccountButtonLayout" > - - <ProgressBar - android:id="@+id/progress" - style="@style/FxAccountProgress" /> - - <Button - android:id="@+id/button" - style="@style/FxAccountProgressButton" - android:text="@string/fxaccount_update_credentials_button_label" /> - </RelativeLayout> - - <TextView - android:id="@+id/forgot_password_link" - style="@style/FxAccountLinkifiedItem" - android:text="@string/fxaccount_sign_in_forgot_password" /> - - <LinearLayout style="@style/FxAccountSpacer" /> - - <ImageView - style="@style/FxAccountIcon" - android:contentDescription="@string/fxaccount_empty_contentDescription" /> - </LinearLayout> - -</ScrollView> diff --git a/mobile/android/services/src/main/res/values-large-v11/fxaccount_styles.xml b/mobile/android/services/src/main/res/values-large-v11/fxaccount_styles.xml deleted file mode 100644 index 85d1e10808a9db77a11ceadc90313f06d76fe383..0000000000000000000000000000000000000000 --- a/mobile/android/services/src/main/res/values-large-v11/fxaccount_styles.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. ---> - -<resources xmlns:android="http://schemas.android.com/apk/res/android"> - - <style name="FxAccountMiddle"> - <item name="android:orientation">vertical</item> - <item name="android:layout_width">500dp</item> - <item name="android:minWidth">500dp</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:layout_weight">1</item> - <!-- layout_gravity controls where the middle container goes - in its parent, and gravity controls where the container - places its internal content. Usually, the middle - container is a LinearLayout and the container is a - ScrollView. In this case, layout_gravity="center" - interacts badly with vertical scrolling. What is needed - is layout_gravity="center_horizontal" and - gravity="center". --> - <item name="android:gravity">center</item> - <item name="android:layout_gravity">center_horizontal</item> - <item name="android:paddingTop">25dp</item> - <item name="android:paddingLeft">12dp</item> - <item name="android:paddingRight">12dp</item> - <item name="android:paddingBottom">15dp</item> - </style> - -</resources> diff --git a/mobile/android/services/src/main/res/values/fxaccount_colors.xml b/mobile/android/services/src/main/res/values/fxaccount_colors.xml index 7529c90880ccee2026fa451cb2cb043047fed879..f7140faff47a99084eb8f75a6dd69a141df1e47e 100644 --- a/mobile/android/services/src/main/res/values/fxaccount_colors.xml +++ b/mobile/android/services/src/main/res/values/fxaccount_colors.xml @@ -5,31 +5,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android"> <color name="fxaccount_textColor">#424f59</color> - <color name="fxaccount_textColorSubdued">#8a9ba8</color> - - <color name="fxaccount_linkified_textColor">#8a9ba8</color> - <color name="fxaccount_linkified_textColorLink">#0095dd</color> - <color name="fxaccount_linkified_textColorLinkSubdued">#8a9ba8</color> - - <color name="fxaccount_error">#d63920</color> - <color name="fxaccount_error_preference_backgroundcolor">#fad4d2</color> - - <color name="fxaccount_button_textColor">#ffffff</color> - <color name="fxaccount_button_background_active">#0095dd</color> - <color name="fxaccount_button_background_hit">#0088cc</color> - <color name="fxaccount_button_background_inactive">#8a9ba8</color> - <color name="fxaccount_input_textColor">#424f59</color> - <color name="fxaccount_input_textColor_pressed">#424f59</color> - <color name="fxaccount_input_textColor_inactive">#8a9ba8</color> - <color name="fxaccount_input_textColorHint">#8a9ba8</color> - <color name="fxaccount_link_textColor">#0095dd</color> - <color name="fxaccount_link_textColor_pressed">#0088cc</color> - <color name="fxaccount_link_textColor_inactive">#8a9ba8</color> - <color name="fxaccount_input_borderActive">#0095dd</color> - <color name="fxaccount_input_borderInactive">#8a9ba8</color> - <color name="fxaccount_password_show_textcolor">#8a9ba8</color> - <color name="fxaccount_password_show_backgroundcolor">#ffffff</color> - <color name="fxaccount_password_hide_textcolor">#ffffff</color> - <color name="fxaccount_password_hide_backgroundcolor">#8a9ba8</color> </resources> diff --git a/mobile/android/services/src/main/res/values/fxaccount_dimens.xml b/mobile/android/services/src/main/res/values/fxaccount_dimens.xml index f355141b4d47125a0f6ef0d9f857b43ecb022618..d1d44585d7283e788f9b74a9f9bed51af1905a40 100644 --- a/mobile/android/services/src/main/res/values/fxaccount_dimens.xml +++ b/mobile/android/services/src/main/res/values/fxaccount_dimens.xml @@ -4,21 +4,6 @@ - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> <resources> - <dimen name="fxaccount_stroke_width">1dp</dimen> - <dimen name="fxaccount_corner_radius">3dp</dimen> - - <!-- The amount of horizontal padding that appears inside the email, - password, etc input fields. --> - <dimen name="fxaccount_input_padding_horizontal">10dp</dimen> - <!-- And the amount of vertical padding that appears inside input - fields. --> - <dimen name="fxaccount_input_padding_vertical">10dp</dimen> - - <!-- The amount of horizontal padding that appears around the show/hide - password button. Vertical padding is provided by the adjacent input - field. --> - <dimen name="fxaccount_show_password_padding_horizontal">8dp</dimen> - <!-- Preference fragment padding, bottom --> <dimen name="preference_fragment_padding_bottom">0dp</dimen> <!-- Preference fragment padding, sides --> diff --git a/mobile/android/services/src/main/res/values/fxaccount_styles.xml b/mobile/android/services/src/main/res/values/fxaccount_styles.xml index 724a9fd7a582e9ec795aae318f84800b4892acea..ab332b7f68eff8127e3c56b48322a052e89404f0 100644 --- a/mobile/android/services/src/main/res/values/fxaccount_styles.xml +++ b/mobile/android/services/src/main/res/values/fxaccount_styles.xml @@ -13,32 +13,6 @@ <item name="android:windowNoTitle">false</item> </style> - <style name="FxAccountMiddle"> - <item name="android:background">@android:color/white</item> - <item name="android:orientation">vertical</item> - <item name="android:layout_width">fill_parent</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:layout_weight">1</item> - <item name="android:paddingTop">10dp</item> - <item name="android:paddingLeft">20dp</item> - <item name="android:paddingRight">20dp</item> - <item name="android:paddingBottom">10dp</item> - </style> - - <style name="FxAccountSpacer"> - <item name="android:orientation">vertical</item> - <item name="android:layout_width">fill_parent</item> - <item name="android:layout_height">0dp</item> - <item name="android:layout_weight">1</item> - </style> - - <style name="FxAccountHeaderItem" parent="@style/FxAccountTextItem"> - <item name="android:textSize">24sp</item> - <item name="android:layout_marginBottom">20dp</item> - <item name="android:layout_marginLeft">0dp</item> - <item name="android:layout_marginRight">0dp</item> - </style> - <style name="FxAccountTextItem" parent="@android:style/TextAppearance.Medium"> <item name="android:textColor">@color/fxaccount_textColor</item> <item name="android:layout_width">fill_parent</item> @@ -50,113 +24,4 @@ <item name="android:layout_marginRight">10dp</item> </style> - <style name="FxAccountLinkItem" parent="@style/FxAccountTextItem"> - <item name="android:clickable">true</item> - <item name="android:focusable">false</item> - <item name="android:textColor">@drawable/fxaccount_linkitem_textcolor</item> - </style> - - <style name="FxAccountButton" parent="@android:style/Widget.Button"> - <item name="android:background">@drawable/fxaccount_button_background</item> - <item name="android:textColor">@drawable/fxaccount_button_color</item> - <item name="android:textSize">20sp</item> - <item name="android:paddingTop">10dp</item> - <item name="android:paddingLeft">20dp</item> - <item name="android:paddingRight">20dp</item> - <item name="android:paddingBottom">10dp</item> - <item name="android:layout_width">fill_parent</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:layout_marginBottom">10dp</item> - </style> - - <!-- Use this in a FxAccountButtonLayout. --> - <style name="FxAccountProgressButton" parent="@style/FxAccountButton"> - <item name="android:layout_marginBottom">0dp</item> - </style> - - <style name="FxAccountEditItem" parent="@android:style/Widget.EditText"> - <item name="android:textSize">18sp</item> - <item name="android:paddingLeft">@dimen/fxaccount_input_padding_horizontal</item> - <item name="android:paddingRight">@dimen/fxaccount_input_padding_horizontal</item> - <item name="android:paddingTop">@dimen/fxaccount_input_padding_vertical</item> - <item name="android:paddingBottom">@dimen/fxaccount_input_padding_vertical</item> - <item name="android:background">@drawable/fxaccount_textfield_background</item> - <item name="android:layout_width">match_parent</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:layout_marginBottom">10dp</item> - <item name="android:singleLine">true</item> - <item name="android:textColor">@color/fxaccount_input_textColor</item> - <item name="android:textColorHint">@color/fxaccount_input_textColorHint</item> - </style> - - <style name="FxAccountLinkifiedItem" parent="@style/FxAccountTextItem"> - <item name="android:clickable">true</item> - <item name="android:focusable">true</item> - <item name="android:textColor">@color/fxaccount_linkified_textColor</item> - <item name="android:textColorLink">@color/fxaccount_linkified_textColorLink</item> - <item name="android:layout_width">fill_parent</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:gravity">center</item> - </style> - - <style name="FxAccountPolicyItem" parent="@style/FxAccountLinkifiedItem"> - <item name="android:layout_marginBottom">20dp</item> - <item name="android:textColorLink">@color/fxaccount_linkified_textColorLinkSubdued</item> - <item name="android:textSize">12sp</item> - </style> - - <style name="FxAccountIcon"> - <item name="android:layout_width">wrap_content</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:layout_gravity">center_horizontal</item> - <item name="android:src">@drawable/icon</item> - </style> - - <style name="FxAccountErrorItem"> - <item name="android:layout_width">fill_parent</item> - <item name="android:layout_marginBottom">10dp</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:gravity">center_horizontal</item> - <item name="android:background">@drawable/fxaccount_textview_error_background</item> - <item name="android:padding">5dp</item> - <item name="android:text">Error</item> - <item name="android:textColor">@android:color/white</item> - <item name="android:textColorLink">@android:color/white</item> - <item name="android:textSize">14sp</item> - <item name="android:visibility">invisible</item> - </style> - - <style name="FxAccountProgress" parent="@android:style/Widget.ProgressBar.Small"> - <item name="android:layout_width">wrap_content</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:layout_centerInParent">true</item> - <item name="android:visibility">invisible</item> - </style> - - <style name="FxAccountButtonLayout"> - <item name="android:orientation">vertical</item> - <item name="android:layout_width">fill_parent</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:background">@drawable/fxaccount_button_background</item> - <item name="android:layout_marginTop">10dp</item> - <item name="android:layout_marginBottom">10dp</item> - </style> - - <style name="FxAccountCheckBox"> - <item name="android:layout_width">fill_parent</item> - <item name="android:layout_height">wrap_content</item> - <item name="android:layout_marginBottom">10dp</item> - <item name="android:textColor">@drawable/fxaccount_checkbox_textcolor</item> - </style> - - <style name="FxAccountShowHidePasswordButton" parent="@android:style/Widget.Button"> - <item name="android:background">@drawable/fxaccount_password_button_show_background</item> - <item name="android:minHeight">0dp</item> - <item name="android:minWidth">0dp</item> - <item name="android:paddingLeft">@dimen/fxaccount_show_password_padding_horizontal</item> - <item name="android:paddingRight">@dimen/fxaccount_show_password_padding_horizontal</item> - <item name="android:textColor">@color/fxaccount_input_textColor</item> - <item name="android:textSize">16sp</item> - </style> - </resources> diff --git a/mobile/android/themes/core/jar.mn b/mobile/android/themes/core/jar.mn index ca7c3f67929123aa0e4416e1fc93b009900b2bea..6ee5a0aecd57734adb1f0610df82c3f5dbccd59b 100644 --- a/mobile/android/themes/core/jar.mn +++ b/mobile/android/themes/core/jar.mn @@ -8,9 +8,7 @@ chrome.jar: % skin browser classic/1.0 %skin/ skin/aboutPage.css (aboutPage.css) skin/about.css (about.css) -#ifndef MOZ_ANDROID_NATIVE_ACCOUNT_UI skin/aboutAccounts.css (aboutAccounts.css) -#endif skin/aboutAddons.css (aboutAddons.css) skin/aboutBase.css (aboutBase.css) #ifdef MOZ_DEVICES diff --git a/services/sync/locales/en-US/sync.properties b/services/sync/locales/en-US/sync.properties index ede7afb5bdcc2330f7316f6d816aeedd1399d937..8c3b9b14c508a5e8b56e1f6c55b77e798d98d690 100644 --- a/services/sync/locales/en-US/sync.properties +++ b/services/sync/locales/en-US/sync.properties @@ -24,3 +24,6 @@ error.sync.eol.label = Service Unavailable error.sync.eol.description = Your Firefox Sync service is no longer available. You need to upgrade %1$S to keep syncing. sync.eol.learnMore.label = Learn more sync.eol.learnMore.accesskey = L + +syncnow.label = Sync Now +syncing.label = Syncing diff --git a/toolkit/.eslintrc b/toolkit/.eslintrc new file mode 100644 index 0000000000000000000000000000000000000000..e7c2eefb76335dc71a98479d40778a3eb065bc67 --- /dev/null +++ b/toolkit/.eslintrc @@ -0,0 +1,196 @@ +{ + // When adding items to this file please check for effects on all of toolkit + // and browser + "rules": { + // Braces only needed for multi-line arrow function blocks + // "arrow-body-style": [2, "as-needed"], + + // Require spacing around => + // "arrow-spacing": 2, + + // Always require spacing around a single line block + // "block-spacing": 1, + + // No newline before open brace for a block + // "brace-style": 2, + + // No space before always a space after a comma + // "comma-spacing": [2, {"before": false, "after": true}], + + // Commas at the end of the line not the start + // "comma-style": 2, + + // Don't require spaces around computed properties + // "computed-property-spacing": [2, "never"], + + // Functions must always return something or nothing + // "consistent-return": 2, + + // Require braces around blocks that start a new line + // Note that this rule is likely to be overridden on a per-directory basis + // very frequently. + // "curly": [2, "multi-line"], + + // Always require a trailing EOL + "eol-last": 2, + + // Require function* name() + // "generator-star-spacing": [2, {"before": false, "after": true}], + + // Two space indent + // "indent": [2, 2, { "SwitchCase": 1 }], + + // Space after colon not before in property declarations + // "key-spacing": [2, { "beforeColon": false, "afterColon": true, "mode": "minimum" }], + + // Unix linebreaks + // "linebreak-style": [2, "unix"], + + // Always require parenthesis for new calls + // "new-parens": 2, + + // Use [] instead of Array() + // "no-array-constructor": 2, + + // No duplicate arguments in function declarations + // "no-dupe-args": 2, + + // No duplicate keys in object declarations + // "no-dupe-keys": 2, + + // No duplicate cases in switch statements + // "no-duplicate-case": 2, + + // No labels + // "no-labels": 2, + + // If an if block ends with a return no need for an else block + // "no-else-return": 2, + + // No empty statements + // "no-empty": 2, + + // No empty character classes in regex + // "no-empty-character-class": 2, + + // Disallow empty destructuring + // "no-empty-pattern": 2, + + // No assiging to exception variable + // "no-ex-assign": 2, + + // No using !! where casting to boolean is already happening + // "no-extra-boolean-cast": 2, + + // No double semicolon + // "no-extra-semi": 2, + + // No overwriting defined functions + // "no-func-assign": 2, + + // No invalid regular expresions + // "no-invalid-regexp": 2, + + // No odd whitespace characters + // "no-irregular-whitespace": 2, + + // No single if block inside an else block + // "no-lonely-if": 2, + + // No mixing spaces and tabs in indent + // "no-mixed-spaces-and-tabs": [2, "smart-tabs"], + + // No unnecessary spacing + // "no-multi-spaces": 2, + + // No reassigning native JS objects + // "no-native-reassign": 2, + + // No (!foo in bar) + // "no-negated-in-lhs": 2, + + // Nested ternary statements are confusing + // "no-nested-ternary": 2, + + // Use {} instead of new Object() + // "no-new-object": 2, + + // No Math() or JSON() + // "no-obj-calls": 2, + + // No octal literals + // "no-octal": 2, + + // No redeclaring variables + // "no-redeclare": 2, + + // No unnecessary comparisons + // "no-self-compare": 2, + + // No declaring variables from an outer scope + // "no-shadow": 2, + + // No declaring variables that hide things like arguments + // "no-shadow-restricted-names": 2, + + // No spaces between function name and parentheses + // "no-spaced-func": 2, + + // No trailing whitespace + // "no-trailing-spaces": 2, + + // No using undeclared variables + // "no-undef": 2, + + // Error on newline where a semicolon is needed + // "no-unexpected-multiline": 2, + + // No unreachable statements + // "no-unreachable": 2, + + // No expressions where a statement is expected + // "no-unused-expressions": 2, + + // No declaring variables that are never used + // "no-unused-vars": [2, {"vars": "all", "args": "none"}], + + // No using variables before defined + // "no-use-before-define": 2, + + // No using with + // "no-with": 2, + + // Always require semicolon at end of statement + // "semi": [2, "always"], + + // Require space after keywords + // "space-after-keywords": 2, + + // Require space before blocks + // "space-before-blocks": 2, + + // Never use spaces before function parentheses + // "space-before-function-paren": [2, { "anonymous": "always", "named": "never" }], + + // Require spaces before finally, catch, etc. + // "space-before-keywords": [2, "always"], + + // No space padding in parentheses + // "space-in-parens": [2, "never"], + + // Require spaces around operators + // "space-infix-ops": 2, + + // Require spaces after return, throw and case + // "space-return-throw-case": 2, + + // ++ and -- should not need spacing + // "space-unary-ops": [2, { "words": true, "nonwords": false }], + + // No comparisons to NaN + // "use-isnan": 2, + + // Only check typeof against valid results + // "valid-typeof": 2, + } +} diff --git a/toolkit/components/alerts/resources/content/alert.js b/toolkit/components/alerts/resources/content/alert.js index 57f65fc4391cf02d5bf1df637f35e556e5fc0342..684ceb392074fa49df6d9d15cd5169ecc9b53076 100644 --- a/toolkit/components/alerts/resources/content/alert.js +++ b/toolkit/components/alerts/resources/content/alert.js @@ -4,15 +4,18 @@ var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; +Cu.import("resource://gre/modules/AppConstants.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + // Copied from nsILookAndFeel.h, see comments on eMetric_AlertNotificationOrigin const NS_ALERT_HORIZONTAL = 1; const NS_ALERT_LEFT = 2; const NS_ALERT_TOP = 4; -const WINDOW_MARGIN = 10; +const WINDOW_MARGIN = AppConstants.platform == "win" ? 0 : 10; const BODY_TEXT_LIMIT = 200; +const WINDOW_SHADOW_SPREAD = AppConstants.platform == "win" ? 10 : 0; -Cu.import("resource://gre/modules/Services.jsm"); var gOrigin = 0; // Default value: alert from bottom right. var gReplacedWindow = null; @@ -217,9 +220,9 @@ function moveWindowToEnd() { let alertWindow = windows.getNext(); if (alertWindow != window) { if (gOrigin & NS_ALERT_TOP) { - y = Math.max(y, alertWindow.screenY + alertWindow.outerHeight); + y = Math.max(y, alertWindow.screenY + alertWindow.outerHeight - WINDOW_SHADOW_SPREAD); } else { - y = Math.min(y, alertWindow.screenY - window.outerHeight); + y = Math.min(y, alertWindow.screenY - window.outerHeight + WINDOW_SHADOW_SPREAD); } } } @@ -234,7 +237,7 @@ function moveWindowToEnd() { function onAlertBeforeUnload() { if (!gIsReplaced) { // Move other alert windows to fill the gap left by closing alert. - let heightDelta = window.outerHeight + WINDOW_MARGIN; + let heightDelta = window.outerHeight + WINDOW_MARGIN - WINDOW_SHADOW_SPREAD; let windows = Services.wm.getEnumerator("alert:alert"); while (windows.hasMoreElements()) { let alertWindow = windows.getNext(); diff --git a/toolkit/components/extensions/Extension.jsm b/toolkit/components/extensions/Extension.jsm index 41677e3643f9b4b5189055ab23c685370d5856e5..4381fc756a6f04e5639c83e6590f40bfd3042124 100644 --- a/toolkit/components/extensions/Extension.jsm +++ b/toolkit/components/extensions/Extension.jsm @@ -213,6 +213,29 @@ ExtensionPage.prototype = { return this.contentWindow; }, + get principal() { + return this.contentWindow.document.nodePrincipal; + }, + + checkLoadURL(url, options = {}) { + let ssm = Services.scriptSecurityManager; + + let flags = ssm.STANDARD; + if (!options.allowScript) { + flags |= ssm.DISALLOW_SCRIPT; + } + if (!options.allowInheritsPrincipal) { + flags |= ssm.DISALLOW_INHERIT_PRINCIPAL; + } + + try { + ssm.checkLoadURIStrWithPrincipal(this.principal, url, flags); + } catch (e) { + return false; + } + return true; + }, + callOnClose(obj) { this.onClose.add(obj); }, @@ -860,10 +883,10 @@ Extension.prototype = extend(Object.create(ExtensionData.prototype), { let whitelist = []; for (let perm of permissions) { - if (perm.match(/:\/\//)) { - whitelist.push(perm); - } else { + if (/^\w+(\.\w+)*$/.test(perm)) { this.permissions.add(perm); + } else { + whitelist.push(perm); } } this.whiteListedHosts = new MatchPattern(whitelist); diff --git a/toolkit/components/extensions/ExtensionContent.jsm b/toolkit/components/extensions/ExtensionContent.jsm index d299b16e2c76563815bbd4c93310eddd22ff462b..34e632235f6aae4ea892d6d68177c8f15991032d 100644 --- a/toolkit/components/extensions/ExtensionContent.jsm +++ b/toolkit/components/extensions/ExtensionContent.jsm @@ -112,6 +112,9 @@ function Script(options) this.matches_ = new MatchPattern(this.options.matches); this.exclude_matches_ = new MatchPattern(this.options.exclude_matches || null); + // TODO: MatchPattern should pre-mangle host-only patterns so that we + // don't need to call a separate match function. + this.matches_host_ = new MatchPattern(this.options.matchesHost || null); // TODO: Support glob patterns. } @@ -119,7 +122,7 @@ function Script(options) Script.prototype = { matches(window) { let uri = window.document.documentURIObject; - if (!this.matches_.matches(uri)) { + if (!(this.matches_.matches(uri) || this.matches_host_.matchesIgnoringPath(uri))) { return false; } @@ -131,6 +134,16 @@ Script.prototype = { return false; } + if ("innerWindowID" in this.options) { + let innerWindowID = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .currentInnerWindowID; + + if (innerWindowID !== this.options.innerWindowID) { + return false; + } + } + // TODO: match_about_blank. return true; @@ -569,7 +582,6 @@ this.ExtensionContent = { receiveMessage({target, name, data}) { switch (name) { case "Extension:Execute": - data.options.matches = "<all_urls>"; let script = new Script(data.options); let {extensionId} = data; DocumentManager.executeScript(target, extensionId, script); diff --git a/toolkit/components/passwordmgr/LoginManagerContent.jsm b/toolkit/components/passwordmgr/LoginManagerContent.jsm index 3b81a9a371707483254671f8189afcd24f2abe0f..75fc97cf6a393d2de30e6a06f0bb213d5eb5f189 100644 --- a/toolkit/components/passwordmgr/LoginManagerContent.jsm +++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm @@ -1149,41 +1149,30 @@ var LoginManagerContent = { }; var LoginUtils = { - /* - * _getPasswordOrigin - * + /** * Get the parts of the URL we want for identification. + * Strip out things like the userPass portion */ - _getPasswordOrigin : function (uriString, allowJS) { + _getPasswordOrigin(uriString, allowJS) { var realm = ""; try { var uri = Services.io.newURI(uriString, null, null); if (allowJS && uri.scheme == "javascript") - return "javascript:" - - realm = uri.scheme + "://" + uri.host; - - // If the URI explicitly specified a port, only include it when - // it's not the default. (We never want "http://foo.com:80") - var port = uri.port; - if (port != -1) { - var handler = Services.io.getProtocolHandler(uri.scheme); - if (port != handler.defaultPort) - realm += ":" + port; - } + return "javascript:"; + realm = uri.scheme + "://" + uri.hostPort; } catch (e) { // bug 159484 - disallow url types that don't support a hostPort. // (although we handle "javascript:..." as a special case above.) - log("Couldn't parse origin for", uriString); + log("Couldn't parse origin for", uriString, e); realm = null; } return realm; }, - _getActionOrigin : function (form) { + _getActionOrigin(form) { var uriString = form.action; // A blank or missing action submits to where it came from. diff --git a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js index 351650c8b8a8b51eedc74df0f1acac5a505c41c6..eb534443e80a8da6a54a7dfb251c85e6949b0bc4 100644 --- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js +++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js @@ -1567,35 +1567,21 @@ LoginManagerPrompter.prototype = { }, - /* - * _getFormattedHostname - * + /** * The aURI parameter may either be a string uri, or an nsIURI instance. * * Returns the hostname to use in a nsILoginInfo object (for example, * "http://example.com"). */ _getFormattedHostname : function (aURI) { - var uri; + let uri; if (aURI instanceof Ci.nsIURI) { uri = aURI; } else { uri = Services.io.newURI(aURI, null, null); } - var scheme = uri.scheme; - - var hostname = scheme + "://" + uri.host; - - // If the URI explicitly specified a port, only include it when - // it's not the default. (We never want "http://foo.com:80") - var port = uri.port; - if (port != -1) { - var handler = Services.io.getProtocolHandler(scheme); - if (port != handler.defaultPort) - hostname += ":" + port; - } - return hostname; + return uri.scheme + "://" + uri.hostPort; }, diff --git a/toolkit/components/passwordmgr/test/unit/test_getPasswordOrigin.js b/toolkit/components/passwordmgr/test/unit/test_getPasswordOrigin.js new file mode 100644 index 0000000000000000000000000000000000000000..f2773ec6246a8c38dff8987de0b0df9fa5c19759 --- /dev/null +++ b/toolkit/components/passwordmgr/test/unit/test_getPasswordOrigin.js @@ -0,0 +1,28 @@ +/* + * Test for LoginUtils._getPasswordOrigin + */ + +"use strict"; + +const LMCBackstagePass = Cu.import("resource://gre/modules/LoginManagerContent.jsm"); +const TESTCASES = [ + ["javascript:void(0);", null], + ["javascript:void(0);", "javascript:", true], + ["chrome://MyAccount", null], + ["data:text/html,example", null], + ["http://username:password@example.com:80/foo?bar=baz#fragment", "http://example.com", true], + ["http://127.0.0.1:80/foo", "http://127.0.0.1"], + ["http://[::1]:80/foo", "http://[::1]"], + ["http://example.com:8080/foo", "http://example.com:8080"], + ["http://127.0.0.1:8080/foo", "http://127.0.0.1:8080", true], + ["http://[::1]:8080/foo", "http://[::1]:8080"], + ["https://example.com:443/foo", "https://example.com"], + ["https://[::1]:443/foo", "https://[::1]"], + ["https://[::1]:8443/foo", "https://[::1]:8443"], + ["ftp://username:password@[::1]:2121/foo", "ftp://[::1]:2121"], +]; + +for (let [input, expected, allowJS] of TESTCASES) { + let actual = LMCBackstagePass.LoginUtils._getPasswordOrigin(input, allowJS); + Assert.strictEqual(actual, expected, "Checking: " + input); +} diff --git a/toolkit/components/passwordmgr/test/unit/xpcshell.ini b/toolkit/components/passwordmgr/test/unit/xpcshell.ini index 365f0df4a471b7cfa3254c64e0a75b6af4880e7c..f4d00ac0f0e6a8d96450392a08a86f021b7a47fa 100644 --- a/toolkit/components/passwordmgr/test/unit/xpcshell.ini +++ b/toolkit/components/passwordmgr/test/unit/xpcshell.ini @@ -22,6 +22,7 @@ run-if = buildapp == "browser" [test_disabled_hosts.js] [test_getFormFields.js] [test_getPasswordFields.js] +[test_getPasswordOrigin.js] [test_legacy_empty_formSubmitURL.js] [test_legacy_validation.js] [test_logins_change.js] diff --git a/toolkit/components/prompts/src/nsPrompter.js b/toolkit/components/prompts/src/nsPrompter.js index 4d4ecaa998c8f9c3aeff48d972cebf078a660e10..8ba1ea0f88ea9af1192fbc5c7c91bb6566e1146f 100644 --- a/toolkit/components/prompts/src/nsPrompter.js +++ b/toolkit/components/prompts/src/nsPrompter.js @@ -204,21 +204,11 @@ var PromptUtilsTemp = { authInfo.password = password; }, - // Copied from login manager + /** + * Strip out things like userPass and path for display. + */ getFormattedHostname : function (uri) { - let scheme = uri.scheme; - let hostname = scheme + "://" + uri.host; - - // If the URI explicitly specified a port, only include it when - // it's not the default. (We never want "http://foo.com:80") - let port = uri.port; - if (port != -1) { - let handler = Services.io.getProtocolHandler(scheme); - if (port != handler.defaultPort) - hostname += ":" + port; - } - - return hostname; + return uri.scheme + "://" + uri.hostPort; }, // Copied from login manager diff --git a/toolkit/components/search/nsSearchService.js b/toolkit/components/search/nsSearchService.js index dab4954fc731b4f0bf1ecc191622447a45b10fe1..0f3cdc40777b608520ab4eaeeac00b50aca3b80e 100644 --- a/toolkit/components/search/nsSearchService.js +++ b/toolkit/components/search/nsSearchService.js @@ -3090,17 +3090,29 @@ SearchService.prototype = { // Start by clearing the initialized state, so we don't abort early. gInitialized = false; - // Clear the engines, too, so we don't stick with the stale ones. - this._engines = {}; - this.__sortedEngines = null; - this._currentEngine = null; - this._defaultEngine = null; - this._visibleDefaultEngines = []; - this._metaData = {}; - this._cacheFileJSON = null; - Task.spawn(function* () { try { + if (this._batchTask) { + LOG("finalizing batch task"); + let task = this._batchTask; + this._batchTask = null; + yield task.finalize(); + } + + // Clear the engines, too, so we don't stick with the stale ones. + this._engines = {}; + this.__sortedEngines = null; + this._currentEngine = null; + this._defaultEngine = null; + this._visibleDefaultEngines = []; + this._metaData = {}; + this._cacheFileJSON = null; + + // Tests that want to force a synchronous re-initialization need to + // be notified when we are done uninitializing. + Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, + "uninit-complete"); + let cache = {}; cache = yield this._asyncReadCacheFile(); if (!gInitialized && cache.metaData) @@ -4102,7 +4114,8 @@ SearchService.prototype = { get currentEngine() { this._ensureInitialized(); - if (!this._currentEngine) { + let currentEngine = this._currentEngine; + if (!currentEngine) { let name = this.getGlobalAttr("current"); let engine = this.getEngineByName(name); if (engine && (this.getGlobalAttr("hash") == getVerificationHash(name) || @@ -4110,23 +4123,32 @@ SearchService.prototype = { // If the current engine is a default one, we can relax the // verification hash check to reduce the annoyance for users who // backup/sync their profile in custom ways. - this._currentEngine = engine; + currentEngine = engine; } } - if (!this._currentEngine || this._currentEngine.hidden) - this._currentEngine = this._originalDefaultEngine; - if (!this._currentEngine || this._currentEngine.hidden) - this._currentEngine = this._getSortedEngines(false)[0]; + if (!currentEngine || currentEngine.hidden) + currentEngine = this._originalDefaultEngine; + if (!currentEngine || currentEngine.hidden) + currentEngine = this._getSortedEngines(false)[0]; - if (!this._currentEngine) { + if (!currentEngine) { // Last resort fallback: unhide the original default engine. - this._currentEngine = this._originalDefaultEngine; - if (this._currentEngine) - this._currentEngine.hidden = false; + currentEngine = this._originalDefaultEngine; + if (currentEngine) + currentEngine.hidden = false; + } + + if (currentEngine) { + // If the current engine wasn't set or was hidden, we used a fallback + // to pick a new current engine. As soon as we return it, this new + // current engine will become user-visible, so we should persist it. + // Calling the setter achieves this, and is a no-op when we haven't + // actually changed the current engine. + this.currentEngine = currentEngine; } - return this._currentEngine; + return currentEngine; }, set currentEngine(val) { diff --git a/toolkit/components/search/tests/xpcshell/test_geodefaults.js b/toolkit/components/search/tests/xpcshell/test_geodefaults.js index 227b2b77aa21b50d0d672f1ccfff10bb13138530..920ef9a3d1a3eabbd8ce5263e0df45789a8fff87 100644 --- a/toolkit/components/search/tests/xpcshell/test_geodefaults.js +++ b/toolkit/components/search/tests/xpcshell/test_geodefaults.js @@ -84,14 +84,13 @@ add_task(function* no_request_if_prefed_off() { add_task(function* should_get_geo_defaults_only_once() { // (Re)initializing the search service should trigger a request, // and set the default engine based on it. - let commitPromise = promiseAfterCache(); // Due to the previous initialization, we expect the countryCode to already be set. do_check_true(Services.prefs.prefHasUserValue("browser.search.countryCode")); do_check_eq(Services.prefs.getCharPref("browser.search.countryCode"), "FR"); yield asyncReInit(); checkRequest(); do_check_eq(Services.search.currentEngine.name, kTestEngineName); - yield commitPromise; + yield promiseAfterCache(); // Verify the metadata was written correctly. let metadata = yield promiseGlobalMetadata(); @@ -110,20 +109,18 @@ add_task(function* should_get_geo_defaults_only_once() { add_task(function* should_request_when_countryCode_not_set() { Services.prefs.clearUserPref("browser.search.countryCode"); - let commitPromise = promiseAfterCache(); yield asyncReInit(); checkRequest(); - yield commitPromise; + yield promiseAfterCache(); }); add_task(function* should_recheck_if_interval_expired() { yield forceExpiration(); - let commitPromise = promiseAfterCache(); let date = Date.now(); yield asyncReInit(); checkRequest(); - yield commitPromise; + yield promiseAfterCache(); // Check that the expiration timestamp has been updated. let metadata = yield promiseGlobalMetadata(); @@ -146,7 +143,9 @@ add_task(function* should_recheck_when_broken_hash() { yield promiseSaveGlobalMetadata(metadata); let commitPromise = promiseAfterCache(); + let unInitPromise = waitForSearchNotification("uninit-complete"); let reInitPromise = asyncReInit(); + yield unInitPromise; // Synchronously check the current default engine, to force a sync init. // The hash is wrong, so we should fallback to the default engine from prefs. @@ -161,6 +160,14 @@ add_task(function* should_recheck_when_broken_hash() { // Check that the hash is back to its previous value. metadata = yield promiseGlobalMetadata(); do_check_eq(typeof metadata.searchDefaultHash, "string"); + if (metadata.searchDefaultHash == "broken") { + // If the server takes more than 1000ms to return the result, + // the commitPromise was resolved by a first save of the cache + // that saved the engines, but not the request's results. + do_print("waiting for the cache to be saved a second time"); + yield promiseAfterCache(); + metadata = yield promiseGlobalMetadata(); + } do_check_eq(metadata.searchDefaultHash, hash); // The current default engine shouldn't change during a session. @@ -170,7 +177,9 @@ add_task(function* should_recheck_when_broken_hash() { // without doing yet another request. yield asyncReInit(); checkNoRequest(); + commitPromise = promiseAfterCache(); do_check_eq(Services.search.currentEngine.name, kTestEngineName); + yield commitPromise; }); add_task(function* should_remember_cohort_id() { diff --git a/toolkit/components/search/tests/xpcshell/test_hidden.js b/toolkit/components/search/tests/xpcshell/test_hidden.js index 98a5813fb51624cfaa83b4f27094c9c3ce994c40..f49344cd8f76e61719ab1cb909ecde491dfab560 100644 --- a/toolkit/components/search/tests/xpcshell/test_hidden.js +++ b/toolkit/components/search/tests/xpcshell/test_hidden.js @@ -67,6 +67,7 @@ add_task(function* sync_init() { do_check_neq(engine, null); yield reInitPromise; + yield promiseAfterCache(); }); add_task(function* invalid_engine() { @@ -79,7 +80,6 @@ add_task(function* invalid_engine() { let url = "data:application/json,{\"interval\": 31536000, \"settings\": {\"searchDefault\": \"hidden\", \"visibleDefaultEngines\": [\"hidden\", \"bogus\"]}}"; Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref(kUrlPref, url); - let commitPromise = promiseAfterCache(); yield asyncReInit(); let engines = Services.search.getEngines(); diff --git a/toolkit/components/search/tests/xpcshell/test_selectedEngine.js b/toolkit/components/search/tests/xpcshell/test_selectedEngine.js index 3cd997292a62a14bd1509f4b735fb97d5f26e872..2601581becb5cfc57acf7c352adc65aad434aa96 100644 --- a/toolkit/components/search/tests/xpcshell/test_selectedEngine.js +++ b/toolkit/components/search/tests/xpcshell/test_selectedEngine.js @@ -93,6 +93,57 @@ add_task(function* test_settingToDefault() { do_check_eq(metadata.current, ""); }); +add_task(function* test_resetToOriginalDefaultEngine() { + let defaultName = getDefaultEngineName(); + do_check_eq(Services.search.currentEngine.name, defaultName); + + Services.search.currentEngine = + Services.search.getEngineByName(kTestEngineName); + do_check_eq(Services.search.currentEngine.name, kTestEngineName); + yield promiseAfterCache(); + + Services.search.resetToOriginalDefaultEngine(); + do_check_eq(Services.search.currentEngine.name, defaultName); + yield promiseAfterCache(); +}); + +add_task(function* test_fallback_kept_after_restart() { + // Set current engine to a default engine that isn't the original default. + let builtInEngines = Services.search.getDefaultEngines(); + let defaultName = getDefaultEngineName(); + let nonDefaultBuiltInEngine; + for (let engine of builtInEngines) { + if (engine.name != defaultName) { + nonDefaultBuiltInEngine = engine; + break; + } + } + Services.search.currentEngine = nonDefaultBuiltInEngine; + do_check_eq(Services.search.currentEngine.name, nonDefaultBuiltInEngine.name); + yield promiseAfterCache(); + + // Remove that engine... + Services.search.removeEngine(nonDefaultBuiltInEngine); + // The engine being a default (built-in) one, it should be hidden + // rather than actually removed. + do_check_true(nonDefaultBuiltInEngine.hidden); + + // Using the currentEngine getter should force a fallback to the + // original default engine. + do_check_eq(Services.search.currentEngine.name, defaultName); + + // Restoring the default engines should unhide our built-in test + // engine, but not change the value of currentEngine. + Services.search.restoreDefaultEngines(); + do_check_false(nonDefaultBuiltInEngine.hidden); + do_check_eq(Services.search.currentEngine.name, defaultName); + yield promiseAfterCache(); + + // After a restart, the currentEngine value should still be unchanged. + yield asyncReInit(); + do_check_eq(Services.search.currentEngine.name, defaultName); +}); + function run_test() { removeMetadata(); diff --git a/toolkit/content/browser-child.js b/toolkit/content/browser-child.js index 327851e489684a372ba3651da9eac2f4456739d5..11aca00401e308bee57ef189e5e000c6d2215f51 100644 --- a/toolkit/content/browser-child.js +++ b/toolkit/content/browser-child.js @@ -55,14 +55,19 @@ var WebProgressListener = { }, _setupJSON: function setupJSON(aWebProgress, aRequest) { + let innerWindowID = null; if (aWebProgress) { - let domWindowID; + let domWindowID = null; try { - domWindowID = aWebProgress && aWebProgress.DOMWindowID; + let utils = aWebProgress.DOMWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + domWindowID = utils.outerWindowID; + innerWindowID = utils.currentInnerWindowID; } catch (e) { // If nsDocShell::Destroy has already been called, then we'll - // get NS_NOINTERFACE when trying to get the DOM window ID. - domWindowID = null; + // get NS_NOINTERFACE when trying to get the DOM window. + // If there is no current inner window, we'll get + // NS_ERROR_NOT_AVAILABLE. } aWebProgress = { @@ -77,7 +82,8 @@ var WebProgressListener = { webProgress: aWebProgress || null, requestURI: this._requestSpec(aRequest, "URI"), originalRequestURI: this._requestSpec(aRequest, "originalURI"), - documentContentType: content.document && content.document.contentType + documentContentType: content.document && content.document.contentType, + innerWindowID, }; }, diff --git a/toolkit/content/widgets/browser.xml b/toolkit/content/widgets/browser.xml index 6d64b39acf16d9c76559b43d38949856da22f166..328be7e146c297d2bfded0f3d1a30a9d8b1d593b 100644 --- a/toolkit/content/widgets/browser.xml +++ b/toolkit/content/widgets/browser.xml @@ -420,6 +420,22 @@ ]]></getter> </property> + <property name="innerWindowID" readonly="true"> + <getter><![CDATA[ + try { + return this.contentWindow + .QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils) + .currentInnerWindowID; + } catch (e) { + if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) { + throw e; + } + return null; + } + ]]></getter> + </property> + <field name="_lastSearchString">null</field> <field name="_lastSearchHighlight">false</field> @@ -1181,6 +1197,7 @@ "_fullZoom", "_textZoom", "_isSyntheticDocument", + "_innerWindowID", ]); } diff --git a/toolkit/content/widgets/remote-browser.xml b/toolkit/content/widgets/remote-browser.xml index 70f913e103e745162c53ddfd1af1e4accc6668ec..b1763640ed73b6892dbc2ee5980eea905c872aab 100644 --- a/toolkit/content/widgets/remote-browser.xml +++ b/toolkit/content/widgets/remote-browser.xml @@ -212,6 +212,13 @@ onget="return this._outerWindowID" readonly="true"/> + <field name="_innerWindowID">null</field> + <property name="innerWindowID"> + <getter><![CDATA[ + return this._innerWindowID; + ]]></getter> + </property> + <property name="autoCompletePopup" onget="return document.getElementById(this.getAttribute('autocompletepopup'))" readonly="true"/> diff --git a/toolkit/modules/AppConstants.jsm b/toolkit/modules/AppConstants.jsm index 25d59369dd38555a9a5914f25f10b2e4e4585cd9..938ae23944950fd04f3ceac4fb82e9bd2977303a 100644 --- a/toolkit/modules/AppConstants.jsm +++ b/toolkit/modules/AppConstants.jsm @@ -73,13 +73,6 @@ this.AppConstants = Object.freeze({ false, #endif - MOZ_ANDROID_NATIVE_ACCOUNT_UI: -#ifdef MOZ_ANDROID_NATIVE_ACCOUNT_UI - true, -#else - false, -#endif - MOZ_SAFE_BROWSING: #ifdef MOZ_SAFE_BROWSING true, diff --git a/toolkit/modules/RemoteWebProgress.jsm b/toolkit/modules/RemoteWebProgress.jsm index 8a5b8193df9377566205dd68d071a99689f59568..4cae1d76be3524b205ae6bd9304e9ab0b1a99f13 100644 --- a/toolkit/modules/RemoteWebProgress.jsm +++ b/toolkit/modules/RemoteWebProgress.jsm @@ -235,6 +235,7 @@ RemoteWebProgressManager.prototype = { this._browser._mayEnableCharacterEncodingMenu = json.mayEnableCharacterEncodingMenu; this._browser._contentPrincipal = json.principal; this._browser._isSyntheticDocument = json.synthetic; + this._browser._innerWindowID = json.innerWindowID; } this._callProgressListeners("onLocationChange", webProgress, request, location, flags); diff --git a/toolkit/themes/windows/global/alerts/alert.css b/toolkit/themes/windows/global/alerts/alert.css index 7473174ea0d0f26e8d2c75022a723bd70352459a..e0667adf84ed7e53444f17512fbbc5731faac8c9 100644 --- a/toolkit/themes/windows/global/alerts/alert.css +++ b/toolkit/themes/windows/global/alerts/alert.css @@ -13,6 +13,7 @@ #alertNotification { -moz-appearance: none; background: transparent; + padding: 10px; } #alertBox { @@ -20,6 +21,7 @@ border-radius: 1px; background-color: -moz-Dialog; color: -moz-DialogText; + box-shadow: 0 2px 10px rgba(0,0,0,0.59); } .alertCloseButton {