Merge m-c to inbound.
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"revision": "3d73a644a8ecb4a4c426584956d0c7f6b05e4cdb",
|
||||
"revision": "4f00231c5cc538139e63bee1a7ed8456f6cefed7",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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() {
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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"/>
|
||||
|
@ -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]
|
||||
|
32
browser/base/content/test/general/browser_menuButtonFitts.js
Normal 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);
|
||||
}
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 = {
|
||||
|
@ -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>
|
@ -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
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
|
205
browser/metro/theme/firstruncontent.css
Normal 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");
|
||||
}
|
||||
}
|
BIN
browser/metro/theme/images/arrow-left-light.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
browser/metro/theme/images/arrow-left-light@1.4x.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
browser/metro/theme/images/arrow-left-light@1.8x.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
browser/metro/theme/images/arrow-top-light.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
browser/metro/theme/images/arrow-top-light@1.4x.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
browser/metro/theme/images/arrow-top-light@1.8x.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
BIN
browser/themes/linux/sync-horizontalbar.png
Normal file
After Width: | Height: | Size: 721 B |
BIN
browser/themes/linux/syncProgress-horizontalbar.png
Normal file
After Width: | Height: | Size: 535 B |
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
BIN
browser/themes/osx/sync-horizontalbar.png
Normal file
After Width: | Height: | Size: 707 B |
BIN
browser/themes/osx/sync-horizontalbar@2x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
browser/themes/osx/syncProgress-horizontalbar.png
Normal file
After Width: | Height: | Size: 537 B |
BIN
browser/themes/osx/syncProgress-horizontalbar@2x.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
BIN
browser/themes/windows/sync-horizontalbar-XPVista7.png
Normal file
After Width: | Height: | Size: 721 B |
BIN
browser/themes/windows/sync-horizontalbar.png
Normal file
After Width: | Height: | Size: 546 B |
BIN
browser/themes/windows/syncProgress-horizontalbar-XPVista7.png
Normal file
After Width: | Height: | Size: 535 B |
BIN
browser/themes/windows/syncProgress-horizontalbar.png
Normal file
After Width: | Height: | Size: 370 B |
@ -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");
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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',
|
||||
|
154
mobile/android/base/fxa/sync/FxAccountSchedulePolicy.java
Normal 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));
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
42
mobile/android/base/fxa/sync/SchedulePolicy.java
Normal 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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
34
mobile/android/base/sync/BackoffHandler.java
Normal 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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
67
mobile/android/base/sync/PrefsBackoffHandler.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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";
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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()) + ".");
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -57,7 +57,7 @@ public class DefaultGlobalSessionCallback implements GlobalSessionCallback {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldBackOff() {
|
||||
public boolean shouldBackOffStorage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|