Merge m-c to inbound, a=merge
|
@ -29,7 +29,7 @@
|
|||
#version {
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
margin-inline-start: 0;
|
||||
margin-left: 0;
|
||||
-moz-user-select: text;
|
||||
-moz-user-focus: normal;
|
||||
cursor: text;
|
||||
|
@ -38,6 +38,7 @@
|
|||
#version:-moz-locale-dir(rtl) {
|
||||
direction: ltr;
|
||||
text-align: right;
|
||||
margin-left: 5px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ var StarUI = {
|
|||
element.hidden = false;
|
||||
element.addEventListener("keypress", this, false);
|
||||
element.addEventListener("mouseout", this, false);
|
||||
element.addEventListener("mouseover", this, false);
|
||||
element.addEventListener("mousemove", this, false);
|
||||
element.addEventListener("popuphidden", this, false);
|
||||
element.addEventListener("popupshown", this, false);
|
||||
return this.panel = element;
|
||||
|
@ -63,7 +63,7 @@ var StarUI = {
|
|||
// nsIDOMEventListener
|
||||
handleEvent(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "mouseover":
|
||||
case "mousemove":
|
||||
clearTimeout(this._autoCloseTimer);
|
||||
break;
|
||||
case "popuphidden":
|
||||
|
@ -130,14 +130,13 @@ var StarUI = {
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case "mouseout": {
|
||||
case "mouseout":
|
||||
// Explicit fall-through
|
||||
case "popupshown":
|
||||
// Don't handle events for descendent elements.
|
||||
if (aEvent.target != aEvent.currentTarget) {
|
||||
break;
|
||||
}
|
||||
// Explicit fall-through
|
||||
}
|
||||
case "popupshown":
|
||||
// auto-close if new and not interacted with
|
||||
if (this._isNewBookmark) {
|
||||
// 3500ms matches the timeout that Pocket uses in
|
||||
|
@ -146,7 +145,9 @@ var StarUI = {
|
|||
if (this._closePanelQuickForTesting) {
|
||||
delay /= 10;
|
||||
}
|
||||
this._autoCloseTimer = setTimeout(() => this.panel.hidePopup(), delay, this);
|
||||
this._autoCloseTimer = setTimeout(() => {
|
||||
this.panel.hidePopup();
|
||||
}, delay);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -309,10 +309,11 @@ var AboutNetAndCertErrorListener = {
|
|||
let div = content.document.getElementById("certificateErrorText");
|
||||
div.textContent = msg.data.info;
|
||||
let learnMoreLink = content.document.getElementById("learnMoreLink");
|
||||
let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
|
||||
|
||||
switch (msg.data.code) {
|
||||
case SEC_ERROR_UNKNOWN_ISSUER:
|
||||
learnMoreLink.href = "https://support.mozilla.org/kb/troubleshoot-SEC_ERROR_UNKNOWN_ISSUER";
|
||||
learnMoreLink.href = baseURL + "security-error";
|
||||
break;
|
||||
|
||||
// in case the certificate expired we make sure the system clock
|
||||
|
@ -348,8 +349,7 @@ var AboutNetAndCertErrorListener = {
|
|||
.style.display = "block";
|
||||
}
|
||||
}
|
||||
let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "time-errors";
|
||||
learnMoreLink.setAttribute("href", url);
|
||||
learnMoreLink.href = baseURL + "time-errors";
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -356,7 +356,7 @@ add_task(function* checkUnknownIssuerLearnMoreLink() {
|
|||
let learnMoreLink = content.document.getElementById("learnMoreLink");
|
||||
return learnMoreLink.href;
|
||||
});
|
||||
is(href, "https://support.mozilla.org/kb/troubleshoot-SEC_ERROR_UNKNOWN_ISSUER");
|
||||
ok(href.endsWith("security-error"), "security-error in the Learn More URL");
|
||||
|
||||
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
});
|
||||
|
|
|
@ -13,7 +13,6 @@ let bookmarkStar = document.getElementById("bookmarks-menu-button");
|
|||
let bookmarkPanelTitle = document.getElementById("editBookmarkPanelTitle");
|
||||
|
||||
StarUI._closePanelQuickForTesting = true;
|
||||
Services.prefs.setBoolPref("browser.bookmarks.closePanelQuickForTesting", true);
|
||||
|
||||
function* test_bookmarks_popup({isNewBookmark, popupShowFn, popupEditFn,
|
||||
shouldAutoClose, popupHideFn, isBookmarkRemoved}) {
|
||||
|
@ -122,39 +121,26 @@ add_task(function* panel_shown_for_keyboardshortcut_on_new_bookmark_star_and_aut
|
|||
});
|
||||
});
|
||||
|
||||
add_task(function* panel_shown_for_new_bookmarks_mouseover_mouseout() {
|
||||
add_task(function* panel_shown_for_new_bookmarks_mousemove_mouseout() {
|
||||
yield test_bookmarks_popup({
|
||||
isNewBookmark: true,
|
||||
popupShowFn() {
|
||||
bookmarkStar.click();
|
||||
},
|
||||
*popupEditFn() {
|
||||
let mouseOverPromise = new Promise(resolve => {
|
||||
bookmarkPanel.addEventListener("mouseover", function onmouseover() {
|
||||
bookmarkPanel.removeEventListener("mouseover", onmouseover);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
yield new Promise(resolve => {
|
||||
EventUtils.synthesizeNativeMouseMove(bookmarkPanel, 0, 0, resolve, window);
|
||||
});
|
||||
info("Waiting for mouseover event");
|
||||
yield mouseOverPromise;
|
||||
info("Got mouseover event");
|
||||
let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove");
|
||||
EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"});
|
||||
info("Waiting for mousemove event");
|
||||
yield mouseMovePromise;
|
||||
info("Got mousemove event");
|
||||
|
||||
yield new Promise(resolve => setTimeout(resolve, 400));
|
||||
is(bookmarkPanel.state, "open", "Panel should still be open on mouseover");
|
||||
is(bookmarkPanel.state, "open", "Panel should still be open on mousemove");
|
||||
},
|
||||
*popupHideFn() {
|
||||
let mouseOutPromise = new Promise(resolve => {
|
||||
bookmarkPanel.addEventListener("mouseout", function onmouseout() {
|
||||
bookmarkPanel.removeEventListener("mouseout", onmouseout);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
yield new Promise(resolve => {
|
||||
EventUtils.synthesizeNativeMouseMove(bookmarkStar, 0, 0, resolve, window);
|
||||
});
|
||||
let mouseOutPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mouseout");
|
||||
EventUtils.synthesizeMouse(bookmarkPanel, 0, 0, {type: "mouseout"});
|
||||
EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
|
||||
info("Waiting for mouseout event");
|
||||
yield mouseOutPromise;
|
||||
info("Got mouseout event, should autoclose now");
|
||||
|
@ -267,6 +253,5 @@ add_task(function* ctrl_d_edit_bookmark_remove_bookmark() {
|
|||
});
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.clearUserPref("browser.bookmarks.closePanelQuickForTesting");
|
||||
delete StarUI._closePanelQuickForTesting;
|
||||
})
|
||||
|
|
|
@ -731,7 +731,7 @@ function assertMixedContentBlockingState(tabbrowser, states = {}) {
|
|||
"Using active loaded icon");
|
||||
}
|
||||
if (activeBlocked && !passiveLoaded) {
|
||||
is(connectionIconImage, "url(\"chrome://browser/skin/identity-mixed-active-blocked.svg\")",
|
||||
is(connectionIconImage, "url(\"chrome://browser/skin/identity-secure.svg\")",
|
||||
"Using active blocked icon");
|
||||
}
|
||||
if (passiveLoaded && !(activeLoaded || activeBlocked)) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<Application xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
|
||||
<VisualElements
|
||||
DisplayName='Firefox Developer Edition'
|
||||
Logo='VisualElements\VisualElements_150.png'
|
||||
SmallLogo='VisualElements\VisualElements_70.png'
|
||||
Logo='browser\VisualElements\VisualElements_150.png'
|
||||
SmallLogo='browser\VisualElements\VisualElements_70.png'
|
||||
ForegroundText='light'
|
||||
BackgroundColor='#14171a'/>
|
||||
<DefaultTile ShowName='allLogos'/>
|
||||
|
|
Двоичные данные
browser/branding/aurora/VisualElements_150.png
До Ширина: | Высота: | Размер: 39 KiB После Ширина: | Высота: | Размер: 21 KiB |
Двоичные данные
browser/branding/aurora/VisualElements_70.png
До Ширина: | Высота: | Размер: 11 KiB После Ширина: | Высота: | Размер: 6.5 KiB |
|
@ -1,8 +1,8 @@
|
|||
<Application xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
|
||||
<VisualElements
|
||||
ShowNameOnSquare150x150Logo='on'
|
||||
Square150x150Logo='VisualElements\VisualElements_150.png'
|
||||
Square70x70Logo='VisualElements\VisualElements_70.png'
|
||||
Square150x150Logo='browser\VisualElements\VisualElements_150.png'
|
||||
Square70x70Logo='browser\VisualElements\VisualElements_70.png'
|
||||
ForegroundText='light'
|
||||
BackgroundColor='#14171a'/>
|
||||
</Application>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<Application xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
|
||||
<VisualElements
|
||||
DisplayName='Nightly'
|
||||
Logo='VisualElements\VisualElements_150.png'
|
||||
SmallLogo='VisualElements\VisualElements_70.png'
|
||||
Logo='browser\VisualElements\VisualElements_150.png'
|
||||
SmallLogo='browser\VisualElements\VisualElements_70.png'
|
||||
ForegroundText='light'
|
||||
BackgroundColor='#14171a'/>
|
||||
<DefaultTile ShowName='allLogos'/>
|
||||
|
|
Двоичные данные
browser/branding/nightly/VisualElements_150.png
До Ширина: | Высота: | Размер: 33 KiB После Ширина: | Высота: | Размер: 18 KiB |
Двоичные данные
browser/branding/nightly/VisualElements_70.png
До Ширина: | Высота: | Размер: 9.7 KiB После Ширина: | Высота: | Размер: 5.7 KiB |
|
@ -1,8 +1,8 @@
|
|||
<Application xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
|
||||
<VisualElements
|
||||
ShowNameOnSquare150x150Logo='on'
|
||||
Square150x150Logo='VisualElements\VisualElements_150.png'
|
||||
Square70x70Logo='VisualElements\VisualElements_70.png'
|
||||
Square150x150Logo='browser\VisualElements\VisualElements_150.png'
|
||||
Square70x70Logo='browser\VisualElements\VisualElements_70.png'
|
||||
ForegroundText='light'
|
||||
BackgroundColor='#14171a'/>
|
||||
</Application>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<Application xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
|
||||
<VisualElements
|
||||
DisplayName='Mozilla Firefox'
|
||||
Logo='VisualElements\VisualElements_150.png'
|
||||
SmallLogo='VisualElements\VisualElements_70.png'
|
||||
Logo='browser\VisualElements\VisualElements_150.png'
|
||||
SmallLogo='browser\VisualElements\VisualElements_70.png'
|
||||
ForegroundText='light'
|
||||
BackgroundColor='#0996f8'/>
|
||||
<DefaultTile ShowName='allLogos'/>
|
||||
|
|
Двоичные данные
browser/branding/official/VisualElements_150.png
До Ширина: | Высота: | Размер: 31 KiB После Ширина: | Высота: | Размер: 19 KiB |
Двоичные данные
browser/branding/official/VisualElements_70.png
До Ширина: | Высота: | Размер: 9.8 KiB После Ширина: | Высота: | Размер: 6.1 KiB |
|
@ -1,8 +1,8 @@
|
|||
<Application xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
|
||||
<VisualElements
|
||||
ShowNameOnSquare150x150Logo='on'
|
||||
Square150x150Logo='VisualElements\VisualElements_150.png'
|
||||
Square70x70Logo='VisualElements\VisualElements_70.png'
|
||||
Square150x150Logo='browser\VisualElements\VisualElements_150.png'
|
||||
Square70x70Logo='browser\VisualElements\VisualElements_70.png'
|
||||
ForegroundText='light'
|
||||
BackgroundColor='#0996f8'/>
|
||||
</Application>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<Application>
|
||||
<VisualElements
|
||||
DisplayName='Nightly'
|
||||
Logo='VisualElements\VisualElements_150.png'
|
||||
SmallLogo='VisualElements\VisualElements_70.png'
|
||||
Logo='browser\VisualElements\VisualElements_150.png'
|
||||
SmallLogo='browser\VisualElements\VisualElements_70.png'
|
||||
ForegroundText='light'
|
||||
BackgroundColor='#14171a'>
|
||||
<DefaultTile ShowName='allLogos'/>
|
||||
|
|
Двоичные данные
browser/branding/unofficial/VisualElements_150.png
До Ширина: | Высота: | Размер: 33 KiB После Ширина: | Высота: | Размер: 18 KiB |
Двоичные данные
browser/branding/unofficial/VisualElements_70.png
До Ширина: | Высота: | Размер: 9.7 KiB После Ширина: | Высота: | Размер: 5.7 KiB |
|
@ -1,8 +1,8 @@
|
|||
<Application xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
|
||||
<VisualElements
|
||||
ShowNameOnSquare150x150Logo='on'
|
||||
Square150x150Logo='VisualElements\VisualElements_150.png'
|
||||
Square70x70Logo='VisualElements\VisualElements_70.png'
|
||||
Square150x150Logo='browser\VisualElements\VisualElements_150.png'
|
||||
Square70x70Logo='browser\VisualElements\VisualElements_70.png'
|
||||
ForegroundText='light'
|
||||
BackgroundColor='#14171a'/>
|
||||
</Application>
|
||||
|
|
|
@ -9,6 +9,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
|
||||
var {
|
||||
EventManager,
|
||||
IconDetails,
|
||||
runSafe,
|
||||
} = ExtensionUtils;
|
||||
|
||||
|
@ -53,8 +54,8 @@ var gMenuBuilder = {
|
|||
let parentWindow = contextData.menu.ownerDocument.defaultView;
|
||||
let extension = root.extension;
|
||||
|
||||
let url = IconDetails.getURL(extension.manifest.icons, parentWindow, extension, 16 /* size */);
|
||||
let resolvedURL = root.extension.baseURI.resolve(url);
|
||||
let {icon} = IconDetails.getURL(extension.manifest.icons, parentWindow, extension, 16 /* size */);
|
||||
let resolvedURL = root.extension.baseURI.resolve(icon);
|
||||
|
||||
if (rootElement.localName == "menu") {
|
||||
rootElement.setAttribute("class", "menu-iconic");
|
||||
|
|
|
@ -135,7 +135,8 @@
|
|||
}
|
||||
|
||||
#urlbar[pageproxystate="valid"] > #identity-box.verifiedDomain > #connection-icon,
|
||||
#urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity > #connection-icon {
|
||||
#urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity > #connection-icon,
|
||||
#urlbar[pageproxystate="valid"] > #identity-box.mixedActiveBlocked > #connection-icon {
|
||||
list-style-image: url(chrome://browser/skin/identity-secure.svg);
|
||||
visibility: visible;
|
||||
}
|
||||
|
@ -153,11 +154,6 @@
|
|||
visibility: visible;
|
||||
}
|
||||
|
||||
#urlbar[pageproxystate="valid"] > #identity-box.mixedActiveBlocked > #connection-icon {
|
||||
list-style-image: url(chrome://browser/skin/identity-mixed-active-blocked.svg);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#urlbar[pageproxystate="valid"] > #identity-box.certUserOverridden > #connection-icon {
|
||||
list-style-image: url(chrome://browser/skin/identity-mixed-passive-loaded.svg);
|
||||
visibility: visible;
|
||||
|
|
|
@ -1,31 +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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="16" height="16" viewBox="0 0 16 16">
|
||||
<style>
|
||||
.icon-default {
|
||||
fill: #4d9a26;
|
||||
}
|
||||
</style>
|
||||
|
||||
<defs>
|
||||
<rect id="shape-lock-clasp-outer" x="2" y="1" width="8" height="10" rx="4" ry="4" />
|
||||
<rect id="shape-lock-clasp-inner" x="4" y="3" width="4" height="6" rx="2" ry="2" />
|
||||
<rect id="shape-lock-base" x="1" y="6" width="10" height="7" rx="1" ry="1" />
|
||||
|
||||
<mask id="mask-clasp-cutout">
|
||||
<rect width="16" height="16" fill="#000" />
|
||||
<use xlink:href="#shape-lock-clasp-outer" fill="#fff" />
|
||||
<use xlink:href="#shape-lock-clasp-inner" fill="#000" />
|
||||
</mask>
|
||||
</defs>
|
||||
|
||||
<use xlink:href="#shape-lock-clasp-outer" mask="url(#mask-clasp-cutout)" class="icon-default" />
|
||||
<use xlink:href="#shape-lock-base" class="icon-default" />
|
||||
<path fill="#fff" d="M10.5,5C9.8,5,9.1,5.4,8.8,6.2l-3.5,6.8c-0.4,0.7-0.4,1.4,0,2c0.4,0.6,1,1,1.8,1H14c0.8,0,1.4-0.4,1.8-1 c0.3-0.6,0.3-1.4,0-2l-3.5-6.8C11.9,5.4,11.2,5,10.5,5L10.5,5z"/>
|
||||
<path fill="#999" d="M14.8,13.4l-3.5-6.8C11.2,6.2,10.9,6,10.5,6c-0.3,0-0.7,0.2-0.9,0.6l-3.5,6.8c-0.2,0.4-0.2,0.8,0,1.1C6.3,14.8,6.6,15,7,15 H14c0.4,0,0.7-0.2,0.9-0.5C15.1,14.2,15,13.8,14.8,13.4z"/>
|
||||
<path fill="#fff" d="M10,8.5C10,8.2,10.2,8,10.5,8S11,8.2,11,8.5L10.8,11h-0.6L10,8.5z" />
|
||||
<circle fill="#fff" cx="10.5" cy="12.5" r=".75" />
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 1.6 KiB |
|
@ -64,7 +64,6 @@
|
|||
skin/classic/browser/identity-icon.svg (../shared/identity-block/identity-icon.svg)
|
||||
skin/classic/browser/identity-not-secure.svg (../shared/identity-block/identity-not-secure.svg)
|
||||
skin/classic/browser/identity-secure.svg (../shared/identity-block/identity-secure.svg)
|
||||
skin/classic/browser/identity-mixed-active-blocked.svg (../shared/identity-block/identity-mixed-active-blocked.svg)
|
||||
skin/classic/browser/identity-mixed-passive-loaded.svg (../shared/identity-block/identity-mixed-passive-loaded.svg)
|
||||
skin/classic/browser/identity-mixed-active-loaded.svg (../shared/identity-block/identity-mixed-active-loaded.svg)
|
||||
skin/classic/browser/info.svg (../shared/info.svg)
|
||||
|
|
|
@ -193,14 +193,14 @@ const getRelativeRect = function (node, relativeTo) {
|
|||
* Defaults to true. The tooltip is closed when clicking outside.
|
||||
* Should this event be stopped and consumed or not.
|
||||
* - {Boolean} useXulWrapper
|
||||
* Defaults to true. If the tooltip is hosted in a XUL document, use a XUL panel
|
||||
* Defaults to false. If the tooltip is hosted in a XUL document, use a XUL panel
|
||||
* in order to use all the screen viewport available.
|
||||
*/
|
||||
function HTMLTooltip(toolbox, {
|
||||
type = "normal",
|
||||
autofocus = false,
|
||||
consumeOutsideClicks = true,
|
||||
useXulWrapper = true,
|
||||
useXulWrapper = false,
|
||||
} = {}) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
|
|
|
@ -177,7 +177,6 @@
|
|||
height: 16px;
|
||||
width: 32px;
|
||||
overflow: hidden;
|
||||
pointer-events: all;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
@ -209,6 +208,7 @@
|
|||
border-style: solid;
|
||||
border-width: 0px 3px 3px 0px;
|
||||
border-radius: 3px;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.tooltip-bottom .tooltip-arrow:before {
|
||||
|
|
|
@ -834,6 +834,8 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
|
|||
'fxa/authenticator/FxADefaultLoginStateMachineDelegate.java',
|
||||
'fxa/FirefoxAccounts.java',
|
||||
'fxa/FxAccountConstants.java',
|
||||
'fxa/FxAccountDevice.java',
|
||||
'fxa/FxAccountDeviceRegistrator.java',
|
||||
'fxa/login/BaseRequestDelegate.java',
|
||||
'fxa/login/Cohabiting.java',
|
||||
'fxa/login/Doghouse.java',
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.json.JSONObject;
|
|||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.FxAccountDeviceRegistrator;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Engaged;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
|
|
|
@ -4,13 +4,18 @@
|
|||
|
||||
package org.mozilla.gecko.background.fxa;
|
||||
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.StatusResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.RecoveryEmailStatusResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.TwoKeys;
|
||||
import org.mozilla.gecko.fxa.FxAccountDevice;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
|
||||
public interface FxAccountClient {
|
||||
public void status(byte[] sessionToken, RequestDelegate<StatusResponse> requestDelegate);
|
||||
public void accountStatus(String uid, RequestDelegate<AccountStatusResponse> requestDelegate);
|
||||
public void recoveryEmailStatus(byte[] sessionToken, RequestDelegate<RecoveryEmailStatusResponse> requestDelegate);
|
||||
public void keys(byte[] keyFetchToken, RequestDelegate<TwoKeys> requestDelegate);
|
||||
public void sign(byte[] sessionToken, ExtendedJSONObject publicKey, long certificateDurationInMilliseconds, RequestDelegate<String> requestDelegate);
|
||||
public void registerOrUpdateDevice(byte[] sessionToken, FxAccountDevice device, RequestDelegate<FxAccountDevice> requestDelegate);
|
||||
public void deviceList(byte[] sessionToken, RequestDelegate<FxAccountDevice[]> requestDelegate);
|
||||
}
|
||||
|
|
|
@ -4,11 +4,14 @@
|
|||
|
||||
package org.mozilla.gecko.background.fxa;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientMalformedResponseException;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.Locales;
|
||||
import org.mozilla.gecko.fxa.FxAccountDevice;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.crypto.HKDF;
|
||||
|
@ -67,6 +70,7 @@ public class FxAccountClient20 implements FxAccountClient {
|
|||
public static final String JSON_KEY_INFO = "info";
|
||||
public static final String JSON_KEY_CODE = "code";
|
||||
public static final String JSON_KEY_ERRNO = "errno";
|
||||
public static final String JSON_KEY_EXISTS = "exists";
|
||||
|
||||
protected static final String[] requiredErrorStringFields = { JSON_KEY_ERROR, JSON_KEY_MESSAGE, JSON_KEY_INFO };
|
||||
protected static final String[] requiredErrorLongFields = { JSON_KEY_CODE, JSON_KEY_ERRNO };
|
||||
|
@ -168,6 +172,11 @@ public class FxAccountClient20 implements FxAccountClient {
|
|||
});
|
||||
}
|
||||
|
||||
enum ResponseType {
|
||||
JSON_ARRAY,
|
||||
JSON_OBJECT
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate resource callbacks into request callbacks invoked on the provided
|
||||
* executor.
|
||||
|
@ -177,19 +186,27 @@ public class FxAccountClient20 implements FxAccountClient {
|
|||
* invoked via the executor, so you don't need to delegate further.
|
||||
*/
|
||||
protected abstract class ResourceDelegate<T> extends BaseResourceDelegate {
|
||||
protected abstract void handleSuccess(final int status, HttpResponse response, final ExtendedJSONObject body);
|
||||
|
||||
protected void handleSuccess(final int status, HttpResponse response, final ExtendedJSONObject body) throws Exception {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
protected void handleSuccess(final int status, HttpResponse response, final JSONArray body) throws Exception {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
protected final RequestDelegate<T> delegate;
|
||||
|
||||
protected final byte[] tokenId;
|
||||
protected final byte[] reqHMACKey;
|
||||
protected final SkewHandler skewHandler;
|
||||
protected final ResponseType responseType;
|
||||
|
||||
/**
|
||||
* Create a delegate for an un-authenticated resource.
|
||||
*/
|
||||
public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate) {
|
||||
this(resource, delegate, null, null);
|
||||
public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate, ResponseType responseType) {
|
||||
this(resource, delegate, responseType, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -198,12 +215,13 @@ public class FxAccountClient20 implements FxAccountClient {
|
|||
* Every Hawk request that encloses an entity (PATCH, POST, and PUT) will
|
||||
* include the payload verification hash.
|
||||
*/
|
||||
public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate, final byte[] tokenId, final byte[] reqHMACKey) {
|
||||
public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate, ResponseType responseType, final byte[] tokenId, final byte[] reqHMACKey) {
|
||||
super(resource);
|
||||
this.delegate = delegate;
|
||||
this.reqHMACKey = reqHMACKey;
|
||||
this.tokenId = tokenId;
|
||||
this.skewHandler = SkewHandler.getSkewHandlerForResource(resource);
|
||||
this.responseType = responseType;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -250,8 +268,14 @@ public class FxAccountClient20 implements FxAccountClient {
|
|||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ExtendedJSONObject body = new SyncResponse(response).jsonObjectBody();
|
||||
ResourceDelegate.this.handleSuccess(status, response, body);
|
||||
SyncResponse syncResponse = new SyncResponse(response);
|
||||
if (responseType == ResponseType.JSON_ARRAY) {
|
||||
JSONArray body = syncResponse.jsonArrayBody();
|
||||
ResourceDelegate.this.handleSuccess(status, response, body);
|
||||
} else {
|
||||
ExtendedJSONObject body = syncResponse.jsonObjectBody();
|
||||
ResourceDelegate.this.handleSuccess(status, response, body);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
}
|
||||
|
@ -285,7 +309,7 @@ public class FxAccountClient20 implements FxAccountClient {
|
|||
}
|
||||
}
|
||||
|
||||
protected <T> void post(BaseResource resource, final ExtendedJSONObject requestBody, final RequestDelegate<T> delegate) {
|
||||
protected <T> void post(BaseResource resource, final ExtendedJSONObject requestBody) {
|
||||
if (requestBody == null) {
|
||||
resource.post((HttpEntity) null);
|
||||
} else {
|
||||
|
@ -413,38 +437,69 @@ public class FxAccountClient20 implements FxAccountClient {
|
|||
return;
|
||||
}
|
||||
|
||||
resource.delegate = new ResourceDelegate<TwoKeys>(resource, delegate, tokenId, reqHMACKey) {
|
||||
resource.delegate = new ResourceDelegate<TwoKeys>(resource, delegate, ResponseType.JSON_OBJECT, tokenId, reqHMACKey) {
|
||||
@Override
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
|
||||
try {
|
||||
byte[] kA = new byte[FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES];
|
||||
byte[] wrapkB = new byte[FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES];
|
||||
unbundleBody(body, requestKey, FxAccountUtils.KW("account/keys"), kA, wrapkB);
|
||||
delegate.handleSuccess(new TwoKeys(kA, wrapkB));
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
return;
|
||||
}
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) throws Exception {
|
||||
byte[] kA = new byte[FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES];
|
||||
byte[] wrapkB = new byte[FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES];
|
||||
unbundleBody(body, requestKey, FxAccountUtils.KW("account/keys"), kA, wrapkB);
|
||||
delegate.handleSuccess(new TwoKeys(kA, wrapkB));
|
||||
}
|
||||
};
|
||||
resource.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Thin container for status response.
|
||||
* Thin container for account status response.
|
||||
*/
|
||||
public static class StatusResponse {
|
||||
public static class AccountStatusResponse {
|
||||
public final boolean exists;
|
||||
public AccountStatusResponse(boolean exists) {
|
||||
this.exists = exists;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the account status of an account given a uid.
|
||||
*
|
||||
* @param uid to query.
|
||||
* @param delegate to invoke callbacks.
|
||||
*/
|
||||
public void accountStatus(String uid, final RequestDelegate<AccountStatusResponse> delegate) {
|
||||
final BaseResource resource;
|
||||
try {
|
||||
final Map<String, String> params = new HashMap<>(1);
|
||||
params.put("uid", uid);
|
||||
resource = getBaseResource("account/status", params);
|
||||
} catch (URISyntaxException | UnsupportedEncodingException e) {
|
||||
invokeHandleError(delegate, e);
|
||||
return;
|
||||
}
|
||||
|
||||
resource.delegate = new ResourceDelegate<AccountStatusResponse>(resource, delegate, ResponseType.JSON_OBJECT) {
|
||||
@Override
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) throws Exception {
|
||||
boolean exists = body.getBoolean(JSON_KEY_EXISTS);
|
||||
delegate.handleSuccess(new AccountStatusResponse(exists));
|
||||
}
|
||||
};
|
||||
resource.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Thin container for recovery email status response.
|
||||
*/
|
||||
public static class RecoveryEmailStatusResponse {
|
||||
public final String email;
|
||||
public final boolean verified;
|
||||
public StatusResponse(String email, boolean verified) {
|
||||
public RecoveryEmailStatusResponse(String email, boolean verified) {
|
||||
this.email = email;
|
||||
this.verified = verified;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the status of an account given a valid session token.
|
||||
* Query the recovery email status of an account given a valid session token.
|
||||
* <p>
|
||||
* This API is a little odd: the auth server returns the email and
|
||||
* verification state of the account that corresponds to the (opaque) session
|
||||
|
@ -456,7 +511,7 @@ public class FxAccountClient20 implements FxAccountClient {
|
|||
* @param delegate
|
||||
* to invoke callbacks.
|
||||
*/
|
||||
public void status(byte[] sessionToken, final RequestDelegate<StatusResponse> delegate) {
|
||||
public void recoveryEmailStatus(byte[] sessionToken, final RequestDelegate<RecoveryEmailStatusResponse> delegate) {
|
||||
final byte[] tokenId = new byte[32];
|
||||
final byte[] reqHMACKey = new byte[32];
|
||||
final byte[] requestKey = new byte[32];
|
||||
|
@ -475,20 +530,14 @@ public class FxAccountClient20 implements FxAccountClient {
|
|||
return;
|
||||
}
|
||||
|
||||
resource.delegate = new ResourceDelegate<StatusResponse>(resource, delegate, tokenId, reqHMACKey) {
|
||||
resource.delegate = new ResourceDelegate<RecoveryEmailStatusResponse>(resource, delegate, ResponseType.JSON_OBJECT, tokenId, reqHMACKey) {
|
||||
@Override
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
|
||||
try {
|
||||
String[] requiredStringFields = new String[] { JSON_KEY_EMAIL };
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
|
||||
String email = body.getString(JSON_KEY_EMAIL);
|
||||
Boolean verified = body.getBoolean(JSON_KEY_VERIFIED);
|
||||
delegate.handleSuccess(new StatusResponse(email, verified));
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
return;
|
||||
}
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) throws Exception {
|
||||
String[] requiredStringFields = new String[] { JSON_KEY_EMAIL };
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
|
||||
String email = body.getString(JSON_KEY_EMAIL);
|
||||
Boolean verified = body.getBoolean(JSON_KEY_VERIFIED);
|
||||
delegate.handleSuccess(new RecoveryEmailStatusResponse(email, verified));
|
||||
}
|
||||
};
|
||||
resource.get();
|
||||
|
@ -517,9 +566,9 @@ public class FxAccountClient20 implements FxAccountClient {
|
|||
return;
|
||||
}
|
||||
|
||||
resource.delegate = new ResourceDelegate<String>(resource, delegate, tokenId, reqHMACKey) {
|
||||
resource.delegate = new ResourceDelegate<String>(resource, delegate, ResponseType.JSON_OBJECT, tokenId, reqHMACKey) {
|
||||
@Override
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) throws Exception {
|
||||
String cert = body.getString("cert");
|
||||
if (cert == null) {
|
||||
delegate.handleError(new FxAccountClientException("cert must be a non-null string"));
|
||||
|
@ -528,7 +577,7 @@ public class FxAccountClient20 implements FxAccountClient {
|
|||
delegate.handleSuccess(cert);
|
||||
}
|
||||
};
|
||||
post(resource, body, delegate);
|
||||
post(resource, body);
|
||||
}
|
||||
|
||||
protected static final String[] LOGIN_RESPONSE_REQUIRED_STRING_FIELDS = new String[] { JSON_KEY_UID, JSON_KEY_SESSIONTOKEN };
|
||||
|
@ -580,35 +629,29 @@ public class FxAccountClient20 implements FxAccountClient {
|
|||
return;
|
||||
}
|
||||
|
||||
resource.delegate = new ResourceDelegate<LoginResponse>(resource, delegate) {
|
||||
resource.delegate = new ResourceDelegate<LoginResponse>(resource, delegate, ResponseType.JSON_OBJECT) {
|
||||
@Override
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
|
||||
try {
|
||||
final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS;
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) throws Exception {
|
||||
final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS;
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
|
||||
|
||||
final String[] requiredBooleanFields = LOGIN_RESPONSE_REQUIRED_BOOLEAN_FIELDS;
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredBooleanFields, Boolean.class);
|
||||
final String[] requiredBooleanFields = LOGIN_RESPONSE_REQUIRED_BOOLEAN_FIELDS;
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredBooleanFields, Boolean.class);
|
||||
|
||||
String uid = body.getString(JSON_KEY_UID);
|
||||
boolean verified = body.getBoolean(JSON_KEY_VERIFIED);
|
||||
byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
|
||||
byte[] keyFetchToken = null;
|
||||
if (getKeys) {
|
||||
keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
|
||||
}
|
||||
LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
|
||||
|
||||
delegate.handleSuccess(loginResponse);
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
return;
|
||||
String uid = body.getString(JSON_KEY_UID);
|
||||
boolean verified = body.getBoolean(JSON_KEY_VERIFIED);
|
||||
byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
|
||||
byte[] keyFetchToken = null;
|
||||
if (getKeys) {
|
||||
keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
|
||||
}
|
||||
LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
|
||||
|
||||
delegate.handleSuccess(loginResponse);
|
||||
}
|
||||
};
|
||||
|
||||
post(resource, body, delegate);
|
||||
post(resource, body);
|
||||
}
|
||||
|
||||
public void createAccount(final byte[] emailUTF8, final byte[] quickStretchedPW,
|
||||
|
@ -635,34 +678,30 @@ public class FxAccountClient20 implements FxAccountClient {
|
|||
}
|
||||
|
||||
// This is very similar to login, except verified is not required.
|
||||
resource.delegate = new ResourceDelegate<LoginResponse>(resource, delegate) {
|
||||
resource.delegate = new ResourceDelegate<LoginResponse>(resource, delegate, ResponseType.JSON_OBJECT) {
|
||||
@Override
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
|
||||
try {
|
||||
final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS;
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) throws Exception {
|
||||
final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS;
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
|
||||
|
||||
String uid = body.getString(JSON_KEY_UID);
|
||||
boolean verified = false; // In production, we're definitely not verified immediately upon creation.
|
||||
Boolean tempVerified = body.getBoolean(JSON_KEY_VERIFIED);
|
||||
if (tempVerified != null) {
|
||||
verified = tempVerified;
|
||||
}
|
||||
byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
|
||||
byte[] keyFetchToken = null;
|
||||
if (getKeys) {
|
||||
keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
|
||||
}
|
||||
LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
|
||||
|
||||
delegate.handleSuccess(loginResponse);
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
String uid = body.getString(JSON_KEY_UID);
|
||||
boolean verified = false; // In production, we're definitely not verified immediately upon creation.
|
||||
Boolean tempVerified = body.getBoolean(JSON_KEY_VERIFIED);
|
||||
if (tempVerified != null) {
|
||||
verified = tempVerified;
|
||||
}
|
||||
byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
|
||||
byte[] keyFetchToken = null;
|
||||
if (getKeys) {
|
||||
keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
|
||||
}
|
||||
LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
|
||||
|
||||
delegate.handleSuccess(loginResponse);
|
||||
}
|
||||
};
|
||||
|
||||
post(resource, body, delegate);
|
||||
post(resource, body);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -737,4 +776,86 @@ public class FxAccountClient20 implements FxAccountClient {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a device given a valid session token.
|
||||
*
|
||||
* @param sessionToken to query.
|
||||
* @param delegate to invoke callbacks.
|
||||
*/
|
||||
@Override
|
||||
public void registerOrUpdateDevice(byte[] sessionToken, FxAccountDevice device, RequestDelegate<FxAccountDevice> delegate) {
|
||||
final byte[] tokenId = new byte[32];
|
||||
final byte[] reqHMACKey = new byte[32];
|
||||
final byte[] requestKey = new byte[32];
|
||||
try {
|
||||
HKDF.deriveMany(sessionToken, new byte[0], FxAccountUtils.KW("sessionToken"), tokenId, reqHMACKey, requestKey);
|
||||
} catch (Exception e) {
|
||||
invokeHandleError(delegate, e);
|
||||
return;
|
||||
}
|
||||
|
||||
final BaseResource resource;
|
||||
final ExtendedJSONObject body;
|
||||
try {
|
||||
resource = getBaseResource("account/device");
|
||||
body = device.toJson();
|
||||
} catch (URISyntaxException | UnsupportedEncodingException e) {
|
||||
invokeHandleError(delegate, e);
|
||||
return;
|
||||
}
|
||||
|
||||
resource.delegate = new ResourceDelegate<FxAccountDevice>(resource, delegate, ResponseType.JSON_OBJECT, tokenId, reqHMACKey) {
|
||||
@Override
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
|
||||
try {
|
||||
delegate.handleSuccess(FxAccountDevice.fromJson(body));
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
post(resource, body);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deviceList(byte[] sessionToken, RequestDelegate<FxAccountDevice[]> delegate) {
|
||||
final byte[] tokenId = new byte[32];
|
||||
final byte[] reqHMACKey = new byte[32];
|
||||
final byte[] requestKey = new byte[32];
|
||||
try {
|
||||
HKDF.deriveMany(sessionToken, new byte[0], FxAccountUtils.KW("sessionToken"), tokenId, reqHMACKey, requestKey);
|
||||
} catch (Exception e) {
|
||||
invokeHandleError(delegate, e);
|
||||
return;
|
||||
}
|
||||
|
||||
final BaseResource resource;
|
||||
final ExtendedJSONObject body;
|
||||
try {
|
||||
resource = getBaseResource("account/devices");
|
||||
} catch (URISyntaxException | UnsupportedEncodingException e) {
|
||||
invokeHandleError(delegate, e);
|
||||
return;
|
||||
}
|
||||
|
||||
resource.delegate = new ResourceDelegate<FxAccountDevice[]>(resource, delegate, ResponseType.JSON_ARRAY, tokenId, reqHMACKey) {
|
||||
@Override
|
||||
public void handleSuccess(int status, HttpResponse response, JSONArray devicesJson) {
|
||||
try {
|
||||
FxAccountDevice[] devices = new FxAccountDevice[devicesJson.size()];
|
||||
for (int i = 0; i < devices.length; i++) {
|
||||
ExtendedJSONObject deviceJson = new ExtendedJSONObject((JSONObject) devicesJson.get(i));
|
||||
devices[i] = FxAccountDevice.fromJson(deviceJson);
|
||||
}
|
||||
delegate.handleSuccess(devices);
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
resource.get();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ public interface FxAccountRemoteError {
|
|||
public static final int INCORRECT_API_VERSION_FOR_THIS_ACCOUNT = 119;
|
||||
public static final int INCORRECT_EMAIL_CASE = 120;
|
||||
public static final int ACCOUNT_LOCKED = 121;
|
||||
public static final int UNKNOWN_DEVICE = 123;
|
||||
public static final int DEVICE_SESSION_CONFLICT = 124;
|
||||
public static final int SERVICE_TEMPORARILY_UNAVAILABLE_DUE_TO_HIGH_LOAD = 201;
|
||||
public static final int UNKNOWN_ERROR = 999;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/* 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;
|
||||
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
|
||||
public class FxAccountDevice {
|
||||
|
||||
public static final String JSON_KEY_NAME = "name";
|
||||
public static final String JSON_KEY_ID = "id";
|
||||
public static final String JSON_KEY_TYPE = "type";
|
||||
public static final String JSON_KEY_ISCURRENTDEVICE = "isCurrentDevice";
|
||||
|
||||
public String id;
|
||||
public String name;
|
||||
public String type;
|
||||
public Boolean isCurrentDevice;
|
||||
|
||||
public FxAccountDevice(String name, String id, String type, Boolean isCurrentDevice) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.isCurrentDevice = isCurrentDevice;
|
||||
}
|
||||
|
||||
public static FxAccountDevice forRegister(String name, String type) {
|
||||
return new FxAccountDevice(name, null, type, null);
|
||||
}
|
||||
|
||||
public static FxAccountDevice forUpdate(String id, String name) {
|
||||
return new FxAccountDevice(name, id, null, null);
|
||||
}
|
||||
|
||||
public static FxAccountDevice fromJson(ExtendedJSONObject json) {
|
||||
String name = json.getString(JSON_KEY_NAME);
|
||||
String id = json.getString(JSON_KEY_ID);
|
||||
String type = json.getString(JSON_KEY_TYPE);
|
||||
Boolean isCurrentDevice = json.getBoolean(JSON_KEY_ISCURRENTDEVICE);
|
||||
return new FxAccountDevice(name, id, type, isCurrentDevice);
|
||||
}
|
||||
|
||||
public ExtendedJSONObject toJson() {
|
||||
final ExtendedJSONObject body = new ExtendedJSONObject();
|
||||
if (this.name != null) {
|
||||
body.put(JSON_KEY_NAME, this.name);
|
||||
}
|
||||
if (this.id != null) {
|
||||
body.put(JSON_KEY_ID, this.id);
|
||||
}
|
||||
if (this.type != null) {
|
||||
body.put(JSON_KEY_TYPE, this.type);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
/* 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountRemoteError;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.fxa.login.State.StateLabel;
|
||||
import org.mozilla.gecko.fxa.login.TokensAndKeysState;
|
||||
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/* This class provides a way to register the current device against FxA
|
||||
* and also stores the registration details in the Android FxAccount.
|
||||
* This should be used in a state where we possess a sessionToken, most likely the Married state.
|
||||
*/
|
||||
public class FxAccountDeviceRegistrator {
|
||||
|
||||
public abstract static class RegisterDelegate {
|
||||
private boolean allowRecursion = true;
|
||||
protected abstract void onComplete(String deviceId);
|
||||
}
|
||||
|
||||
public static class InvalidFxAState extends Exception {
|
||||
private static final long serialVersionUID = -8537626959811195978L;
|
||||
|
||||
public InvalidFxAState(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
// The current version of the device registration, we use this to re-register
|
||||
// devices after we update what we send on device registration.
|
||||
public static final Integer DEVICE_REGISTRATION_VERSION = 1;
|
||||
|
||||
private static final String LOG_TAG = "FxADeviceRegistrator";
|
||||
|
||||
private FxAccountDeviceRegistrator() {}
|
||||
|
||||
public static void register(final AndroidFxAccount fxAccount, final Context context) throws InvalidFxAState {
|
||||
register(fxAccount, context, new RegisterDelegate() {
|
||||
@Override
|
||||
public void onComplete(String deviceId) {}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidFxAState thrown if we're not in a fxa state with a session token
|
||||
*/
|
||||
public static void register(final AndroidFxAccount fxAccount, final Context context,
|
||||
final RegisterDelegate delegate) throws InvalidFxAState {
|
||||
final byte[] sessionToken = getSessionToken(fxAccount);
|
||||
|
||||
final FxAccountDevice device;
|
||||
String deviceId = fxAccount.getDeviceId();
|
||||
String clientName = getClientName(fxAccount, context);
|
||||
if (TextUtils.isEmpty(deviceId)) {
|
||||
Log.i(LOG_TAG, "Attempting registration for a new device");
|
||||
device = FxAccountDevice.forRegister(clientName, "mobile");
|
||||
} else {
|
||||
Log.i(LOG_TAG, "Attempting registration for an existing device");
|
||||
Logger.pii(LOG_TAG, "Device ID: " + deviceId);
|
||||
device = FxAccountDevice.forUpdate(deviceId, clientName);
|
||||
}
|
||||
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor(); // Not called often, it's okay to spawn another thread
|
||||
final FxAccountClient20 fxAccountClient =
|
||||
new FxAccountClient20(fxAccount.getAccountServerURI(), executor);
|
||||
fxAccountClient.registerOrUpdateDevice(sessionToken, device, new RequestDelegate<FxAccountDevice>() {
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
Log.e(LOG_TAG, "Error while updating a device registration: ", e);
|
||||
delegate.onComplete(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(FxAccountClientRemoteException error) {
|
||||
Log.e(LOG_TAG, "Error while updating a device registration: ", error);
|
||||
if (error.httpStatusCode == 400) {
|
||||
if (error.apiErrorNumber == FxAccountRemoteError.UNKNOWN_DEVICE) {
|
||||
recoverFromUnknownDevice(fxAccount);
|
||||
delegate.onComplete(null);
|
||||
} else if (error.apiErrorNumber == FxAccountRemoteError.DEVICE_SESSION_CONFLICT) {
|
||||
recoverFromDeviceSessionConflict(error, fxAccountClient, sessionToken, fxAccount,
|
||||
context, delegate); // Will call delegate.onComplete
|
||||
}
|
||||
} else
|
||||
if (error.httpStatusCode == 401
|
||||
&& error.apiErrorNumber == FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN) {
|
||||
handleTokenError(error, fxAccountClient, fxAccount);
|
||||
delegate.onComplete(null);
|
||||
} else {
|
||||
logErrorAndResetDeviceRegistrationVersion(error, fxAccount);
|
||||
delegate.onComplete(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSuccess(FxAccountDevice result) {
|
||||
Log.i(LOG_TAG, "Device registration complete");
|
||||
Logger.pii(LOG_TAG, "Registered device ID: " + result.id);
|
||||
fxAccount.setFxAUserData(result.id, DEVICE_REGISTRATION_VERSION);
|
||||
delegate.onComplete(result.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void logErrorAndResetDeviceRegistrationVersion(
|
||||
final FxAccountClientRemoteException error, final AndroidFxAccount fxAccount) {
|
||||
Log.e(LOG_TAG, "Device registration failed", error);
|
||||
fxAccount.resetDeviceRegistrationVersion();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getClientName(final AndroidFxAccount fxAccount, final Context context) {
|
||||
try {
|
||||
SharedPreferencesClientsDataDelegate clientsDataDelegate =
|
||||
new SharedPreferencesClientsDataDelegate(fxAccount.getSyncPrefs(), context);
|
||||
return clientsDataDelegate.getClientName();
|
||||
} catch (UnsupportedEncodingException | GeneralSecurityException e) {
|
||||
Log.e(LOG_TAG, "Unable to get client name.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static byte[] getSessionToken(final AndroidFxAccount fxAccount) throws InvalidFxAState {
|
||||
State state = fxAccount.getState();
|
||||
StateLabel stateLabel = state.getStateLabel();
|
||||
if (stateLabel == StateLabel.Cohabiting || stateLabel == StateLabel.Married) {
|
||||
TokensAndKeysState tokensAndKeysState = (TokensAndKeysState) state;
|
||||
return tokensAndKeysState.getSessionToken();
|
||||
}
|
||||
throw new InvalidFxAState("Cannot get sessionToken: not in a TokensAndKeysState state");
|
||||
}
|
||||
|
||||
private static void handleTokenError(final FxAccountClientRemoteException error,
|
||||
final FxAccountClient fxAccountClient,
|
||||
final AndroidFxAccount fxAccount) {
|
||||
Log.i(LOG_TAG, "Recovering from invalid token error: ", error);
|
||||
logErrorAndResetDeviceRegistrationVersion(error, fxAccount);
|
||||
fxAccountClient.accountStatus(fxAccount.getState().uid,
|
||||
new RequestDelegate<AccountStatusResponse>() {
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(FxAccountClientRemoteException e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSuccess(AccountStatusResponse result) {
|
||||
State doghouseState = fxAccount.getState().makeDoghouseState();
|
||||
if (!result.exists) {
|
||||
Log.i(LOG_TAG, "token invalidated because the account no longer exists");
|
||||
// TODO: Should be in a "I have an Android account, but the FxA is gone." State.
|
||||
// This will do for now..
|
||||
fxAccount.setState(doghouseState);
|
||||
return;
|
||||
}
|
||||
Log.e(LOG_TAG, "sessionToken invalid");
|
||||
fxAccount.setState(doghouseState);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void recoverFromUnknownDevice(final AndroidFxAccount fxAccount) {
|
||||
Log.i(LOG_TAG, "unknown device id, clearing the cached device id");
|
||||
fxAccount.setDeviceId(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will call delegate#complete in all cases
|
||||
*/
|
||||
private static void recoverFromDeviceSessionConflict(final FxAccountClientRemoteException error,
|
||||
final FxAccountClient fxAccountClient,
|
||||
final byte[] sessionToken,
|
||||
final AndroidFxAccount fxAccount,
|
||||
final Context context,
|
||||
final RegisterDelegate delegate) {
|
||||
Log.w(LOG_TAG, "device session conflict, attempting to ascertain the correct device id");
|
||||
fxAccountClient.deviceList(sessionToken, new RequestDelegate<FxAccountDevice[]>() {
|
||||
private void onError() {
|
||||
Log.e(LOG_TAG, "failed to recover from device-session conflict");
|
||||
logErrorAndResetDeviceRegistrationVersion(error, fxAccount);
|
||||
delegate.onComplete(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
onError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(FxAccountClientRemoteException e) {
|
||||
onError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSuccess(FxAccountDevice[] devices) {
|
||||
for (FxAccountDevice device : devices) {
|
||||
if (device.isCurrentDevice) {
|
||||
fxAccount.setFxAUserData(device.id, 0); // Reset device registration version
|
||||
if (!delegate.allowRecursion) {
|
||||
Log.d(LOG_TAG, "Failure to register a device on the second try");
|
||||
break;
|
||||
}
|
||||
delegate.allowRecursion = false; // Make sure we don't fall into an infinite loop
|
||||
try {
|
||||
register(fxAccount, context, delegate); // Will call delegate.onComplete()
|
||||
return;
|
||||
} catch (InvalidFxAState e) {
|
||||
Log.d(LOG_TAG, "Invalid state when trying to recover from a session conflict ", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
onError();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -84,6 +84,8 @@ public class FxAccountStatusFragment
|
|||
private static final long LAST_SYNCED_TIME_UPDATE_INTERVAL_IN_MILLISECONDS = 60 * 1000;
|
||||
private static final long PROFILE_FETCH_RETRY_INTERVAL_IN_MILLISECONDS = 60 * 1000;
|
||||
|
||||
private static final String[] STAGES_TO_SYNC_ON_DEVICE_NAME_CHANGE = new String[] { "clients" };
|
||||
|
||||
// By default, the auth/account server preference is only shown when the
|
||||
// account is configured to use a custom server. In debug mode, this is set.
|
||||
private static boolean ALWAYS_SHOW_AUTH_SERVER = false;
|
||||
|
@ -939,7 +941,10 @@ public class FxAccountStatusFragment
|
|||
}
|
||||
final long now = System.currentTimeMillis();
|
||||
clientsDataDelegate.setClientName(newClientName, now);
|
||||
requestDelayedSync(); // Try to update our remote client record.
|
||||
// Force sync the client record, we want the user to see the device name change immediately
|
||||
// on the FxA Device Manager if possible ( = we are online) to avoid confusion
|
||||
// ("I changed my Android's device name but I don't see it on my computer").
|
||||
fxAccount.requestImmediateSync(STAGES_TO_SYNC_ON_DEVICE_NAME_CHANGE, null);
|
||||
hardRefresh(); // Updates the value displayed to the user, among other things.
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,10 @@ import android.content.SharedPreferences;
|
|||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.ResultReceiver;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.background.common.GlobalConstants;
|
||||
|
@ -74,6 +77,9 @@ public class AndroidFxAccount {
|
|||
public static final String BUNDLE_KEY_STATE = "state";
|
||||
public static final String BUNDLE_KEY_PROFILE_JSON = "profile";
|
||||
|
||||
public static final String ACCOUNT_KEY_DEVICE_ID = "deviceId";
|
||||
public static final String ACCOUNT_KEY_DEVICE_REGISTRATION_VERSION = "deviceRegistrationVersion";
|
||||
|
||||
// Account authentication token type for fetching account profile.
|
||||
public static final String PROFILE_OAUTH_TOKEN_TYPE = "oauth::profile";
|
||||
|
||||
|
@ -384,6 +390,8 @@ public class AndroidFxAccount {
|
|||
} catch (UnsupportedEncodingException e) {
|
||||
// Ignore.
|
||||
}
|
||||
o.put("fxaDeviceId", getDeviceId());
|
||||
o.put("fxaDeviceRegistrationVersion", getDeviceRegistrationVersion());
|
||||
return o;
|
||||
}
|
||||
|
||||
|
@ -773,6 +781,44 @@ public class AndroidFxAccount {
|
|||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public synchronized String getDeviceId() {
|
||||
return accountManager.getUserData(account, ACCOUNT_KEY_DEVICE_ID);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public synchronized int getDeviceRegistrationVersion() {
|
||||
String versionStr = accountManager.getUserData(account, ACCOUNT_KEY_DEVICE_REGISTRATION_VERSION);
|
||||
if (TextUtils.isEmpty(versionStr)) {
|
||||
return 0;
|
||||
} else {
|
||||
try {
|
||||
return Integer.parseInt(versionStr);
|
||||
} catch (NumberFormatException ex) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void setDeviceId(String id) {
|
||||
accountManager.setUserData(account, ACCOUNT_KEY_DEVICE_ID, id);
|
||||
}
|
||||
|
||||
public synchronized void setDeviceRegistrationVersion(int deviceRegistrationVersion) {
|
||||
accountManager.setUserData(account, ACCOUNT_KEY_DEVICE_REGISTRATION_VERSION,
|
||||
Integer.toString(deviceRegistrationVersion));
|
||||
}
|
||||
|
||||
public synchronized void resetDeviceRegistrationVersion() {
|
||||
setDeviceRegistrationVersion(0);
|
||||
}
|
||||
|
||||
public synchronized void setFxAUserData(String id, int deviceRegistrationVersion) {
|
||||
accountManager.setUserData(account, ACCOUNT_KEY_DEVICE_ID, id);
|
||||
accountManager.setUserData(account, ACCOUNT_KEY_DEVICE_REGISTRATION_VERSION,
|
||||
Integer.toString(deviceRegistrationVersion));
|
||||
}
|
||||
|
||||
@SuppressLint("ParcelCreator") // The CREATOR field is defined in the super class.
|
||||
private class ProfileResultReceiver extends ResultReceiver {
|
||||
public ProfileResultReceiver(Handler handler) {
|
||||
|
|
|
@ -34,6 +34,10 @@ public abstract class TokensAndKeysState extends State {
|
|||
return o;
|
||||
}
|
||||
|
||||
public byte[] getSessionToken() {
|
||||
return sessionToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action getNeededAction() {
|
||||
return Action.None;
|
||||
|
|
|
@ -13,6 +13,7 @@ import android.content.SharedPreferences;
|
|||
import android.content.SyncResult;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.common.telemetry.TelemetryWrapper;
|
||||
|
@ -21,6 +22,7 @@ import org.mozilla.gecko.background.fxa.SkewHandler;
|
|||
import org.mozilla.gecko.browserid.JSONWebTokenUtils;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.FxAccountDeviceRegistrator;
|
||||
import org.mozilla.gecko.fxa.authenticator.AccountPickler;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.authenticator.FxADefaultLoginStateMachineDelegate;
|
||||
|
@ -536,7 +538,13 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
final String clientState = married.getClientState();
|
||||
syncWithAssertion(audience, assertion, tokenServerEndpointURI, tokenBackoffHandler, sharedPrefs, syncKeyBundle, clientState, sessionCallback, extras, fxAccount);
|
||||
|
||||
// Force fetch the profile avatar information.
|
||||
// Register the device if necessary (asynchronous, in another thread)
|
||||
if (fxAccount.getDeviceRegistrationVersion() != FxAccountDeviceRegistrator.DEVICE_REGISTRATION_VERSION
|
||||
|| TextUtils.isEmpty(fxAccount.getDeviceId())) {
|
||||
FxAccountDeviceRegistrator.register(fxAccount, context);
|
||||
}
|
||||
|
||||
// Force fetch the profile avatar information. (asynchronous, in another thread)
|
||||
Logger.info(LOG_TAG, "Fetching profile avatar information.");
|
||||
fxAccount.fetchProfileJSON();
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -5,9 +5,13 @@
|
|||
package org.mozilla.gecko.sync;
|
||||
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.FxAccountDeviceRegistrator;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
|
@ -37,6 +41,14 @@ public class SharedPreferencesClientsDataDelegate implements ClientsDataDelegate
|
|||
return accountGUID;
|
||||
}
|
||||
|
||||
private synchronized void saveClientNameToSharedPreferences(String clientName, long now) {
|
||||
sharedPreferences
|
||||
.edit()
|
||||
.putString(SyncConfiguration.PREF_CLIENT_NAME, clientName)
|
||||
.putLong(SyncConfiguration.PREF_CLIENT_DATA_TIMESTAMP, now)
|
||||
.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set client name.
|
||||
*
|
||||
|
@ -44,11 +56,14 @@ public class SharedPreferencesClientsDataDelegate implements ClientsDataDelegate
|
|||
*/
|
||||
@Override
|
||||
public synchronized void setClientName(String clientName, long now) {
|
||||
sharedPreferences
|
||||
.edit()
|
||||
.putString(SyncConfiguration.PREF_CLIENT_NAME, clientName)
|
||||
.putLong(SyncConfiguration.PREF_CLIENT_DATA_TIMESTAMP, now)
|
||||
.commit();
|
||||
saveClientNameToSharedPreferences(clientName, now);
|
||||
|
||||
// Update the FxA device registration
|
||||
final Account account = FirefoxAccounts.getFirefoxAccount(context);
|
||||
if (account != null) {
|
||||
final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
|
||||
fxAccount.resetDeviceRegistrationVersion();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -62,7 +77,7 @@ public class SharedPreferencesClientsDataDelegate implements ClientsDataDelegate
|
|||
if (clientName == null) {
|
||||
clientName = getDefaultClientName();
|
||||
long now = System.currentTimeMillis();
|
||||
setClientName(clientName, now);
|
||||
saveClientNameToSharedPreferences(clientName, now); // Save locally only to avoid a recursion loop
|
||||
}
|
||||
return clientName;
|
||||
}
|
||||
|
|
|
@ -11,8 +11,12 @@ import java.io.InputStreamReader;
|
|||
import java.io.Reader;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.NonArrayJSONException;
|
||||
import org.mozilla.gecko.sync.NonObjectJSONException;
|
||||
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
|
@ -110,6 +114,33 @@ public class MozResponse {
|
|||
}
|
||||
}
|
||||
|
||||
public JSONArray jsonArrayBody() throws NonArrayJSONException, IOException {
|
||||
final JSONParser parser = new JSONParser();
|
||||
try {
|
||||
if (body != null) {
|
||||
// Do it from the cached String.
|
||||
return (JSONArray) parser.parse(body);
|
||||
}
|
||||
|
||||
final HttpEntity entity = this.response.getEntity();
|
||||
if (entity == null) {
|
||||
throw new IOException("no entity");
|
||||
}
|
||||
|
||||
final InputStream content = entity.getContent();
|
||||
final Reader in = new BufferedReader(new InputStreamReader(content, "UTF-8"));
|
||||
try {
|
||||
return (JSONArray) parser.parse(in);
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
} catch (ClassCastException | ParseException e) {
|
||||
NonArrayJSONException exception = new NonArrayJSONException("value must be a json array");
|
||||
exception.initCause(e);
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean hasHeader(String h) {
|
||||
return this.response.containsHeader(h);
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ public class ClientRecord extends Record {
|
|||
public String application; // Display name, E.g., "Firefox Beta"
|
||||
public String appPackage; // E.g., "org.mozilla.firefox_beta"
|
||||
public String device; // E.g., "HTC One"
|
||||
public String fxaDeviceId; // E.g., "525b624eaaf1e40d21ec8997c3116ad8"
|
||||
|
||||
public ClientRecord(String guid, String collection, long lastModified, boolean deleted) {
|
||||
super(guid, collection, lastModified, deleted);
|
||||
|
@ -115,6 +116,10 @@ public class ClientRecord extends Record {
|
|||
if (payload.containsKey("device")) {
|
||||
this.device = payload.getString("device");
|
||||
}
|
||||
|
||||
if (payload.containsKey("fxaDeviceId")) {
|
||||
this.fxaDeviceId = payload.getString("fxaDeviceId");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -132,7 +137,6 @@ public class ClientRecord extends Record {
|
|||
payload.put("protocols", this.protocols);
|
||||
}
|
||||
|
||||
|
||||
if (this.formfactor != null) {
|
||||
payload.put("formfactor", this.formfactor);
|
||||
}
|
||||
|
@ -152,6 +156,10 @@ public class ClientRecord extends Record {
|
|||
if (this.device != null) {
|
||||
payload.put("device", this.device);
|
||||
}
|
||||
|
||||
if (this.fxaDeviceId != null) {
|
||||
payload.put("fxaDeviceId", this.fxaDeviceId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -201,6 +209,7 @@ public class ClientRecord extends Record {
|
|||
out.application = this.application;
|
||||
out.appPackage = this.appPackage;
|
||||
out.device = this.device;
|
||||
out.fxaDeviceId = this.fxaDeviceId;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
|
||||
package org.mozilla.gecko.sync.stage;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
@ -15,6 +19,8 @@ import org.json.simple.JSONArray;
|
|||
import org.json.simple.JSONObject;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.sync.CommandProcessor;
|
||||
import org.mozilla.gecko.sync.CommandProcessor.Command;
|
||||
import org.mozilla.gecko.sync.CryptoRecord;
|
||||
|
@ -385,6 +391,16 @@ public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
|
|||
r.device = android.os.Build.MODEL;
|
||||
r.formfactor = delegate.getFormFactor();
|
||||
|
||||
Context context = session.getContext();
|
||||
final Account account = FirefoxAccounts.getFirefoxAccount(context);
|
||||
if (account != null) {
|
||||
final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
|
||||
final String deviceId = fxAccount.getDeviceId();
|
||||
if (!TextUtils.isEmpty(deviceId)) {
|
||||
r.fxaDeviceId = deviceId;
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,22 +3,28 @@
|
|||
|
||||
package org.mozilla.gecko.fxa.login;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.StatusResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.RecoveryEmailStatusResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.TwoKeys;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountRemoteError;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.fxa.FxAccountDevice;
|
||||
import org.mozilla.gecko.browserid.MockMyIDTokenFactory;
|
||||
import org.mozilla.gecko.browserid.RSACryptoImplementation;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpStatus;
|
||||
import ch.boye.httpclientandroidlib.ProtocolVersion;
|
||||
|
@ -41,6 +47,7 @@ public class MockFxAccountClient implements FxAccountClient {
|
|||
public boolean verified;
|
||||
public final byte[] kA;
|
||||
public final byte[] wrapkB;
|
||||
public final Map<String, FxAccountDevice> devices;
|
||||
|
||||
public User(String email, byte[] quickStretchedPW) {
|
||||
this.email = email;
|
||||
|
@ -49,6 +56,7 @@ public class MockFxAccountClient implements FxAccountClient {
|
|||
this.verified = false;
|
||||
this.kA = Utils.generateRandomBytes(FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES);
|
||||
this.wrapkB = Utils.generateRandomBytes(FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES);
|
||||
this.devices = new HashMap<String, FxAccountDevice>();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,14 +102,26 @@ public class MockFxAccountClient implements FxAccountClient {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void status(byte[] sessionToken, RequestDelegate<StatusResponse> requestDelegate) {
|
||||
public void accountStatus(String uid, RequestDelegate<AccountStatusResponse> requestDelegate) {
|
||||
boolean userFound = false;
|
||||
for (User user : users.values()) {
|
||||
if (user.uid.equals(uid)) {
|
||||
userFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
requestDelegate.handleSuccess(new AccountStatusResponse(userFound));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recoveryEmailStatus(byte[] sessionToken, RequestDelegate<RecoveryEmailStatusResponse> requestDelegate) {
|
||||
String email = sessionTokens.get(Utils.byte2Hex(sessionToken));
|
||||
User user = users.get(email);
|
||||
if (email == null || user == null) {
|
||||
handleFailure(requestDelegate, HttpStatus.SC_UNAUTHORIZED, FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN, "invalid sessionToken");
|
||||
return;
|
||||
}
|
||||
requestDelegate.handleSuccess(new StatusResponse(email, user.verified));
|
||||
requestDelegate.handleSuccess(new RecoveryEmailStatusResponse(email, user.verified));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -141,4 +161,56 @@ public class MockFxAccountClient implements FxAccountClient {
|
|||
requestDelegate.handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerOrUpdateDevice(byte[] sessionToken, FxAccountDevice deviceToRegister, RequestDelegate<FxAccountDevice> requestDelegate) {
|
||||
String email = sessionTokens.get(Utils.byte2Hex(sessionToken));
|
||||
User user = users.get(email);
|
||||
if (email == null || user == null) {
|
||||
handleFailure(requestDelegate, HttpStatus.SC_UNAUTHORIZED, FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN, "invalid sessionToken");
|
||||
return;
|
||||
}
|
||||
if (!user.verified) {
|
||||
handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT, "user is unverified");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String deviceId = deviceToRegister.id;
|
||||
if (TextUtils.isEmpty(deviceId)) { // Create
|
||||
deviceId = UUID.randomUUID().toString();
|
||||
FxAccountDevice device = new FxAccountDevice(deviceToRegister.name, deviceId, deviceToRegister.type, null);
|
||||
deviceToRegister.id = deviceId;
|
||||
} else { // Update
|
||||
FxAccountDevice existingDevice = user.devices.get(deviceId);
|
||||
if (existingDevice != null) {
|
||||
if (!TextUtils.isEmpty(deviceToRegister.name)) {
|
||||
existingDevice.name = deviceToRegister.name;
|
||||
} // We could also update the other fields..
|
||||
} else { // Device unknown
|
||||
handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.UNKNOWN_DEVICE, "device is unknown");
|
||||
return;
|
||||
}
|
||||
}
|
||||
requestDelegate.handleSuccess(deviceToRegister);
|
||||
} catch (Exception e) {
|
||||
requestDelegate.handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deviceList(byte[] sessionToken, RequestDelegate<FxAccountDevice[]> requestDelegate) {
|
||||
String email = sessionTokens.get(Utils.byte2Hex(sessionToken));
|
||||
User user = users.get(email);
|
||||
if (email == null || user == null) {
|
||||
handleFailure(requestDelegate, HttpStatus.SC_UNAUTHORIZED, FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN, "invalid sessionToken");
|
||||
return;
|
||||
}
|
||||
if (!user.verified) {
|
||||
handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT, "user is unverified");
|
||||
return;
|
||||
}
|
||||
Collection<FxAccountDevice> devices = user.devices.values();
|
||||
FxAccountDevice[] devicesArray = devices.toArray(new FxAccountDevice[devices.size()]);
|
||||
requestDelegate.handleSuccess(devicesArray);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
|
|||
import org.mozilla.android.sync.test.helpers.MockServer;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.StatusResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.RecoveryEmailStatusResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.testhelpers.TestRunner;
|
||||
import org.mozilla.gecko.background.testhelpers.WaitHelper;
|
||||
|
@ -106,9 +106,9 @@ public class TestUserAgentHeaders {
|
|||
WaitHelper.getTestWaiter().performWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
client.status(new byte[] { 0 }, new RequestDelegate<FxAccountClient20.StatusResponse>() {
|
||||
client.recoveryEmailStatus(new byte[] { 0 }, new RequestDelegate<RecoveryEmailStatusResponse>() {
|
||||
@Override
|
||||
public void handleSuccess(StatusResponse result) {
|
||||
public void handleSuccess(RecoveryEmailStatusResponse result) {
|
||||
WaitHelper.getTestWaiter().performNotify();
|
||||
}
|
||||
|
||||
|
|
|
@ -5272,8 +5272,6 @@ pref("browser.addon-watch.interval", 15000);
|
|||
pref("browser.addon-watch.interval", -1);
|
||||
#endif
|
||||
pref("browser.addon-watch.ignore", "[\"mochikit@mozilla.org\",\"special-powers@mozilla.org\",\"fxdevtools-adapters@mozilla.org\",\"fx-devtools\"]");
|
||||
// the percentage of time addons are allowed to use without being labeled slow
|
||||
pref("browser.addon-watch.percentage-limit", 5);
|
||||
|
||||
// Search service settings
|
||||
pref("browser.search.log", false);
|
||||
|
|
|
@ -34,7 +34,7 @@ class TestMixedScriptContentBlocking(FirefoxTestCase):
|
|||
if enabled:
|
||||
color, icon_filename, state = (
|
||||
'rgb(0, 136, 0)',
|
||||
'identity-mixed-active-blocked',
|
||||
'identity-secure',
|
||||
'blocked'
|
||||
)
|
||||
else:
|
||||
|
|
|
@ -214,12 +214,7 @@ this.ReaderMode = {
|
|||
*/
|
||||
downloadAndParseDocument: Task.async(function* (url) {
|
||||
let uri = Services.io.newURI(url, null, null);
|
||||
TelemetryStopwatch.start("READER_MODE_DOWNLOAD_MS");
|
||||
let doc = yield this._downloadDocument(url).catch(e => {
|
||||
TelemetryStopwatch.finish("READER_MODE_DOWNLOAD_MS");
|
||||
throw e;
|
||||
});
|
||||
TelemetryStopwatch.finish("READER_MODE_DOWNLOAD_MS");
|
||||
let doc = yield this._downloadDocument(url);
|
||||
return yield this._readerParse(uri, doc);
|
||||
}),
|
||||
|
||||
|
@ -426,13 +421,10 @@ this.ReaderMode = {
|
|||
pathBase: Services.io.newURI(".", null, uri).spec
|
||||
};
|
||||
|
||||
TelemetryStopwatch.start("READER_MODE_SERIALIZE_DOM_MS");
|
||||
let serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
|
||||
createInstance(Ci.nsIDOMSerializer);
|
||||
let serializedDoc = serializer.serializeToString(doc);
|
||||
TelemetryStopwatch.finish("READER_MODE_SERIALIZE_DOM_MS");
|
||||
|
||||
TelemetryStopwatch.start("READER_MODE_WORKER_PARSE_MS");
|
||||
let article = null;
|
||||
try {
|
||||
article = yield ReaderWorker.post("parseDocument", [uriParam, serializedDoc]);
|
||||
|
@ -440,7 +432,6 @@ this.ReaderMode = {
|
|||
Cu.reportError("Error in ReaderWorker: " + e);
|
||||
histogram.add(PARSE_ERROR_WORKER);
|
||||
}
|
||||
TelemetryStopwatch.finish("READER_MODE_WORKER_PARSE_MS");
|
||||
|
||||
if (!article) {
|
||||
this.log("Worker did not return an article");
|
||||
|
|
|
@ -8656,41 +8656,16 @@
|
|||
"releaseChannelCollection": "opt-out",
|
||||
"description": "Reports results from the graphics sanity test to track which drivers are having problems (0=TEST_PASSED, 1=TEST_FAILED_RENDER, 2=TEST_FAILED_VIDEO, 3=TEST_CRASHED)"
|
||||
},
|
||||
"READER_MODE_SERIALIZE_DOM_MS": {
|
||||
"expires_in_version": "50",
|
||||
"alert_emails": ["mleibovic@mozilla.com"],
|
||||
"kind": "exponential",
|
||||
"high": 5000,
|
||||
"n_buckets": 15,
|
||||
"description": "Time (ms) to serialize a DOM to send to the reader worker"
|
||||
},
|
||||
"READER_MODE_WORKER_PARSE_MS": {
|
||||
"expires_in_version": "50",
|
||||
"alert_emails": ["mleibovic@mozilla.com"],
|
||||
"kind": "exponential",
|
||||
"high": 10000,
|
||||
"n_buckets": 30,
|
||||
"description": "Time (ms) for the reader worker to parse a document"
|
||||
},
|
||||
"READER_MODE_DOWNLOAD_MS": {
|
||||
"expires_in_version": "50",
|
||||
"alert_emails": ["mleibovic@mozilla.com"],
|
||||
"kind": "exponential",
|
||||
"low": 50,
|
||||
"high": 40000,
|
||||
"n_buckets": 60,
|
||||
"description": "Time (ms) to download a document to show in reader mode"
|
||||
},
|
||||
"READER_MODE_PARSE_RESULT" : {
|
||||
"expires_in_version": "50",
|
||||
"alert_emails": ["mleibovic@mozilla.com"],
|
||||
"expires_in_version": "54",
|
||||
"alert_emails": ["firefox-dev@mozilla.org", "gijs@mozilla.com"],
|
||||
"kind": "enumerated",
|
||||
"n_values": 5,
|
||||
"description": "The result of trying to parse a document to show in reader view (0=Success, 1=Error too many elements, 2=Error in worker, 3=Error no article)"
|
||||
},
|
||||
"READER_MODE_DOWNLOAD_RESULT" : {
|
||||
"expires_in_version": "50",
|
||||
"alert_emails": ["mleibovic@mozilla.com"],
|
||||
"expires_in_version": "54",
|
||||
"alert_emails": ["firefox-dev@mozilla.org", "gijs@mozilla.com"],
|
||||
"kind": "enumerated",
|
||||
"n_values": 5,
|
||||
"description": "The result of trying to download a document to show in reader view (0=Success, 1=Error XHR, 2=Error no document)"
|
||||
|
|
|
@ -46,13 +46,13 @@ Structure::
|
|||
addons: <string>, // obsolete, use ``environment.addons``
|
||||
},
|
||||
|
||||
processes: {...},
|
||||
childPayloads: [...], // only present with e10s; reduced payloads from content processes, null on failure
|
||||
simpleMeasurements: {...},
|
||||
|
||||
// The following properties may all be null if we fail to collect them.
|
||||
histograms: {...},
|
||||
keyedHistograms: {...},
|
||||
scalars: {...},
|
||||
chromeHangs: {...},
|
||||
threadHangStats: [...],
|
||||
log: [...],
|
||||
|
@ -84,6 +84,23 @@ This uses a monotonic clock, so this may mismatch with other measurements that a
|
|||
|
||||
If ``sessionLength`` is ``-1``, the monotonic clock is not working.
|
||||
|
||||
processes
|
||||
---------
|
||||
This section contains per-process data.
|
||||
|
||||
Structure::
|
||||
|
||||
"processes" : {
|
||||
... other processes ...
|
||||
"parent": {
|
||||
scalars: {...},
|
||||
},
|
||||
}
|
||||
|
||||
scalars
|
||||
~~~~~~~
|
||||
This section contains the :doc:`scalars` that are valid for the current platform. Scalars are not created nor submitted if no data was added to them, and are only reported with subsession pings. Scalar data is only currently reported for the main process. Their type and format is described by the ``Scalars.yaml`` file. Its most recent version is available `here <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/Scalars.yaml>`_. The ``info.revision`` field indicates the revision of the file that describes the reported scalars.
|
||||
|
||||
childPayloads
|
||||
-------------
|
||||
The Telemetry payloads sent by child processes, recorded on child process shutdown (event ``content-child-shutdown`` observed) and whenever ``TelemetrySession.requestChildPayloads()`` is called (currently only used in tests). They are reduced session payloads, only available with e10s. Among some other things, they don't report addon details, addon histograms or UI Telemetry.
|
||||
|
@ -183,10 +200,6 @@ This section contains the keyed histograms available for the current platform.
|
|||
|
||||
As of Firefox 48, this section does not contain empty keyed histograms anymore.
|
||||
|
||||
scalars
|
||||
----------
|
||||
This section contains the :doc:`scalars` that are valid for the current platform. Scalars are not created nor submitted if no data was added to them, and are only reported with subsession pings. Their type and format is described by the ``Scalars.yaml`` file. Its most recent version is available `here <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/Scalars.yaml>`_. The ``info.revision`` field indicates the revision of the file that describes the reported scalars.
|
||||
|
||||
threadHangStats
|
||||
---------------
|
||||
Contains the statistics about the hangs in main and background threads. Note that hangs in this section capture the [C++ pseudostack](https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Profiling_with_the_Built-in_Profiler#Native_stack_vs._Pseudo_stack) and an incomplete JS stack, which is not 100% precise.
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
&aboutTelemetry.raw;<br />
|
||||
</div>
|
||||
<div id="current-ping-picker">
|
||||
<input id="show-subsession-data" type="checkbox" />&aboutTelemetry.showSubsessionData;
|
||||
<input id="show-subsession-data" type="checkbox" checked="checked" />&aboutTelemetry.showSubsessionData;
|
||||
</div>
|
||||
<div id="archived-ping-picker" class="hidden">
|
||||
&aboutTelemetry.choosePing;<br />
|
||||
|
|
|
@ -93,13 +93,22 @@ this.XPathGenerator = {
|
|||
get restorableFormNodes() {
|
||||
// for a comprehensive list of all available <INPUT> types see
|
||||
// http://mxr.mozilla.org/mozilla-central/search?string=kInputTypeTable
|
||||
let ignoreTypes = ["password", "hidden", "button", "image", "submit", "reset"];
|
||||
let ignoreInputs = new Map([
|
||||
["type", ["password", "hidden", "button", "image", "submit", "reset"]],
|
||||
["autocomplete", ["off"]]
|
||||
]);
|
||||
// XXXzeniko work-around until lower-case has been implemented (bug 398389)
|
||||
let toLowerCase = '"ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"';
|
||||
let ignore = "not(translate(@type, " + toLowerCase + ")='" +
|
||||
ignoreTypes.join("' or translate(@type, " + toLowerCase + ")='") + "')";
|
||||
let formNodesXPath = "//textarea|//select|//xhtml:textarea|//xhtml:select|" +
|
||||
"//input[" + ignore + "]|//xhtml:input[" + ignore + "]";
|
||||
let ignores = [];
|
||||
for (let [attrName, attrValues] of ignoreInputs) {
|
||||
for (let attrValue of attrValues)
|
||||
ignores.push(`translate(@${attrName}, ${toLowerCase})='${attrValue}'`);
|
||||
}
|
||||
let ignore = `not(${ignores.join(" or ")})`;
|
||||
|
||||
let formNodesXPath = `//textarea[${ignore}]|//xhtml:textarea[${ignore}]|` +
|
||||
`//select[${ignore}]|//xhtml:select[${ignore}]|` +
|
||||
`//input[${ignore}]|//xhtml:input[${ignore}]`;
|
||||
|
||||
// Special case for about:config's search field.
|
||||
formNodesXPath += '|/xul:window[@id="config"]//xul:textbox[@id="textbox"]';
|
||||
|
|