Merge m-c to inbound.

This commit is contained in:
Ryan VanderMeulen 2014-02-14 08:57:13 -05:00
commit a7d964785f
79 changed files with 1107 additions and 193 deletions

View File

@ -12,7 +12,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ade88673d00414c3177f7444543b2fa01324708e"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eda08beb3ba9a159843c70ffde0f9660ec351eb9"/>

View File

@ -11,7 +11,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ade88673d00414c3177f7444543b2fa01324708e"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>

View File

@ -12,7 +12,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ade88673d00414c3177f7444543b2fa01324708e"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eda08beb3ba9a159843c70ffde0f9660ec351eb9"/>

View File

@ -1,4 +1,4 @@
{
"revision": "3d73a644a8ecb4a4c426584956d0c7f6b05e4cdb",
"revision": "4f00231c5cc538139e63bee1a7ed8456f6cefed7",
"repo_path": "/integration/gaia-central"
}

View File

@ -11,7 +11,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ade88673d00414c3177f7444543b2fa01324708e"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>

View File

@ -10,7 +10,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ade88673d00414c3177f7444543b2fa01324708e"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>

View File

@ -12,7 +12,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ade88673d00414c3177f7444543b2fa01324708e"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>

View File

@ -11,7 +11,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ade88673d00414c3177f7444543b2fa01324708e"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>

View File

@ -11,7 +11,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ade88673d00414c3177f7444543b2fa01324708e"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>

View File

@ -11,7 +11,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ade88673d00414c3177f7444543b2fa01324708e"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8ac5dc6b76709e742cb923c58d1f4b415b3e08fb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>

View File

@ -226,8 +226,16 @@ let wrapper = {
// Button onclick handlers
function handleOldSync() {
// we just want to navigate the current tab to the new location...
window.location = Services.urlFormatter.formatURLPref("app.support.baseURL") + "old-sync";
let chromeWin = window
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.QueryInterface(Ci.nsIDOMChromeWindow);
let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "old-sync";
chromeWin.switchToTabHavingURI(url, true);
}
function getStarted() {

View File

@ -104,14 +104,18 @@ let gSyncUI = {
if (!gBrowser)
return;
let button = document.getElementById("sync-button");
if (!button)
return;
let syncButton = document.getElementById("sync-button");
let panelHorizontalButton = document.getElementById("PanelUI-fxa-status");
[syncButton, panelHorizontalButton].forEach(function(button) {
if (!button)
return;
button.removeAttribute("status");
});
if (needsSetup && syncButton)
syncButton.removeAttribute("tooltiptext");
button.removeAttribute("status");
this._updateLastSyncTime();
if (needsSetup)
button.removeAttribute("tooltiptext");
},
@ -120,11 +124,12 @@ let gSyncUI = {
if (!gBrowser)
return;
let button = document.getElementById("sync-button");
if (!button)
return;
button.setAttribute("status", "active");
["sync-button", "PanelUI-fxa-status"].forEach(function(id) {
let button = document.getElementById(id);
if (!button)
return;
button.setAttribute("status", "active");
});
},
onSyncDelay: function SUI_onSyncDelay() {
@ -161,6 +166,11 @@ let gSyncUI = {
this.updateUI();
return;
}
// if we are still waiting for the identity manager to initialize, don't show errors
if (Weave.Status.login == Weave.LOGIN_FAILED_NOT_READY) {
this.updateUI();
return;
}
let title = this._stringBundle.GetStringFromName("error.login.title");

View File

@ -389,7 +389,7 @@
<!-- Sync Panel -->
<panel id="sync-start-panel" class="sync-panel" type="arrow" hidden="true"
noautofocus="true" level="top" onclick="this.hidePopup();"
noautofocus="true" onclick="this.hidePopup();"
flip="slide">
<hbox class="sync-panel-outer">
<image class="sync-panel-icon"/>
@ -409,7 +409,7 @@
<!-- Sync Error Panel -->
<panel id="sync-error-panel" class="sync-panel" type="arrow" hidden="true"
noautofocus="true" level="top" onclick="this.hidePopup();"
noautofocus="true" onclick="this.hidePopup();"
flip="slide">
<hbox class="sync-panel-outer">
<image class="sync-panel-icon"/>

View File

@ -269,6 +269,8 @@ skip-if = true # browser_drag.js is disabled, as it needs to be updated for the
[browser_locationBarCommand.js]
skip-if = os == "linux" # Intermittent failures, bug 917535
[browser_locationBarExternalLoad.js]
[browser_menuButtonFitts.js]
skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (bug 969376)
[browser_middleMouse_inherit.js]
[browser_minimize.js]
[browser_mixedcontent_securityflags.js]

View File

@ -0,0 +1,32 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
function test () {
waitForExplicitFinish();
window.maximize();
// Find where the nav-bar is vertically.
var navBar = document.getElementById("nav-bar");
var boundingRect = navBar.getBoundingClientRect();
var yPixel = boundingRect.top + Math.floor(boundingRect.height / 2);
var xPixel = boundingRect.width - 1; // Use the last pixel of the screen since it is maximized.
function onPopupHidden() {
PanelUI.panel.removeEventListener("popuphidden", onPopupHidden);
window.restore();
finish();
}
function onPopupShown() {
PanelUI.panel.removeEventListener("popupshown", onPopupShown);
ok(true, "Clicking at the far edge of the window opened the menu popup.");
PanelUI.panel.addEventListener("popuphidden", onPopupHidden);
PanelUI.hide();
}
registerCleanupFunction(function() {
PanelUI.panel.removeEventListener("popupshown", onPopupShown);
PanelUI.panel.removeEventListener("popuphidden", onPopupHidden);
});
PanelUI.panel.addEventListener("popupshown", onPopupShown);
EventUtils.synthesizeMouseAtPoint(xPixel, yPixel, {}, window);
}

View File

@ -30,13 +30,11 @@
<menupopup id="customization-toolbar-menu" onpopupshowing="onViewToolbarsPopupShowing(event)"/>
</button>
<spacer flex="1"/>
<label id="customization-undo-reset"
hidden="true"
onclick="gCustomizeMode.undoReset();"
onkeypress="gCustomizeMode.undoReset();"
class="text-link">
&undoCmd.label;
</label>
<button id="customization-undo-reset"
class="customizationmode-button"
hidden="true"
oncommand="gCustomizeMode.undoReset();"
label="&undoCmd.label;"/>
<button id="customization-reset-button" oncommand="gCustomizeMode.reset();" label="&customizeMode.restoreDefaults;" class="customizationmode-button"/>
</hbox>
</vbox>

View File

@ -4,8 +4,6 @@
"use strict";
Cu.import("resource://gre/modules/Promise.jsm");
const isOSX = (Services.appinfo.OS === "Darwin");
// Right-click on the home button should

View File

@ -4,6 +4,19 @@
"use strict";
let openUILinkInCalled = false;
let expectOpenUILinkInCall = false;
this.originalOpenUILinkIn = openUILinkIn;
openUILinkIn = (aUrl, aWhichTab) => {
is(aUrl, Services.search.defaultEngine.searchForm, "Search page should be requested to open.");
is(aWhichTab, "current", "Should use the current tab for the search page.");
openUILinkInCalled = true;
if (!expectOpenUILinkInCall) {
ok(false, "OpenUILink in was called when it shouldn't have been.");
}
};
logActiveElement();
// Ctrl+K should open the menu panel and focus the search bar if the search bar is in the panel.
add_task(function() {
let searchbar = document.getElementById("searchbar");
@ -88,28 +101,29 @@ add_task(function() {
// Ctrl+K should open the search page if the search bar has been customized out.
add_task(function() {
this.originalOpenUILinkIn = openUILinkIn;
try {
expectOpenUILinkInCall = true;
CustomizableUI.removeWidgetFromArea("search-container");
let placement = CustomizableUI.getPlacementOfWidget("search-container");
is(placement, null, "Search container should be in palette");
let openUILinkInCalled = false;
openUILinkIn = (aUrl, aWhichTab) => {
is(aUrl, Services.search.defaultEngine.searchForm, "Search page should be requested to open.");
is(aWhichTab, "current", "Should use the current tab for the search page.");
openUILinkInCalled = true;
};
openUILinkInCalled = false;
sendWebSearchKeyCommand();
yield waitForCondition(function() openUILinkInCalled);
ok(openUILinkInCalled, "The search page should have been opened.")
expectOpenUILinkInCall = false;
} catch (e) {
ok(false, e);
}
openUILinkIn = this.originalOpenUILinkIn;
CustomizableUI.reset();
});
registerCleanupFunction(function() {
openUILinkIn = this.originalOpenUILinkIn;
delete this.originalOpenUILinkIn;
});
function sendWebSearchKeyCommand() {
if (Services.appinfo.OS === "Darwin")
EventUtils.synthesizeKey("k", { accelKey: true });
@ -119,7 +133,10 @@ function sendWebSearchKeyCommand() {
function logActiveElement() {
let element = document.activeElement;
info("Active element: " + element ?
element + " (" + element.localName + "#" + element.id + "." + [...element.classList].join(".") + ")" :
"null");
let str = "";
while (element && element.parentNode) {
str = " (" + element.localName + "#" + element.id + "." + [...element.classList].join(".") + ") >" + str;
element = element.parentNode;
}
info("Active element: " + element ? str : "null");
}

View File

@ -5,7 +5,6 @@
"use strict";
let Preferences = Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
Cu.import("resource://gre/modules/Promise.jsm");
let tmp = {};
Cu.import("resource://gre/modules/FxAccounts.jsm", tmp);

View File

@ -175,6 +175,8 @@ var BrowserUI = {
Util.dumpLn("Exception in delay load module:", ex.message);
}
BrowserUI._initFirstRunContent();
// check for left over crash reports and submit them if found.
BrowserUI.startupCrashCheck();
@ -1216,6 +1218,24 @@ var BrowserUI = {
prefsClearButton.disabled = false;
},
_initFirstRunContent: function () {
let dismissed = Services.prefs.getBoolPref("browser.firstrun-content.dismissed");
let firstRunCount = Services.prefs.getIntPref("browser.firstrun.count");
if (!dismissed && firstRunCount > 0) {
document.loadOverlay("chrome://browser/content/FirstRunContentOverlay.xul", null);
}
},
firstRunContentDismiss: function() {
let firstRunElements = Elements.stack.querySelectorAll(".firstrun-content");
for (let node of firstRunElements) {
node.parentNode.removeChild(node);
}
Services.prefs.setBoolPref("browser.firstrun-content.dismissed", true);
},
};
var PanelUI = {

View File

@ -0,0 +1,68 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://browser/skin/firstruncontent.css" type="text/css"?>
<!-- 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/. -->
<!DOCTYPE window [
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
%browserDTD;
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%brandDTD;
]>
<overlay id="firstruncontent"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<stack id="stack">
<box id="firstrun-bg-pane" insertafter="page" class="firstrun-content"></box>
<box id="instruction-tabs" class="firstrun-content">
<vbox class="instruction-content-container" align="center">
<image class="instruction-arrow arrow-top" />
<label class="instruction-label">&firstRunTabs.label;</label>
</vbox>
</box>
<box id="instruction-back" class="firstrun-content">
<vbox class="instruction-content-container" align="start">
<image class="instruction-arrow arrow-back" />
<label class="instruction-label">&firstRunBack.label;</label>
</vbox>
</box>
<box id="instruction-plus" class="firstrun-content">
<vbox class="instruction-content-container" align="end">
<image class="instruction-arrow arrow-forward" />
<label class="instruction-label">&firstRunNewTab.label;</label>
</vbox>
</box>
<appbar id="navbar">
<box id="instruction-star" class="firstrun-content">
<hbox class="instruction-content-container">
<label class="instruction-label">&firstRunStar.label;</label>
<image class="instruction-arrow arrow-down" />
</hbox>
</box>
<box id="instruction-pin" class="firstrun-content">
<vbox class="instruction-content-container" align="end">
<label class="instruction-label">&firstRunPin.label;</label>
<image class="instruction-arrow arrow-down-reverse" />
</vbox>
</box>
<box id="firstrun-gotit" class="firstrun-content">
<button class="firstrun-button" label="&firstRunGotIt.label;" oncommand="BrowserUI.firstRunContentDismiss()" />
</box>
</appbar>
</stack>
</overlay>

View File

@ -99,6 +99,7 @@ chrome.jar:
content/HistoryView.js (content/startui/HistoryView.js)
content/TopSitesView.js (content/startui/TopSitesView.js)
content/FirstRunOverlay.xul (content/startui/FirstRunOverlay.xul)
content/FirstRunContentOverlay.xul (content/startui/FirstRunContentOverlay.xul)
#ifdef MOZ_SERVICES_SYNC
content/RemoteTabsView.js (content/startui/RemoteTabsView.js)
#endif

View File

@ -138,12 +138,9 @@
firstRunStar.label,
firstRunPin.label,
firstRunGotIt.label )
These strings appear as a content overlay the first few times a page
These strings appear as a content overlay the first time a page
is visited. Each one has an arrow pointing toward the feature it
references. The code to display these strings is not enabled yet,
but will be soon. For now, you can see this mockup for an example
of how they are used:
https://bug949213.bugzilla.mozilla.org/attachment.cgi?id=8363973
references.
-->
<!ENTITY firstRunBack.label "Tap to go back to the previous page">
<!ENTITY firstRunNewTab.label "Add a new tab to explore a new site">

View File

@ -136,6 +136,8 @@ pref("browser.display.startUI.maxresults", 16);
// Number of times to display firstrun instructions on new tab page
pref("browser.firstrun.count", 3);
// Has the content first run been dismissed
pref("browser.firstrun-content.dismissed", false);
// Backspace and Shift+Backspace behavior
// 0 goes Back/Forward

View File

@ -0,0 +1,205 @@
/* 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/. */
%filter substitution
%include defines.inc
/* Disable firstrun in some cases */
#stack[startpage] .firstrun-content {
display: none;
}
@media (max-width: 900px) {
.firstrun-content {
display: none;
}
}
/* bg transparent pane --------------- */
#firstrun-bg-pane {
position: absolute;
background-color: rgba(0, 0, 0, .8);
left: 0;
right: 0;
top: 0;
bottom: 0;
}
/* Got it button ---------------------- */
#firstrun-gotit {
position: fixed;
bottom: calc(@toolbar_height@ + 64px);
left: 64px;
}
#firstrun-gotit:-moz-locale-dir(rtl) {
left: auto;
right: 64px;
}
.firstrun-button {
padding: 15px 45px;
font-family: "Segoe UI", sans-serif;
font-size: 25px;
background-image: -moz-linear-gradient(0deg, rgb(255, 128, 0) 0%, rgb(255, 149, 0) 100%);
border: 0;
color: #FFF;
}
/* Instructions arrows ---------------- */
.instruction-arrow {
width: 76px;
height: 76px;
background-repeat: no-repeat;
}
.instruction-arrow.arrow-top,
.instruction-arrow.arrow-down,
.instruction-arrow.arrow-down-reverse {
background-image: url("chrome://browser/skin/images/arrow-top-light.png");
}
.instruction-arrow.arrow-down,
.instruction-arrow.arrow-down-reverse:-moz-locale-dir(rtl) {
transform: rotate(180deg);
}
.instruction-arrow.arrow-down-reverse,
.instruction-arrow.arrow-down:-moz-locale-dir(rtl) {
transform: rotate(180deg) scaleX(-1);
}
.instruction-arrow.arrow-back,
.instruction-arrow.arrow-forward {
background-image: url("chrome://browser/skin/images/arrow-left-light.png");
}
.instruction-arrow.arrow-forward,
.instruction-arrow.arrow-back:-moz-locale-dir(rtl) {
transform: rotate(180deg) scaleY(-1);
}
.instruction-arrow.arrow-forward:-moz-locale-dir(rtl) {
transform: none;
}
/* Instructions text ------------------ */
.instruction-content-container {
width: 380px;
vertical-align: bottom;
}
#instruction-back .instruction-content-container,
#instruction-plus .instruction-content-container {
width: 320px;
}
.instruction-label {
font-size: 18px;
color: #BBB;
line-height: 22px;
}
#instruction-tabs {
position: fixed;
top: 10px;
left: 50%;
transform: translateX(-50%);
}
#instruction-back {
position: fixed;
top: 50%;
left: 100px;
}
#instruction-back:-moz-locale-dir(rtl) {
left: auto;
right: 100px;
}
#instruction-plus {
position: fixed;
top: 50%;
right: 100px;
}
#instruction-plus:-moz-locale-dir(rtl) {
right: auto;
left: 100px;
}
#instruction-star {
position: fixed;
bottom: @toolbar_height@;
right: 145px;
}
#instruction-star:-moz-locale-dir(rtl) {
right: auto;
left: 145px;
}
#instruction-star .instruction-label {
max-width: 250px;
}
#instruction-star .instruction-content-container:-moz-locale-dir(rtl) {
-moz-box-align: start;
vertical-align: top;
}
#instruction-pin {
position: fixed;
bottom: @toolbar_height@;
right: 10px;
}
#instruction-pin:-moz-locale-dir(rtl) {
right: auto;
left: 10px;
}
#instruction-pin .instruction-label {
max-width: 250px;
margin-bottom: 20px;
}
#instruction-pin .instruction-arrow {
-moz-margin-end: 65px;
}
/* Higher resolution images ---------------------- */
@media (min-resolution: @min_res_140pc@) {
/* Load 140% image when scaled by 140% */
.instruction-arrow.arrow-top,
.instruction-arrow.arrow-down,
.instruction-arrow.arrow-down-reverse {
background-image: url("chrome://browser/skin/images/arrow-top-light@1.4x.png");
}
.instruction-arrow.arrow-back,
.instruction-arrow.arrow-forward {
background-image: url("chrome://browser/skin/images/arrow-left-light@1.4x.png");
}
}
@media (min-resolution: @min_res_180pc@) {
/* Load 180% image when scaled by 180% */
.instruction-arrow.arrow-top,
.instruction-arrow.arrow-down,
.instruction-arrow.arrow-down-reverse {
background-image: url("chrome://browser/skin/images/arrow-top-light@1.8x.png");
}
.instruction-arrow.arrow-back,
.instruction-arrow.arrow-forward {
background-image: url("chrome://browser/skin/images/arrow-left-light@1.8x.png");
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -23,6 +23,7 @@ chrome.jar:
skin/touchcontrols.css (touchcontrols.css)
* skin/netError.css (netError.css)
skin/firstrun.css (firstrun.css)
* skin/firstruncontent.css (firstruncontent.css)
* skin/start.css (start.css)
% override chrome://global/skin/about.css chrome://browser/skin/about.css
% override chrome://global/skin/media/videocontrols.css chrome://browser/skin/touchcontrols.css
@ -160,6 +161,12 @@ chrome.jar:
skin/images/arrow-left.png (images/arrow-left.png)
skin/images/arrow-left@1.4x.png (images/arrow-left@1.4x.png)
skin/images/arrow-left@1.8x.png (images/arrow-left@1.8x.png)
skin/images/arrow-top-light.png (images/arrow-top-light.png)
skin/images/arrow-top-light@1.4x.png (images/arrow-top-light@1.4x.png)
skin/images/arrow-top-light@1.8x.png (images/arrow-top-light@1.8x.png)
skin/images/arrow-left-light.png (images/arrow-left-light.png)
skin/images/arrow-left-light@1.4x.png (images/arrow-left-light@1.4x.png)
skin/images/arrow-left-light@1.8x.png (images/arrow-left-light@1.8x.png)
# AboutConfig specific:
skin/images/textfield.png (images/textfield.png)

View File

@ -591,6 +591,10 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
width: 32px;
}
#nav-bar #PanelUI-menu-button {
-moz-padding-start: 7px;
-moz-padding-end: 5px;
}
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled]):hover > .toolbarbutton-icon,
:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([buttonover]):not([open]):hover > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,

View File

@ -274,6 +274,7 @@ browser.jar:
skin/classic/browser/sync-bg.png
skin/classic/browser/sync-128.png
skin/classic/browser/sync-desktopIcon.png
skin/classic/browser/sync-horizontalbar.png
skin/classic/browser/sync-mobileIcon.png
skin/classic/browser/sync-notification-24.png
skin/classic/browser/syncProgress-menuPanel.png
@ -282,6 +283,7 @@ browser.jar:
skin/classic/browser/syncCommon.css
skin/classic/browser/syncQuota.css
skin/classic/browser/syncProgress.css
skin/classic/browser/syncProgress-horizontalbar.png
#endif
skin/classic/browser/notification-pluginNormal.png (../shared/plugins/notification-pluginNormal.png)
skin/classic/browser/notification-pluginAlert.png (../shared/plugins/notification-pluginAlert.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

View File

@ -507,6 +507,13 @@ toolbar .toolbarbutton-1:not([type="menu-button"]),
-moz-margin-start: 10px;
}
#nav-bar #PanelUI-menu-button {
margin-top: 0;
margin-bottom: 0;
-moz-margin-start: 9px;
-moz-margin-end: 7px;
}
@media not all and (min-resolution: 2dppx) {
%include ../shared/toolbarbuttons.inc.css
%include ../shared/menupanel.inc.css

View File

@ -11,6 +11,14 @@
background-size: 16px, auto;
}
#PanelUI-fxa-status {
list-style-image: url(chrome://browser/skin/sync-horizontalbar@2x.png);
}
#PanelUI-fxa-status[status="active"] {
list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar@2x.png);
}
#PanelUI-customize {
list-style-image: url(chrome://browser/skin/menuPanel-customize@2x.png);
}
@ -27,6 +35,7 @@
list-style-image: url(chrome://browser/skin/menuPanel-exit@2x.png);
}
#PanelUI-fxa-status,
#PanelUI-customize,
#PanelUI-help,
#PanelUI-quit {

View File

@ -385,12 +385,16 @@ browser.jar:
skin/classic/browser/sync-bg.png
skin/classic/browser/sync-128.png
skin/classic/browser/sync-desktopIcon.png
skin/classic/browser/sync-horizontalbar.png
skin/classic/browser/sync-horizontalbar@2x.png
skin/classic/browser/sync-mobileIcon.png
skin/classic/browser/sync-notification-24.png
skin/classic/browser/syncSetup.css
skin/classic/browser/syncCommon.css
skin/classic/browser/syncQuota.css
skin/classic/browser/syncProgress.css
skin/classic/browser/syncProgress-horizontalbar.png
skin/classic/browser/syncProgress-horizontalbar@2x.png
#endif
skin/classic/browser/lion/keyhole-circle.png (keyhole-circle-lion.png)
skin/classic/browser/keyhole-circle@2x.png (keyhole-circle-lion@2x.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -105,13 +105,7 @@
}
#customization-undo-reset {
padding-left: 12px;
padding-right: 12px;
%ifdef XP_MACOSX
padding-top: 6px;
%else
padding-top: 7px;
%endif
-moz-margin-end: 10px;
}
#main-window[customize-entered] #customization-panel-container {

View File

@ -26,10 +26,6 @@
background-repeat: no-repeat;
}
#PanelUI-menu-button {
margin: 0 7px 0 9px;
}
.panel-subviews {
padding: 4px;
background-color: hsla(0,0%,100%,.97);
@ -404,6 +400,17 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
flex: 1;
-moz-padding-start: 15px;
-moz-border-start-style: none;
}
#PanelUI-fxa-status {
list-style-image: url(chrome://browser/skin/sync-horizontalbar.png);
}
#PanelUI-fxa-status[status="active"] {
list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar.png);
}
#PanelUI-customize {
list-style-image: url(chrome://browser/skin/menuPanel-customize.png);
}

View File

@ -496,6 +496,11 @@ menuitem.bookmark-item {
-moz-box-pack: center;
}
#nav-bar #PanelUI-menu-button {
-moz-padding-start: 7px;
-moz-padding-end: 5px;
}
#nav-bar .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button):not(#social-provider-button):not(#PanelUI-menu-button) {
padding-left: 5px;
padding-right: 5px;

View File

@ -303,12 +303,16 @@ browser.jar:
skin/classic/browser/sync-128.png
skin/classic/browser/sync-bg.png
skin/classic/browser/sync-desktopIcon.png
skin/classic/browser/sync-horizontalbar.png
skin/classic/browser/sync-horizontalbar-XPVista7.png
skin/classic/browser/sync-mobileIcon.png
skin/classic/browser/sync-notification-24.png
skin/classic/browser/syncSetup.css
skin/classic/browser/syncCommon.css
skin/classic/browser/syncQuota.css
skin/classic/browser/syncProgress.css
skin/classic/browser/syncProgress-horizontalbar.png
skin/classic/browser/syncProgress-horizontalbar-XPVista7.png
#endif
skin/classic/browser/devtools/tooltip/arrow-horizontal-dark.png (../shared/devtools/tooltip/arrow-horizontal-dark.png)
skin/classic/browser/devtools/tooltip/arrow-horizontal-dark@2x.png (../shared/devtools/tooltip/arrow-horizontal-dark@2x.png)
@ -617,12 +621,16 @@ browser.jar:
skin/classic/aero/browser/sync-128.png
skin/classic/aero/browser/sync-bg.png
skin/classic/aero/browser/sync-desktopIcon.png
skin/classic/aero/browser/sync-horizontalbar.png
skin/classic/aero/browser/sync-horizontalbar-XPVista7.png
skin/classic/aero/browser/sync-mobileIcon.png
skin/classic/aero/browser/sync-notification-24.png
skin/classic/aero/browser/syncSetup.css
skin/classic/aero/browser/syncCommon.css
skin/classic/aero/browser/syncQuota.css
skin/classic/aero/browser/syncProgress.css
skin/classic/aero/browser/syncProgress-horizontalbar.png
skin/classic/aero/browser/syncProgress-horizontalbar-XPVista7.png
#endif
#endif
skin/classic/aero/browser/devtools/tooltip/arrow-horizontal-dark.png (../shared/devtools/tooltip/arrow-horizontal-dark.png)
@ -633,3 +641,6 @@ browser.jar:
skin/classic/aero/browser/devtools/tooltip/arrow-horizontal-light@2x.png (../shared/devtools/tooltip/arrow-horizontal-light@2x.png)
skin/classic/aero/browser/devtools/tooltip/arrow-vertical-light.png (../shared/devtools/tooltip/arrow-vertical-light.png)
skin/classic/aero/browser/devtools/tooltip/arrow-vertical-light@2x.png (../shared/devtools/tooltip/arrow-vertical-light@2x.png)
% override chrome://browser/skin/sync-horizontalbar.png chrome://browser/skin/sync-horizontalbar-XPVista7.png os=WINNT osversion<6.2
% override chrome://browser/skin/syncProgress-horizontalbar.png chrome://browser/skin/syncProgress-horizontalbar-XPVista7.png os=WINNT osversion<6.2

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

View File

@ -26,7 +26,6 @@ SimpleTest.waitForExplicitFinish();
var index = -1;
var todayDate = new Date();
var baseServeURL = "http://mochi.test:8888/tests/dom/downloads/tests/";
var baseDownloadPath = "/mnt/sdcard/downloads/";
var lastKnownCurrentBytes = 0;
function next() {
@ -45,11 +44,12 @@ function next() {
function checkConsistentDownloadAttributes(download) {
var href = document.getElementById("download1").getAttribute("href");
var expectedServeURL = baseServeURL + href;
var expectedDownloadPath = baseDownloadPath + "test.bin";
var destinationRegEx = /test\(?[0-9]*\)?\.bin$/;
// bug 945323: Download path isn't honoring download attribute
todo(download.path === expectedDownloadPath,
"Download path = " + expectedDownloadPath);
ok(destinationRegEx.test(download.path),
"Download path '" + download.path +
"' should match '" + destinationRegEx + "' regexp.");
ok(download.startTime >= todayDate,
"Download start time should be greater than or equal to today");

View File

@ -959,6 +959,8 @@ PExternalHelperAppChild*
ContentChild::AllocPExternalHelperAppChild(const OptionalURIParams& uri,
const nsCString& aMimeContentType,
const nsCString& aContentDisposition,
const uint32_t& aContentDispositionHint,
const nsString& aContentDispositionFilename,
const bool& aForceSave,
const int64_t& aContentLength,
const OptionalURIParams& aReferrer,

View File

@ -144,6 +144,8 @@ public:
const OptionalURIParams& uri,
const nsCString& aMimeContentType,
const nsCString& aContentDisposition,
const uint32_t& aContentDispositionHint,
const nsString& aContentDispositionFilename,
const bool& aForceSave,
const int64_t& aContentLength,
const OptionalURIParams& aReferrer,

View File

@ -2393,6 +2393,8 @@ PExternalHelperAppParent*
ContentParent::AllocPExternalHelperAppParent(const OptionalURIParams& uri,
const nsCString& aMimeContentType,
const nsCString& aContentDisposition,
const uint32_t& aContentDispositionHint,
const nsString& aContentDispositionFilename,
const bool& aForceSave,
const int64_t& aContentLength,
const OptionalURIParams& aReferrer,
@ -2400,7 +2402,14 @@ ContentParent::AllocPExternalHelperAppParent(const OptionalURIParams& uri,
{
ExternalHelperAppParent *parent = new ExternalHelperAppParent(uri, aContentLength);
parent->AddRef();
parent->Init(this, aMimeContentType, aContentDisposition, aForceSave, aReferrer, aBrowser);
parent->Init(this,
aMimeContentType,
aContentDisposition,
aContentDispositionHint,
aContentDispositionFilename,
aForceSave,
aReferrer,
aBrowser);
return parent;
}

View File

@ -367,6 +367,8 @@ private:
const OptionalURIParams& aUri,
const nsCString& aMimeContentType,
const nsCString& aContentDisposition,
const uint32_t& aContentDispositionHint,
const nsString& aContentDispositionFilename,
const bool& aForceSave,
const int64_t& aContentLength,
const OptionalURIParams& aReferrer,

View File

@ -422,9 +422,14 @@ parent:
CloseAlert(nsString name, Principal principal);
PExternalHelperApp(OptionalURIParams uri, nsCString aMimeContentType,
nsCString aContentDisposition, bool aForceSave,
int64_t aContentLength, OptionalURIParams aReferrer,
PExternalHelperApp(OptionalURIParams uri,
nsCString aMimeContentType,
nsCString aContentDisposition,
uint32_t aContentDispositionHint,
nsString aContentDispositionFilename,
bool aForceSave,
int64_t aContentLength,
OptionalURIParams aReferrer,
nullable PBrowser aBrowser);
AddGeolocationListener(Principal principal, bool highAccuracy);

View File

@ -576,9 +576,12 @@ sync_java_files = [
'fxa/login/StateFactory.java',
'fxa/login/TokensAndKeysState.java',
'fxa/sync/FxAccountGlobalSession.java',
'fxa/sync/FxAccountSchedulePolicy.java',
'fxa/sync/FxAccountSyncAdapter.java',
'fxa/sync/FxAccountSyncService.java',
'fxa/sync/SchedulePolicy.java',
'sync/AlreadySyncingException.java',
'sync/BackoffHandler.java',
'sync/BadRequiredFieldJSONException.java',
'sync/CollectionKeys.java',
'sync/CommandProcessor.java',
@ -677,6 +680,7 @@ sync_java_files = [
'sync/NonObjectJSONException.java',
'sync/NullClusterURLException.java',
'sync/PersistedMetaGlobal.java',
'sync/PrefsBackoffHandler.java',
'sync/PrefsSource.java',
'sync/receivers/SyncAccountDeletedReceiver.java',
'sync/receivers/SyncAccountDeletedService.java',

View File

@ -0,0 +1,154 @@
/* 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.sync;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.State.Action;
import org.mozilla.gecko.sync.BackoffHandler;
import android.accounts.Account;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Bundle;
public class FxAccountSchedulePolicy implements SchedulePolicy {
private static final String LOG_TAG = "FxAccountSchedulePolicy";
// Our poll intervals are used to trigger automatic background syncs
// in the absence of user activity.
// If we're waiting for the user to click on a verification link, we
// sync very often in order to detect a change in state.
//
// In the case of unverified -> unverified (no transition), this should be
// very close to a single HTTP request (with the SyncAdapter overhead, of
// course, but that's not wildly different from alarm manager overhead).
//
// The /account/status endpoint is HAWK authed by sessionToken, so we still
// have to do some crypto no matter what.
// TODO: only do this for a while...
public static final long POLL_INTERVAL_PENDING_VERIFICATION = 60; // 1 minute.
// If we're in some kind of error state, there's no point trying often.
// This is not the same as a server-imposed backoff, which will be
// reflected dynamically.
public static final long POLL_INTERVAL_ERROR_STATE = 24 * 60 * 60; // 24 hours.
// If we're the only device, just sync a few times a day in case that
// changes.
public static final long POLL_INTERVAL_SINGLE_DEVICE_SEC = 8 * 60 * 60; // 8 hours.
// And if we know there are other devices, let's sync often enough that
// we'll likely be caught up (even if not completely) by the time you
// next use this device.
public static final long POLL_INTERVAL_MULTI_DEVICE_SEC = 30 * 60; // 30 minutes.
// Never sync more frequently than this, unless forced.
public static final long POLL_INTERVAL_MINIMUM_SEC = 45; // 45 seconds.
// This is used solely as an optimization for backoff handling, so it's not
// persisted.
private static volatile long POLL_INTERVAL_CURRENT_SEC = POLL_INTERVAL_SINGLE_DEVICE_SEC;
private final AndroidFxAccount account;
private final Context context;
public FxAccountSchedulePolicy(Context context, AndroidFxAccount account) {
this.account = account;
this.context = context;
}
/**
* Return a millisecond timestamp in the future, offset from the current
* time by the provided amount.
* @param millis the duration by which to delay
* @return a timestamp.
*/
private static long delay(long millis) {
return System.currentTimeMillis() + millis;
}
/**
* Updates the existing system periodic sync interval to the specified duration.
*
* @param intervalSeconds the requested period, which Android will vary by up to 4%.
*/
protected void requestPeriodicSync(final long intervalSeconds) {
final String authority = BrowserContract.AUTHORITY;
final Account account = this.account.getAndroidAccount();
this.context.getContentResolver();
Logger.info(LOG_TAG, "Scheduling periodic sync for " + intervalSeconds + ".");
ContentResolver.addPeriodicSync(account, authority, Bundle.EMPTY, intervalSeconds);
POLL_INTERVAL_CURRENT_SEC = intervalSeconds;
}
@Override
public void onSuccessfulSync(int otherClientsCount) {
// This undoes the change made in observeBackoffMillis -- once we hit backoff we'll
// periodically sync at the backoff duration, but as soon as we succeed we'll switch
// into the client-count-dependent interval.
long interval = (otherClientsCount > 0) ? POLL_INTERVAL_MULTI_DEVICE_SEC : POLL_INTERVAL_SINGLE_DEVICE_SEC;
requestPeriodicSync(interval);
}
@Override
public void onHandleFinal(Action needed) {
switch (needed) {
case NeedsPassword:
case NeedsUpgrade:
requestPeriodicSync(POLL_INTERVAL_ERROR_STATE);
break;
case NeedsVerification:
requestPeriodicSync(POLL_INTERVAL_PENDING_VERIFICATION);
break;
case None:
// No action needed: we'll set the periodic sync interval
// when the sync finishes, via the SessionCallback.
break;
}
}
@Override
public void onUpgradeRequired() {
// TODO: this shouldn't occur in FxA, but when we upgrade we
// need to reduce the interval again.
requestPeriodicSync(POLL_INTERVAL_ERROR_STATE);
}
@Override
public void onUnauthorized() {
// TODO: this shouldn't occur in FxA, but when we fix our credentials
// we need to reduce the interval again.
requestPeriodicSync(POLL_INTERVAL_ERROR_STATE);
}
@Override
public void configureBackoffMillisOnBackoff(BackoffHandler backoffHandler, long backoffMillis, boolean onlyExtend) {
if (onlyExtend) {
backoffHandler.extendEarliestNextRequest(delay(backoffMillis));
} else {
backoffHandler.setEarliestNextRequest(delay(backoffMillis));
}
// Yes, we might be part-way through the interval, in which case the backoff
// code will do its job. But we certainly don't want to reduce the interval
// if we're given a small backoff instruction.
// We'll reset the poll interval next time we sync without a backoff instruction.
if (backoffMillis > (POLL_INTERVAL_CURRENT_SEC * 1000)) {
// Slightly inflate the backoff duration to ensure that a fuzzed
// periodic sync doesn't occur before our backoff has passed. Android
// 19+ default to a 4% fuzz factor.
requestPeriodicSync((long) Math.ceil((1.05 * backoffMillis) / 1000));
}
}
@Override
public void configureBackoffMillisBeforeSyncing(BackoffHandler backoffHandler) {
backoffHandler.setEarliestNextRequest(delay(POLL_INTERVAL_MINIMUM_SEC * 1000));
}
}

View File

@ -5,6 +5,7 @@
package org.mozilla.gecko.fxa.sync;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
@ -31,8 +32,10 @@ import org.mozilla.gecko.fxa.login.Married;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.fxa.login.State.Action;
import org.mozilla.gecko.fxa.login.State.StateLabel;
import org.mozilla.gecko.sync.BackoffHandler;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.PrefsBackoffHandler;
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.Utils;
@ -52,11 +55,13 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SyncResult;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder;
@ -65,6 +70,13 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
public static final int NOTIFICATION_ID = LOG_TAG.hashCode();
// Tracks the last seen storage hostname for backoff purposes.
private static final String PREF_BACKOFF_STORAGE_HOST = "backoffStorageHost";
// Used to do cheap in-memory rate limiting.
private static final int MINIMUM_SYNC_DELAY_MILLIS = 5000;
private volatile long lastSyncRealtimeMillis = 0L;
protected final ExecutorService executor;
public FxAccountSyncAdapter(Context context, boolean autoInitialize) {
@ -163,34 +175,57 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
setSyncResultSoftError();
latch.countDown();
}
public void postponeSync(long millis) {
if (millis <= 0) {
Logger.debug(LOG_TAG, "Asked to postpone sync, but zero delay. Short-circuiting.");
} else {
// delayUntil is broken: https://code.google.com/p/android/issues/detail?id=65669
// So we don't bother doing this. Instead, we rely on the periodic sync
// we schedule, and the backoff handler for the rest.
/*
Logger.warn(LOG_TAG, "Postponing sync by " + millis + "ms.");
syncResult.delayUntil = millis / 1000;
*/
}
setSyncResultSoftError();
latch.countDown();
}
}
/**
* A trivial global session callback that ignores backoff requests, upgrades,
* and authorization errors. It simply waits until the sync completes.
*/
protected static class SessionCallback implements BaseGlobalSessionCallback {
protected final SyncDelegate syncDelegate;
protected final SchedulePolicy schedulePolicy;
protected volatile BackoffHandler storageBackoffHandler;
public SessionCallback(SyncDelegate syncDelegate) {
public SessionCallback(SyncDelegate syncDelegate, SchedulePolicy schedulePolicy) {
this.syncDelegate = syncDelegate;
this.schedulePolicy = schedulePolicy;
}
public void setBackoffHandler(BackoffHandler backoffHandler) {
this.storageBackoffHandler = backoffHandler;
}
@Override
public boolean shouldBackOff() {
return false;
public boolean shouldBackOffStorage() {
return storageBackoffHandler.delayMilliseconds() > 0;
}
@Override
public void requestBackoff(long backoff) {
public void requestBackoff(long backoffMillis) {
final boolean onlyExtend = true; // Because we trust what the storage server says.
schedulePolicy.configureBackoffMillisOnBackoff(storageBackoffHandler, backoffMillis, onlyExtend);
}
@Override
public void informUpgradeRequiredResponse(GlobalSession session) {
schedulePolicy.onUpgradeRequired();
}
@Override
public void informUnauthorizedResponse(GlobalSession globalSession, URI oldClusterURL) {
schedulePolicy.onUnauthorized();
}
@Override
@ -200,35 +235,111 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
@Override
public void handleSuccess(GlobalSession globalSession) {
Logger.info(LOG_TAG, "Global session succeeded.");
syncDelegate.handleSuccess();
// Get the number of clients, so we can schedule the sync interval accordingly.
try {
int otherClientsCount = globalSession.getClientsDelegate().getClientsCount();
Logger.debug(LOG_TAG, "" + otherClientsCount + " other client(s).");
this.schedulePolicy.onSuccessfulSync(otherClientsCount);
} finally {
// Continue with the usual success flow.
syncDelegate.handleSuccess();
}
}
@Override
public void handleError(GlobalSession globalSession, Exception e) {
Logger.warn(LOG_TAG, "Global session failed."); // Exception will be dumped by delegate below.
syncDelegate.handleError(e);
// TODO: should we reduce the periodic sync interval?
}
@Override
public void handleAborted(GlobalSession globalSession, String reason) {
Logger.warn(LOG_TAG, "Global session aborted: " + reason);
syncDelegate.handleError(null);
// TODO: should we reduce the periodic sync interval?
}
};
/**
* Return true if the provided {@link BackoffHandler} isn't reporting that we're in
* a backoff state, or the provided {@link Bundle} contains flags that indicate
* we should force a sync.
*/
private boolean shouldPerformSync(final BackoffHandler backoffHandler, final String kind, final Bundle extras) {
final long delay = backoffHandler.delayMilliseconds();
if (delay <= 0) {
return true;
}
if (extras == null) {
return false;
}
final boolean forced = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
if (forced) {
Logger.info(LOG_TAG, "Forced sync (" + kind + "): overruling remaining backoff of " + delay + "ms.");
} else {
Logger.info(LOG_TAG, "Not syncing (" + kind + "): must wait another " + delay + "ms.");
}
return forced;
}
protected void syncWithAssertion(final String audience,
final String assertion,
URI tokenServerEndpointURI,
final URI tokenServerEndpointURI,
final BackoffHandler tokenBackoffHandler,
final SharedPreferences sharedPrefs,
final KeyBundle syncKeyBundle,
final String clientState,
final BaseGlobalSessionCallback callback) {
TokenServerClient tokenServerclient = new TokenServerClient(tokenServerEndpointURI, executor);
tokenServerclient.getTokenFromBrowserIDAssertion(assertion, true, clientState, new TokenServerClientDelegate() {
final SessionCallback callback,
final Bundle extras) {
final TokenServerClientDelegate delegate = new TokenServerClientDelegate() {
private boolean didReceiveBackoff = false;
@Override
public void handleSuccess(final TokenServerToken token) {
FxAccountConstants.pii(LOG_TAG, "Got token! uid is " + token.uid + " and endpoint is " + token.endpoint + ".");
if (!didReceiveBackoff) {
// We must be OK to touch this token server.
tokenBackoffHandler.setEarliestNextRequest(0L);
}
final URI storageServerURI;
try {
storageServerURI = new URI(token.endpoint);
} catch (URISyntaxException e) {
handleError(e);
return;
}
final String storageHostname = storageServerURI.getHost();
// We back off on a per-host basis. When we have an endpoint URI from a token, we
// can check on the backoff status for that host.
// If we're supposed to be backing off, we abort the not-yet-started session.
final BackoffHandler storageBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "sync.storage");
callback.setBackoffHandler(storageBackoffHandler);
String lastStorageHost = sharedPrefs.getString(PREF_BACKOFF_STORAGE_HOST, null);
final boolean storageHostIsUnchanged = lastStorageHost != null &&
lastStorageHost.equalsIgnoreCase(storageHostname);
if (storageHostIsUnchanged) {
Logger.debug(LOG_TAG, "Storage host is unchanged.");
if (!shouldPerformSync(storageBackoffHandler, "storage", extras)) {
Logger.info(LOG_TAG, "Not syncing: storage server requested backoff.");
callback.handleAborted(null, "Storage backoff");
return;
}
} else {
Logger.debug(LOG_TAG, "Received new storage host.");
}
// Invalidate the previous backoff, because our storage host has changed,
// or we never had one at all, or we're OK to sync.
storageBackoffHandler.setEarliestNextRequest(0L);
FxAccountGlobalSession globalSession = null;
try {
ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs);
@ -237,15 +348,13 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
// skew adjustment that the HawkAuthHeaderProvider uses to adjust its
// timestamps. Eventually we might want this to adapt within the scope of a
// global session.
final SkewHandler tokenServerSkewHandler = SkewHandler.getSkewHandlerFromEndpointString(token.endpoint);
final SkewHandler tokenServerSkewHandler = SkewHandler.getSkewHandlerForHostname(storageHostname);
final long tokenServerSkew = tokenServerSkewHandler.getSkewInSeconds();
AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), false, tokenServerSkew);
final AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), false, tokenServerSkew);
final Context context = getContext();
SyncConfiguration syncConfig = new SyncConfiguration(token.uid, authHeaderProvider, sharedPrefs, syncKeyBundle);
final SyncConfiguration syncConfig = new SyncConfiguration(token.uid, authHeaderProvider, sharedPrefs, syncKeyBundle);
// EXTRAS
final Bundle extras = Bundle.EMPTY;
globalSession = new FxAccountGlobalSession(token.endpoint, syncConfig, callback, context, extras, clientsDataDelegate);
globalSession.start();
} catch (Exception e) {
@ -268,8 +377,22 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
@Override
public void handleBackoff(int backoffSeconds) {
// This is the token server telling us to back off.
Logger.info(LOG_TAG, "Token server requesting backoff of " + backoffSeconds + "s. Backoff handler: " + tokenBackoffHandler);
didReceiveBackoff = true;
// If we've already stored a backoff, overrule it: we only use the server
// value for token server scheduling.
tokenBackoffHandler.setEarliestNextRequest(delay(backoffSeconds * 1000));
}
});
private long delay(long delay) {
return System.currentTimeMillis() + delay;
}
};
TokenServerClient tokenServerclient = new TokenServerClient(tokenServerEndpointURI, executor);
tokenServerclient.getTokenFromBrowserIDAssertion(assertion, true, clientState, delegate);
}
protected static void showNotification(Context context, State state) {
@ -312,6 +435,13 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
Logger.resetLogging();
if (this.lastSyncRealtimeMillis > 0L &&
(this.lastSyncRealtimeMillis + MINIMUM_SYNC_DELAY_MILLIS) > SystemClock.elapsedRealtime()) {
Logger.info(LOG_TAG, "Not syncing FxAccount " + Utils.obfuscateEmail(account.name) +
": minimum interval not met.");
return;
}
Logger.info(LOG_TAG, "Syncing FxAccount" +
" account named like " + Utils.obfuscateEmail(account.name) +
" for authority " + authority +
@ -324,10 +454,10 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
}
final CountDownLatch latch = new CountDownLatch(1);
final SyncDelegate syncDelegate = new SyncDelegate(getContext(), latch, syncResult, fxAccount);
final SyncDelegate syncDelegate = new SyncDelegate(context, latch, syncResult, fxAccount);
try {
State state;
final State state;
try {
state = fxAccount.getState();
} catch (Exception e) {
@ -335,9 +465,22 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
return;
}
// This will be the same chunk of SharedPreferences that GlobalSession/SyncConfiguration will later create.
// This will be the same chunk of SharedPreferences that we pass through to GlobalSession/SyncConfiguration.
final SharedPreferences sharedPrefs = fxAccount.getSyncPrefs();
// Check for a backoff right here.
final BackoffHandler schedulerBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "scheduler");
if (!shouldPerformSync(schedulerBackoffHandler, "scheduler", extras)) {
Logger.info(LOG_TAG, "Not syncing (scheduler).");
syncDelegate.postponeSync(schedulerBackoffHandler.delayMilliseconds());
return;
}
final SchedulePolicy schedulePolicy = new FxAccountSchedulePolicy(context, fxAccount);
// Set a small scheduled 'backoff' to rate-limit the next sync.
schedulePolicy.configureBackoffMillisBeforeSyncing(schedulerBackoffHandler);
final String audience = fxAccount.getAudience();
final String authServerEndpoint = fxAccount.getAccountServerURI();
final String tokenServerEndpoint = fxAccount.getTokenServerURI();
@ -372,25 +515,59 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
Logger.info(LOG_TAG, "handleTransition: " + transition + " to " + state.getStateLabel());
}
private boolean shouldRequestToken(final BackoffHandler tokenBackoffHandler, final Bundle extras) {
return shouldPerformSync(tokenBackoffHandler, "token", extras);
}
@Override
public void handleFinal(State state) {
Logger.info(LOG_TAG, "handleFinal: in " + state.getStateLabel());
fxAccount.setState(state);
schedulePolicy.onHandleFinal(state.getNeededAction());
try {
if (state.getStateLabel() != StateLabel.Married) {
syncDelegate.handleCannotSync(state);
return;
}
Married married = (Married) state;
final long now = System.currentTimeMillis();
final Married married = (Married) state;
SkewHandler skewHandler = SkewHandler.getSkewHandlerFromEndpointString(tokenServerEndpoint);
String assertion = married.generateAssertion(audience, JSONWebTokenUtils.DEFAULT_ASSERTION_ISSUER,
now + skewHandler.getSkewInMillis(),
this.getAssertionDurationInMilliseconds());
final BaseGlobalSessionCallback sessionCallback = new SessionCallback(syncDelegate);
syncWithAssertion(audience, assertion, tokenServerEndpointURI, sharedPrefs, married.getSyncKeyBundle(), married.getClientState(), sessionCallback);
final long now = System.currentTimeMillis();
final long issuedAtMillis = now + skewHandler.getSkewInMillis();
final long assertionDurationMillis = this.getAssertionDurationInMilliseconds();
final String assertion = married.generateAssertion(audience, JSONWebTokenUtils.DEFAULT_ASSERTION_ISSUER, issuedAtMillis, assertionDurationMillis);
/*
* At this point we're in the correct state to sync, and we're ready to fetch
* a token and do some work.
*
* But first we need to do two things:
* 1. Check to see whether we're in a backoff situation for the token server.
* If we are, but we're not forcing a sync, then we go no further.
* 2. Clear an existing backoff (if we're syncing it doesn't matter, and if
* we're forcing we'll get a new backoff if things are still bad).
*
* Note that we don't check the storage backoff before the token dance: the token
* server tells us which server we're syncing to!
*
* That logic lives in the TokenServerClientDelegate elsewhere in this file.
*/
// Strictly speaking this backoff check could be done prior to walking through
// the login state machine, allowing us to short-circuit sooner.
// We don't expect many token server backoffs, and most users will be sitting
// in the Married state, so instead we simply do this here, once.
final BackoffHandler tokenBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "token");
if (!shouldRequestToken(tokenBackoffHandler, extras)) {
Logger.info(LOG_TAG, "Not syncing (token server).");
syncDelegate.postponeSync(tokenBackoffHandler.delayMilliseconds());
return;
}
final SessionCallback sessionCallback = new SessionCallback(syncDelegate, schedulePolicy);
final KeyBundle syncKeyBundle = married.getSyncKeyBundle();
final String clientState = married.getClientState();
syncWithAssertion(audience, assertion, tokenServerEndpointURI, tokenBackoffHandler, sharedPrefs, syncKeyBundle, clientState, sessionCallback, extras);
} catch (Exception e) {
syncDelegate.handleError(e);
return;
@ -404,7 +581,8 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
syncDelegate.handleError(e);
}
Logger.error(LOG_TAG, "Syncing done.");
Logger.info(LOG_TAG, "Syncing done.");
lastSyncRealtimeMillis = SystemClock.elapsedRealtime();
}
protected void debugAssertion(String audience, String assertion) {

View File

@ -0,0 +1,42 @@
/* 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.sync;
import org.mozilla.gecko.fxa.login.State.Action;
import org.mozilla.gecko.sync.BackoffHandler;
public interface SchedulePolicy {
/**
* Call this with the number of other clients syncing to the account.
*/
public abstract void onSuccessfulSync(int otherClientsCount);
public abstract void onHandleFinal(Action needed);
public abstract void onUpgradeRequired();
public abstract void onUnauthorized();
/**
* Before a sync we typically wish to adjust our backoff policy. This cleans
* the slate prior to encountering a new backoff, and also functions as a rate
* limiter.
*
* The {@link SchedulePolicy} acts as a controller for the {@link BackoffHandler}.
* As a result of calling these two methods, the {@link BackoffHandler} will be
* mutated, and additional side-effects (such as scheduling periodic syncs) can
* occur.
*
* @param backoffHandler the backoff handler to configure.
*/
public abstract void configureBackoffMillisBeforeSyncing(BackoffHandler backoffHandler);
/**
* We received an explicit backoff instruction, typically from a server.
*
* @param onlyExtend
* if <code>true</code>, the backoff handler will be asked to update
* its backoff only if the provided value is greater than the current
* backoff.
*/
public abstract void configureBackoffMillisOnBackoff(BackoffHandler backoffHandler, long backoffMillis, boolean onlyExtend);
}

View File

@ -244,18 +244,17 @@ abstract class HomeFragment extends Fragment {
if (mInReadingList) {
GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Remove", mUrl);
GeckoAppShell.sendEventToGecko(e);
int count = BrowserDB.getReadingListCount(cr);
e = GeckoEvent.createBroadcastEvent("Reader:ListCountUpdated", Integer.toString(count));
GeckoAppShell.sendEventToGecko(e);
}
return null;
}
@Override
public void onPostExecute(Void result) {
int messageId = mInReadingList ? R.string.reading_list_removed : R.string.bookmark_removed;
Toast.makeText(mContext, messageId, Toast.LENGTH_SHORT).show();
// The remove from reading list toast is handled in Reader:Removed,
// so handle only the bookmark removed toast here.
if (!mInReadingList) {
Toast.makeText(mContext, R.string.bookmark_removed, Toast.LENGTH_SHORT).show();
}
}
}

View File

@ -0,0 +1,34 @@
/* 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.sync;
public interface BackoffHandler {
public long getEarliestNextRequest();
/**
* Provide a timestamp in millis before which we shouldn't sync again.
* Overrides any existing value.
*
* @param next
* a timestamp in milliseconds.
*/
public void setEarliestNextRequest(long next);
/**
* Provide a timestamp in millis before which we shouldn't sync again. Only
* change our persisted value if it's later than the existing time.
*
* @param next
* a timestamp in milliseconds.
*/
public void extendEarliestNextRequest(long next);
/**
* Return the number of milliseconds until we're allowed to sync again,
* or 0 if now is fine.
*/
public long delayMilliseconds();
}

View File

@ -316,7 +316,7 @@ public class GlobalSession implements PrefsSource, HttpResponseObserver {
*/
protected void restart() throws AlreadySyncingException {
this.currentState = GlobalSyncStage.Stage.idle;
if (callback.shouldBackOff()) {
if (callback.shouldBackOffStorage()) {
this.callback.handleAborted(this, "Told to back off.");
return;
}

View File

@ -0,0 +1,67 @@
/* 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.sync;
import org.mozilla.gecko.background.common.log.Logger;
import android.content.ContentResolver;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
public class PrefsBackoffHandler implements BackoffHandler {
private static final String LOG_TAG = "BackoffHandler";
public static final String PREF_EARLIEST_NEXT = "earliestnext";
private final SharedPreferences prefs;
private final String prefSuffix;
private final String prefEarliest;
public PrefsBackoffHandler(final SharedPreferences prefs, final String prefSuffix) {
if (prefs == null) {
throw new IllegalArgumentException("prefs must not be null.");
}
this.prefs = prefs;
this.prefSuffix = prefSuffix;
this.prefEarliest = PREF_EARLIEST_NEXT + "." + prefSuffix;
}
@Override
public synchronized long getEarliestNextRequest() {
return prefs.getLong(prefEarliest, 0);
}
@Override
public synchronized void setEarliestNextRequest(final long next) {
final Editor edit = prefs.edit();
edit.putLong(prefEarliest, next);
edit.commit();
}
@Override
public synchronized void extendEarliestNextRequest(final long next) {
if (prefs.getLong(prefEarliest, 0) >= next) {
return;
}
final Editor edit = prefs.edit();
edit.putLong(prefEarliest, next);
edit.commit();
}
/**
* Return the number of milliseconds until we're allowed to touch the server again,
* or 0 if now is fine.
*/
@Override
public long delayMilliseconds() {
long earliestNextRequest = getEarliestNextRequest();
if (earliestNextRequest <= 0) {
return 0;
}
long now = System.currentTimeMillis();
return Math.max(0, earliestNextRequest - now);
}
}

View File

@ -251,7 +251,6 @@ public class SyncConfiguration {
public static final String PREF_USER_SELECTED_ENGINES_TO_SYNC = "userSelectedEngines";
public static final String PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP = "userSelectedEnginesTimestamp";
public static final String PREF_EARLIEST_NEXT_SYNC = "earliestnextsync";
public static final String PREF_CLUSTER_URL_IS_STALE = "clusterurlisstale";
public static final String PREF_ACCOUNT_GUID = "account.guid";

View File

@ -55,4 +55,7 @@ public class SyncConstants {
public static final String PER_ACCOUNT_TYPE_PERMISSION = "@MOZ_ANDROID_SHARED_ACCOUNT_TYPE@.permission.PER_ACCOUNT_TYPE";
public static final String DEFAULT_AUTH_SERVER = "https://auth.services.mozilla.com/";
// Used for BackoffHandler storage for Sync 1.1's SyncAdapter.
public static final String BACKOFF_PREF_SUFFIX_11 = "sync";
}

View File

@ -32,5 +32,11 @@ public interface BaseGlobalSessionCallback {
void handleSuccess(GlobalSession globalSession);
void handleStageCompleted(Stage currentState, GlobalSession globalSession);
boolean shouldBackOff();
/**
* Called when a {@link GlobalSession} wants to know if it should continue
* to make storage requests.
*
* @return false if the session should make no further requests.
*/
boolean shouldBackOffStorage();
}

View File

@ -96,7 +96,7 @@ public class EnsureClusterURLStage extends AbstractNonRepositorySyncStage {
*
* 404: user not found | empty body
*
* {@link http://docs.services.mozilla.com/reg/apis.html}
* {@link "http://docs.services.mozilla.com/reg/apis.html"}
*/
@Override
public void handleHttpResponse(HttpResponse response) {

View File

@ -14,9 +14,11 @@ import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sync.AlreadySyncingException;
import org.mozilla.gecko.sync.BackoffHandler;
import org.mozilla.gecko.sync.CredentialException;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.PrefsBackoffHandler;
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
import org.mozilla.gecko.sync.SharedPreferencesNodeAssignmentCallback;
import org.mozilla.gecko.sync.Sync11Configuration;
@ -48,7 +50,6 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.SyncResult;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteException;
@ -65,33 +66,13 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements BaseGlob
protected long syncStartTimestamp;
protected volatile BackoffHandler backoffHandler;
public SyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
mContext = context;
}
/**
* Backoff.
*/
public synchronized long getEarliestNextSync() {
return accountSharedPreferences.getLong(SyncConfiguration.PREF_EARLIEST_NEXT_SYNC, 0);
}
public synchronized void setEarliestNextSync(long next) {
Editor edit = accountSharedPreferences.edit();
edit.putLong(SyncConfiguration.PREF_EARLIEST_NEXT_SYNC, next);
edit.commit();
}
public synchronized void extendEarliestNextSync(long next) {
if (accountSharedPreferences.getLong(SyncConfiguration.PREF_EARLIEST_NEXT_SYNC, 0) >= next) {
return;
}
Editor edit = accountSharedPreferences.edit();
edit.putLong(SyncConfiguration.PREF_EARLIEST_NEXT_SYNC, next);
edit.commit();
}
/**
* Handle an exception: update stats, log errors, etc.
* Wakes up sleeping threads by calling notifyMonitor().
@ -202,20 +183,27 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements BaseGlob
protected SharedPreferencesNodeAssignmentCallback nodeAssignmentDelegate;
/**
* Return the number of milliseconds until we're allowed to sync again,
* or 0 if now is fine.
* Request that no sync start right away. A new sync won't start until
* at least <code>backoff</code> milliseconds from now.
*
* Don't call this unless you are inside `run`.
*
* @param backoff time to wait in milliseconds.
*/
public long delayMilliseconds() {
long earliestNextSync = getEarliestNextSync();
if (earliestNextSync <= 0) {
return 0;
@Override
public void requestBackoff(final long backoff) {
if (this.backoffHandler == null) {
throw new IllegalStateException("No backoff handler: requesting backoff outside run()?");
}
if (backoff > 0) {
// Fuzz the backoff time (up to 25% more) to prevent client lock-stepping; agrees with desktop.
final long fuzzedBackoff = backoff + Math.round((double) backoff * 0.25d * Math.random());
this.backoffHandler.extendEarliestNextRequest(System.currentTimeMillis() + fuzzedBackoff);
}
long now = System.currentTimeMillis();
return Math.max(0, earliestNextSync - now);
}
@Override
public boolean shouldBackOff() {
public boolean shouldBackOffStorage() {
if (thisSyncIsForced) {
/*
* If the user asks us to sync, we should sync regardless. This path is
@ -236,22 +224,10 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements BaseGlob
return false;
}
return delayMilliseconds() > 0;
}
/**
* Request that no sync start right away. A new sync won't start until
* at least <code>backoff</code> milliseconds from now.
*
* @param backoff time to wait in milliseconds.
*/
@Override
public void requestBackoff(final long backoff) {
if (backoff > 0) {
// Fuzz the backoff time (up to 25% more) to prevent client lock-stepping; agrees with desktop.
final long fuzzedBackoff = backoff + Math.round((double) backoff * 0.25d * Math.random());
this.extendEarliestNextSync(System.currentTimeMillis() + fuzzedBackoff);
if (this.backoffHandler == null) {
throw new IllegalStateException("No backoff handler: checking backoff outside run()?");
}
return this.backoffHandler.delayMilliseconds() > 0;
}
/**
@ -365,6 +341,7 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements BaseGlob
final long version = SyncConfiguration.CURRENT_PREFS_VERSION;
self.accountSharedPreferences = Utils.getSharedPreferences(mContext, product, username, serverURL, profile, version);
self.clientsDataDelegate = new SharedPreferencesClientsDataDelegate(accountSharedPreferences);
self.backoffHandler = new PrefsBackoffHandler(accountSharedPreferences, SyncConstants.BACKOFF_PREF_SUFFIX_11);
final String nodeWeaveURL = Utils.nodeWeaveURL(serverURL, username);
self.nodeAssignmentDelegate = new SharedPreferencesNodeAssignmentCallback(accountSharedPreferences, nodeWeaveURL);
@ -373,19 +350,15 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements BaseGlob
", has client guid " + clientsDataDelegate.getAccountGUID() +
", and has " + clientsDataDelegate.getClientsCount() + " clients.");
thisSyncIsForced = (extras != null) && (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false));
long delay = delayMilliseconds();
if (delay > 0) {
if (thisSyncIsForced) {
Logger.info(LOG_TAG, "Forced sync: overruling remaining backoff of " + delay + "ms.");
} else {
Logger.info(LOG_TAG, "Not syncing: must wait another " + delay + "ms.");
long remainingSeconds = delay / 1000;
syncResult.delayUntil = remainingSeconds + BACKOFF_PAD_SECONDS;
setNextSync.set(false);
self.notifyMonitor();
return;
}
final boolean thisSyncIsForced = (extras != null) && (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false));
final long delayMillis = backoffHandler.delayMilliseconds();
boolean shouldSync = thisSyncIsForced || (delayMillis <= 0L);
if (!shouldSync) {
long remainingSeconds = delayMillis / 1000;
syncResult.delayUntil = remainingSeconds + BACKOFF_PAD_SECONDS;
setNextSync.set(false);
self.notifyMonitor();
return;
}
final String prefsPath = Utils.getPrefsPath(product, username, serverURL, profile, version);
@ -418,10 +391,10 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements BaseGlob
if (thisSyncIsForced) {
Logger.info(LOG_TAG, "Setting minimum next sync time to " + next + " (" + interval + "ms from now).");
setEarliestNextSync(next);
self.backoffHandler.setEarliestNextRequest(next);
} else {
Logger.info(LOG_TAG, "Extending minimum next sync time to " + next + " (" + interval + "ms from now).");
extendEarliestNextSync(next);
self.backoffHandler.extendEarliestNextRequest(next);
}
}
Logger.info(LOG_TAG, "Sync took " + Utils.formatDuration(syncStartTimestamp, System.currentTimeMillis()) + ".");

View File

@ -146,6 +146,7 @@ public class TokenServerClient {
}
// Responses should *always* be a valid JSON object.
// It turns out that right now they're not always, but that's a server bug...
ExtendedJSONObject result;
try {
result = res.jsonObjectBody();

View File

@ -18,6 +18,8 @@
package org.mozilla.gecko.util;
import android.text.TextUtils;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
@ -79,7 +81,7 @@ public class ProxySelector {
*/
private Proxy lookupProxy(String hostKey, String portKey, Proxy.Type type, int defaultPort) {
String host = System.getProperty(hostKey);
if (host == null || host.isEmpty()) {
if (TextUtils.isEmpty(host)) {
return null;
}

View File

@ -352,16 +352,10 @@ AboutReader.prototype = {
});
}.bind(this));
} else {
gChromeWin.Reader.removeArticleFromCache(this._article.url , function(success) {
dump("Reader:Remove (in reader) success=" + success);
Services.obs.notifyObservers(null, "Reader:Remove", this._article.url);
gChromeWin.sendMessageToJava({
type: "Reader:Removed",
url: this._article.url
});
}.bind(this));
// In addition to removing the article from the cache (handled in
// browser.js), sending this message will cause the toggle button to be
// updated (handled in this file).
Services.obs.notifyObservers(null, "Reader:Remove", this._article.url);
}
},

View File

@ -7571,6 +7571,13 @@ let Reader = {
case "Reader:Remove": {
this.removeArticleFromCache(aData, function(success) {
this.log("Reader:Remove success=" + success + ", url=" + aData);
if (success) {
sendMessageToJava({
type: "Reader:Removed",
url: url
});
}
}.bind(this));
break;
}

View File

@ -9,7 +9,9 @@ import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.PrefsBackoffHandler;
import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.SyncConstants;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.config.ConfigurationMigrator;
import org.mozilla.gecko.sync.setup.SyncAccounts;
@ -243,7 +245,7 @@ public class TestConfigurationMigrator extends AndroidSyncTestCase {
// Some global stuff.
assertEquals(false, newPrefs.getBoolean(SyncConfiguration.PREF_CLUSTER_URL_IS_STALE, true));
assertEquals(1340402318649L, newPrefs.getLong(SyncConfiguration.PREF_EARLIEST_NEXT_SYNC, 111));
assertEquals(1340402318649L, newPrefs.getLong(PrefsBackoffHandler.PREF_EARLIEST_NEXT + SyncConstants.BACKOFF_PREF_SUFFIX_11, 111));
// Some per-Sync account stuff.
assertEquals("{\"timestamp\":1340402003370}", newPrefs.getString("bookmarks.remote", null));
assertEquals("{\"timestamp\":1340402008397}", newPrefs.getString("bookmarks.local", null));

View File

@ -57,7 +57,7 @@ public class DefaultGlobalSessionCallback implements GlobalSessionCallback {
}
@Override
public boolean shouldBackOff() {
public boolean shouldBackOffStorage() {
return false;
}

View File

@ -48,6 +48,8 @@ void
ExternalHelperAppParent::Init(ContentParent *parent,
const nsCString& aMimeContentType,
const nsCString& aContentDispositionHeader,
const uint32_t& aContentDispositionHint,
const nsString& aContentDispositionFilename,
const bool& aForceSave,
const OptionalURIParams& aReferrer,
PBrowserParent* aBrowser)
@ -61,8 +63,17 @@ ExternalHelperAppParent::Init(ContentParent *parent,
SetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"), referrer);
mContentDispositionHeader = aContentDispositionHeader;
NS_GetFilenameFromDisposition(mContentDispositionFilename, mContentDispositionHeader, mURI);
mContentDisposition = NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
if (!mContentDispositionHeader.IsEmpty()) {
NS_GetFilenameFromDisposition(mContentDispositionFilename,
mContentDispositionHeader,
mURI);
mContentDisposition =
NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
}
else {
mContentDisposition = aContentDispositionHint;
mContentDispositionFilename = aContentDispositionFilename;
}
nsCOMPtr<nsIInterfaceRequestor> window;
if (aBrowser) {
@ -301,7 +312,8 @@ ExternalHelperAppParent::GetContentDisposition(uint32_t *aContentDisposition)
NS_IMETHODIMP
ExternalHelperAppParent::SetContentDisposition(uint32_t aContentDisposition)
{
return NS_ERROR_NOT_AVAILABLE;
mContentDisposition = aContentDisposition;
return NS_OK;
}
NS_IMETHODIMP
@ -317,7 +329,8 @@ ExternalHelperAppParent::GetContentDispositionFilename(nsAString& aContentDispos
NS_IMETHODIMP
ExternalHelperAppParent::SetContentDispositionFilename(const nsAString& aContentDispositionFilename)
{
return NS_ERROR_NOT_AVAILABLE;
mContentDispositionFilename = aContentDispositionFilename;
return NS_OK;
}
NS_IMETHODIMP

View File

@ -50,6 +50,8 @@ public:
void Init(ContentParent *parent,
const nsCString& aMimeContentType,
const nsCString& aContentDisposition,
const uint32_t& aContentDispositionHint,
const nsString& aContentDispositionFilename,
const bool& aForceSave,
const OptionalURIParams& aReferrer,
PBrowserParent* aBrowser);

View File

@ -670,6 +670,8 @@ NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeConte
nsAutoString fileName;
nsAutoCString fileExtension;
uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
uint32_t contentDisposition = -1;
nsresult rv;
// Get the file extension and name that we will need later
@ -679,7 +681,10 @@ NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeConte
if (channel) {
channel->GetURI(getter_AddRefs(uri));
channel->GetContentLength(&contentLength);
channel->GetContentDisposition(&contentDisposition);
channel->GetContentDispositionFilename(fileName);
}
if (XRE_GetProcessType() == GeckoProcessType_Content) {
nsCOMPtr<nsIDOMWindow> window = do_GetInterface(aWindowContext);
NS_ENSURE_STATE(window);
@ -696,8 +701,9 @@ NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeConte
return NS_ERROR_FAILURE;
nsCString disp;
if (channel)
if (channel) {
channel->GetContentDispositionHeader(disp);
}
nsCOMPtr<nsIURI> referrer;
rv = NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
@ -713,8 +719,9 @@ NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeConte
mozilla::dom::PExternalHelperAppChild *pc =
child->SendPExternalHelperAppConstructor(uriParams,
nsCString(aMimeContentType),
disp, aForceSave, contentLength,
referrerParams,
disp, contentDisposition,
fileName, aForceSave,
contentLength, referrerParams,
mozilla::dom::TabChild::GetFrom(window));
ExternalHelperAppChild *childListener = static_cast<ExternalHelperAppChild *>(pc);