Merge latest green b2g-inbound changeset and mozilla-central

This commit is contained in:
Ed Morley 2014-04-10 17:45:11 +01:00
commit 310c9d9a41
133 changed files with 2265 additions and 786 deletions

View File

@ -1176,7 +1176,7 @@ pref("devtools.profiler.ui.show-platform-data", false);
pref("devtools.netmonitor.enabled", true);
// The default Network Monitor UI settings
pref("devtools.netmonitor.panes-network-details-width", 450);
pref("devtools.netmonitor.panes-network-details-width", 550);
pref("devtools.netmonitor.panes-network-details-height", 450);
pref("devtools.netmonitor.statistics", true);
pref("devtools.netmonitor.filters", "[\"all\"]");

View File

@ -14,6 +14,10 @@
%endif
}
#main-window[customize-entered] {
min-width: -moz-fit-content;
}
searchbar {
-moz-binding: url("chrome://browser/content/search/search.xml#searchbar");
}

View File

@ -13,7 +13,7 @@ Cu.import("resource://gre/modules/PageThumbs.jsm");
Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm");
Cu.import("resource://gre/modules/DirectoryLinksProvider.jsm");
Cu.import("resource://gre/modules/NewTabUtils.jsm");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Rect",
"resource://gre/modules/Geometry.jsm");

View File

@ -181,7 +181,7 @@ let gTransformation = {
let deferred = Promise.defer();
batch.push(deferred.promise);
let cb = function () deferred.resolve();
let cb = deferred.resolve;
if (!cells[aIndex])
// The site disappeared from the grid, hide it.
@ -194,8 +194,9 @@ let gTransformation = {
this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: cb});
}, this);
let wait = Promise.promised(function () callback && callback());
wait.apply(null, batch);
if (callback) {
Promise.all(batch).then(callback);
}
},
/**

View File

@ -147,8 +147,7 @@ let gUpdater = {
});
});
let wait = Promise.promised(aCallback);
wait.apply(null, batch);
Promise.all(batch).then(aCallback);
},
/**
@ -180,7 +179,6 @@ let gUpdater = {
gTransformation.showSite(site, function () deferred.resolve());
});
let wait = Promise.promised(aCallback);
wait.apply(null, batch);
Promise.all(batch).then(aCallback);
}
};

View File

@ -1101,6 +1101,22 @@
this._tabAttrModified(oldTab);
this._tabAttrModified(this.mCurrentTab);
if (oldBrowser != newBrowser &&
oldBrowser.docShell &&
oldBrowser.docShell.contentViewer.inPermitUnload) {
// Since the user is switching away from a tab that has
// a beforeunload prompt active, we remove the prompt.
// This prevents confusing user flows like the following:
// 1. User attempts to close Firefox
// 2. User switches tabs (ingoring a beforeunload prompt)
// 3. User returns to tab, presses "Leave page"
let promptBox = this.getTabModalPromptBox(oldBrowser);
let prompts = promptBox.listPrompts();
// NB: This code assumes that the beforeunload prompt
// is the top-most prompt on the tab.
promptBox.removePrompt(prompts[prompts.length - 1]);
}
// Adjust focus
oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
do {
@ -1879,7 +1895,6 @@
<body>
<![CDATA[
if (aTab.closing ||
aTab._pendingPermitUnload ||
this._windowIsClosing)
return false;
@ -1887,16 +1902,10 @@
if (!aTabWillBeMoved) {
let ds = browser.docShell;
if (ds && ds.contentViewer) {
// We need to block while calling permitUnload() because it
// processes the event queue and may lead to another removeTab()
// call before permitUnload() even returned.
aTab._pendingPermitUnload = true;
let permitUnload = ds.contentViewer.permitUnload();
delete aTab._pendingPermitUnload;
if (!permitUnload)
return false;
if (ds &&
ds.contentViewer &&
!ds.contentViewer.permitUnload()) {
return false;
}
}

View File

@ -355,7 +355,7 @@ skip-if = true # disabled until the tree view is added
[browser_save_link-perwindowpb.js]
skip-if = e10s # Bug ?????? - test directly manipulates content (event.target)
[browser_save_private_link_perwindowpb.js]
skip-if = os == "linux" || e10s # Linux: bug 857427; e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
skip-if = e10s # e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
[browser_save_video.js]
skip-if = e10s # Bug ?????? - test directly manipulates content (event.target)
[browser_scope.js]

View File

@ -2400,20 +2400,14 @@ let CustomizableUIInternal = {
},
setToolbarVisibility: function(aToolbarId, aIsVisible) {
let area = gAreas.get(aToolbarId);
if (area.get("type") != CustomizableUI.TYPE_TOOLBAR) {
return;
}
let areaNodes = gBuildAreas.get(aToolbarId);
if (!areaNodes) {
return;
}
// We only persist the attribute the first time.
let isFirstChangedToolbar = true;
for (let areaNode of areaNodes) {
let window = areaNode.ownerDocument.defaultView;
window.setToolbarVisibility(areaNode, aIsVisible, isFirstChangedToolbar);
isFirstChangedToolbar = false;
for (let window of CustomizableUI.windows) {
let toolbar = window.document.getElementById(aToolbarId);
if (toolbar) {
window.setToolbarVisibility(toolbar, aIsVisible, isFirstChangedToolbar);
isFirstChangedToolbar = false;
}
}
},
};

View File

@ -1603,6 +1603,7 @@ CustomizeMode.prototype = {
}
this._updateToolbarCustomizationOutline(this.window);
this._showPanelCustomizationPlaceholders();
DragPositionManager.stop();
},
_isUnwantedDragDrop: function(aEvent) {

View File

@ -332,20 +332,36 @@ AreaPositionManager.prototype = {
_lazyStoreGet: function(aNode) {
let rect = this._nodePositionStore.get(aNode);
if (!rect) {
rect = aNode.getBoundingClientRect();
// getBoundingClientRect() returns a DOMRect that is live, meaning that
// as the element moves around, the rects values change. We don't want
// that - we want a snapshot of what the rect values are right at this
// moment, and nothing else. So we have to clone the values.
let clientRect = aNode.getBoundingClientRect();
rect = {
left: clientRect.left,
right: clientRect.right,
width: clientRect.width,
height: clientRect.height,
top: clientRect.top,
bottom: clientRect.bottom,
};
rect.x = rect.left + rect.width / 2;
rect.y = rect.top + rect.height / 2;
Object.freeze(rect);
this._nodePositionStore.set(aNode, rect);
}
return rect;
},
_firstInRow: function(aNode) {
let bound = this._lazyStoreGet(aNode).top;
// XXXmconley: I'm not entirely sure why we need to take the floor of these
// values - it looks like, periodically, we're getting fractional pixels back
//from lazyStoreGet. I've filed bug 994247 to investigate.
let bound = Math.floor(this._lazyStoreGet(aNode).top);
let rv = aNode;
let prev;
while (rv && (prev = this._getVisibleSiblingForDirection(rv, "previous"))) {
if (this._lazyStoreGet(prev).bottom <= bound) {
if (Math.floor(this._lazyStoreGet(prev).bottom) <= bound) {
return rv;
}
rv = prev;

View File

@ -104,4 +104,5 @@ skip-if = os == "linux"
[browser_987177_xul_wrapper_updating.js]
[browser_987492_window_api.js]
[browser_989289_force_icons_mode_attribute.js]
[browser_992747_toggle_noncustomizable_toolbar.js]
[browser_panel_toggle.js]

View File

@ -0,0 +1,26 @@
/* 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/. */
"use strict";
const TOOLBARID = "test-noncustomizable-toolbar-for-toggling";
function test() {
let tb = document.createElementNS(kNSXUL, "toolbar");
tb.id = TOOLBARID;
gNavToolbox.appendChild(tb);
try {
CustomizableUI.setToolbarVisibility(TOOLBARID, false);
} catch (ex) {
ok(false, "Should not throw exceptions trying to set toolbar visibility.");
}
is(tb.getAttribute("collapsed"), "true", "Toolbar should be collapsed");
try {
CustomizableUI.setToolbarVisibility(TOOLBARID, true);
} catch (ex) {
ok(false, "Should not throw exceptions trying to set toolbar visibility.");
}
is(tb.getAttribute("collapsed"), "false", "Toolbar should be uncollapsed");
tb.remove();
};

View File

@ -275,10 +275,7 @@
<vbox id="fxa-pweng-help">
<spacer flex="1"/>
<hbox id="fxa-pweng-help-link">
<label value=" ["/>
<label class="text-link" value="?"
onclick="gSyncUtils.openMPInfoPage(event);"/>
<label value="]"/>
<image onclick="gSyncUtils.openMPInfoPage(event);" />
</hbox>
<spacer flex="1"/>
</vbox>

View File

@ -276,10 +276,7 @@
<vbox id="fxa-pweng-help">
<spacer flex="1"/>
<hbox id="fxa-pweng-help-link">
<label value=" ["/>
<label class="text-link" value="?"
onclick="gSyncUtils.openMPInfoPage(event);"/>
<label value="]"/>
<image onclick="gSyncUtils.openMPInfoPage(event);" />
</hbox>
<spacer flex="1"/>
</vbox>

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const TEST_URL = 'data:text/html,<script>window.onbeforeunload=' +
'function(e){e.returnValue="?"}</script>';
@ -20,7 +22,10 @@ function onTabViewShown() {
}
function testStayOnPage(contentWindow, groupItemOne, groupItemTwo) {
whenDialogOpened(function (dialog) {
// We created a new tab group with a second tab above, so let's
// pick that second tab here and wait for its onbeforeunload dialog.
let browser = gBrowser.browsers[1];
waitForOnBeforeUnloadDialog(browser, function (btnLeave, btnStay) {
executeSoon(function () {
is(gBrowser.tabs.length, 2,
"The total number of tab is 2 when staying on the page");
@ -34,14 +39,17 @@ function testStayOnPage(contentWindow, groupItemOne, groupItemTwo) {
});
// stay on page
dialog.cancelDialog();
btnStay.click();
});
closeGroupItem(groupItemTwo);
}
function testLeavePage(contentWindow, groupItemOne, groupItemTwo) {
whenDialogOpened(function (dialog) {
// The second tab hasn't been closed yet because we chose to stay. Wait
// for the onbeforeunload dialog again and leave the page this time.
let browser = gBrowser.browsers[1];
waitForOnBeforeUnloadDialog(browser, function (btnLeave, btnStay) {
// clean up and finish the test
groupItemTwo.addSubscriber("close", function onClose() {
groupItemTwo.removeSubscriber("close", onClose);
@ -55,31 +63,8 @@ function testLeavePage(contentWindow, groupItemOne, groupItemTwo) {
});
// Leave page
dialog.acceptDialog();
btnLeave.click();
});
closeGroupItem(groupItemTwo);
}
// ----------
function whenDialogOpened(callback) {
let listener = {
onCloseWindow: function () {},
onWindowTitleChange: function () {},
onOpenWindow: function (xulWin) {
let domWin = xulWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
whenWindowLoaded(domWin, function () {
let dialog = domWin.document.querySelector("dialog");
if (dialog) {
Services.wm.removeListener(listener);
callback(dialog);
}
});
}
};
Services.wm.addListener(listener);
}

View File

@ -7,10 +7,10 @@
* Raymond Lee <raymond@appcoast.com>
*/
"use strict";
const TEST_URL = 'data:text/html,<script>window.onbeforeunload=' +
'function(e){e.returnValue="?"}</script><body ' +
'onunload="alert(\'onunload\')" onpagehide="' +
'alert(\'onpagehide\')"></body>';
'function(e){e.returnValue="?"}</script>';
let contentWindow;
let activeGroup;
@ -23,25 +23,27 @@ function test() {
activeGroup = contentWindow.GroupItems.getActiveGroupItem();
gBrowser.browsers[0].loadURI("data:text/html,<p>test for bug 626455, tab1");
gBrowser.addTab(TEST_URL);
afterAllTabsLoaded(testStayOnPage);
let tab = gBrowser.addTab(TEST_URL);
afterAllTabsLoaded(() => testStayOnPage(tab));
});
}
function testStayOnPage() {
whenDialogOpened(function (dialog) {
function testStayOnPage(blockingTab) {
let browser = blockingTab.linkedBrowser;
waitForOnBeforeUnloadDialog(browser, function (btnLeave, btnStay) {
// stay on page
dialog.cancelDialog();
btnStay.click();
executeSoon(function () {
showTabView(function () {
is(gBrowser.tabs.length, 1,
"The total number of tab is 1 when staying on the page");
let location = gBrowser.browsers[0].currentURI.spec;
isnot(location.indexOf("onbeforeunload"), -1,
"The open tab is the expected one");
// The other initial tab has been closed when trying to close the tab
// group. The only tab left is the one with the onbeforeunload dialog.
let url = gBrowser.browsers[0].currentURI.spec;
ok(url.contains("onbeforeunload"), "The open tab is the expected one");
is(contentWindow.GroupItems.getActiveGroupItem(), activeGroup,
"Active group is still the same");
@ -50,7 +52,7 @@ function testStayOnPage() {
"Only one group is open");
// start the next test
testLeavePage();
testLeavePage(gBrowser.tabs[0]);
});
});
});
@ -58,12 +60,11 @@ function testStayOnPage() {
closeGroupItem(activeGroup);
}
function testLeavePage() {
let dialogsAccepted = 0;
whenDialogOpened(function onDialogOpened(dialog) {
function testLeavePage(blockingTab) {
let browser = blockingTab.linkedBrowser;
waitForOnBeforeUnloadDialog(browser, function (btnLeave, btnStay) {
// Leave page
dialog.acceptDialog();
btnLeave.click();
});
whenGroupClosed(activeGroup, finishTest);
@ -85,6 +86,8 @@ function finishTest() {
is(contentWindow.GroupItems.groupItems.length, 1,
"Only one group is open");
contentWindow = null;
activeGroup = null;
finish();
}
@ -95,29 +98,3 @@ function whenGroupClosed(group, callback) {
callback();
});
}
// ----------
function whenDialogOpened(callback) {
let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);
let listener = {
onCloseWindow: function () {},
onWindowTitleChange: function () {},
onOpenWindow: function (xulWin) {
let domWin = xulWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
whenWindowLoaded(domWin, function () {
let dialog = domWin.document.querySelector("dialog");
if (dialog) {
wm.removeListener(listener);
callback(dialog);
}
});
}
};
wm.addListener(listener);
}

View File

@ -81,6 +81,11 @@ function test4() {
hideTabView(function() {
is(gBrowser.tabs.length, 1, "Total number of tabs is 1 after all tests");
contentWindow = null;
contentElement = null;
groupItem = null;
finish();
});
}

View File

@ -1,6 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// Some tests here assume that all restored tabs are loaded without waiting for
// the user to bring them to the foreground. We ensure this by resetting the
// related preference (see the "firefox.js" defaults file for details).
@ -418,3 +420,17 @@ function promiseWindowClosed(win) {
win.close();
return deferred.promise;
}
// ----------
function waitForOnBeforeUnloadDialog(browser, callback) {
browser.addEventListener("DOMWillOpenModalDialog", function onModalDialog() {
browser.removeEventListener("DOMWillOpenModalDialog", onModalDialog, true);
executeSoon(() => {
let stack = browser.parentNode;
let dialogs = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
let {button0, button1} = dialogs[0].ui;
callback(button0, button1);
});
}, true);
}

View File

@ -527,11 +527,15 @@ let UI = {
if (!this.isTabViewVisible() || this._isChangingVisibility)
return;
this._isChangingVisibility = true;
// another tab might be select if user decides to stay on a page when
// a onclose confirmation prompts.
GroupItems.removeHiddenGroups();
// We need to set this after removing the hidden groups because doing so
// might show prompts which will cause us to be called again, and we'd get
// stuck if we prevent re-entrancy before doing that.
this._isChangingVisibility = true;
TabItems.pausePainting();
this._reorderTabsOnHide.forEach(function(groupItem) {

View File

@ -317,7 +317,7 @@ DevTools.prototype = {
closeToolbox: function DT_closeToolbox(target) {
let toolbox = this._toolboxes.get(target);
if (toolbox == null) {
return;
return promise.reject(null);
}
return toolbox.destroy();
},

View File

@ -22,7 +22,8 @@ let test = asyncTest(function*() {
inspector.sidebar.select("layoutview");
yield inspector.sidebar.once("layoutview-ready");
yield runTests();
yield gDevTools.closeToolbox(toolbox);
// TODO: Closing the toolbox in this test leaks - bug 994314
// yield gDevTools.closeToolbox(target);
});
addTest("Test that editing margin dynamically updates the document, pressing escape cancels the changes",

View File

@ -1,3 +1,4 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
@ -22,7 +23,8 @@ let test = asyncTest(function*() {
inspector.sidebar.select("layoutview");
yield inspector.sidebar.once("layoutview-ready");
yield runTests();
yield gDevTools.closeToolbox(toolbox);
// TODO: Closing the toolbox in this test leaks - bug 994314
// yield gDevTools.closeToolbox(target);
});
addTest("When all properties are set on the node editing one should work",

View File

@ -22,7 +22,8 @@ let test = asyncTest(function*() {
inspector.sidebar.select("layoutview");
yield inspector.sidebar.once("layoutview-ready");
yield runTests();
yield gDevTools.closeToolbox(toolbox);
// TODO: Closing the toolbox in this test leaks - bug 994314
// yield gDevTools.closeToolbox(target);
});
addTest("Test that adding a border applies a border style when necessary",

View File

@ -22,7 +22,8 @@ let test = asyncTest(function*() {
inspector.sidebar.select("layoutview");
yield inspector.sidebar.once("layoutview-ready");
yield runTests();
yield gDevTools.closeToolbox(toolbox);
// TODO: Closing the toolbox in this test leaks - bug 994314
// yield gDevTools.closeToolbox(target);
});
addTest("Test that entering units works",

View File

@ -184,6 +184,34 @@
</menupopup>
</menu>
<menu id="sp-edit-menu" label="&editMenu.label;"
accesskey="&editMenu.accesskey;">
<menupopup id="sp-menu_editpopup">
<menuitem id="menu_undo"/>
<menuitem id="menu_redo"/>
<menuseparator/>
<menuitem id="menu_cut"/>
<menuitem id="menu_copy"/>
<menuitem id="menu_paste"/>
<menuseparator/>
<menuitem id="menu_selectAll"/>
<menuseparator/>
<menuitem id="menu_find"/>
<menuitem id="menu_findAgain"/>
<menuseparator/>
<menuitem id="se-menu-gotoLine"
label="&gotoLineCmd.label;"
accesskey="&gotoLineCmd.accesskey;"
key="key_gotoLine"
command="cmd_gotoLine"/>
<menuitem id="sp-menu-pprint"
label="&pprint.label;"
accesskey="&pprint.accesskey;"
key="sp-key-pprint"
command="sp-cmd-pprint"/>
</menupopup>
</menu>
<menu id="sp-view-menu" label="&viewMenu.label;" accesskey="&viewMenu.accesskey;">
<menupopup id="sp-menu-viewpopup">
<menuitem id="sp-menu-line-numbers"
@ -221,34 +249,6 @@
</menupopup>
</menu>
<menu id="sp-edit-menu" label="&editMenu.label;"
accesskey="&editMenu.accesskey;">
<menupopup id="sp-menu_editpopup">
<menuitem id="menu_undo"/>
<menuitem id="menu_redo"/>
<menuseparator/>
<menuitem id="menu_cut"/>
<menuitem id="menu_copy"/>
<menuitem id="menu_paste"/>
<menuseparator/>
<menuitem id="menu_selectAll"/>
<menuseparator/>
<menuitem id="menu_find"/>
<menuitem id="menu_findAgain"/>
<menuseparator/>
<menuitem id="se-menu-gotoLine"
label="&gotoLineCmd.label;"
accesskey="&gotoLineCmd.accesskey;"
key="key_gotoLine"
command="cmd_gotoLine"/>
<menuitem id="sp-menu-pprint"
label="&pprint.label;"
accesskey="&pprint.accesskey;"
key="sp-key-pprint"
command="sp-cmd-pprint"/>
</menupopup>
</menu>
<menu id="sp-execute-menu" label="&executeMenu.label;"
accesskey="&executeMenu.accesskey;">
<menupopup id="sp-menu_executepopup">

View File

@ -193,10 +193,11 @@ function test() {
function testEnd() {
document.getElementById("developer-toolbar-closebutton").doCommand();
let target1 = TargetFactory.forTab(tab1);
gDevTools.closeToolbox(target1);
gBrowser.removeTab(tab1);
gBrowser.removeTab(tab2);
finish();
gDevTools.closeToolbox(target1).then(() => {
gBrowser.removeTab(tab1);
gBrowser.removeTab(tab2);
finish();
});
}
// Utility functions

View File

@ -356,6 +356,7 @@ Experiments.Experiments.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback, Ci.nsIObserver]),
init: function () {
this._shutdown = false;
configureLogging();
gExperimentsEnabled = gPrefs.get(PREF_ENABLED, false);
@ -386,10 +387,19 @@ Experiments.Experiments.prototype = {
},
/**
* Uninitialize this instance.
*
* This function is susceptible to race conditions. If it is called multiple
* times before the previous uninit() has completed or if it is called while
* an init() operation is being performed, the object may get in bad state
* and/or deadlock could occur.
*
* @return Promise<>
* The promise is fulfilled when all pending tasks are finished.
*/
uninit: function () {
uninit: Task.async(function* () {
yield this._loadTask;
if (!this._shutdown) {
this._stopWatchingAddons();
@ -406,10 +416,11 @@ Experiments.Experiments.prototype = {
this._shutdown = true;
if (this._mainTask) {
return this._mainTask;
yield this._mainTask;
}
return Promise.resolve();
},
this._log.info("Completed uninitialization.");
}),
_startWatchingAddons: function () {
AddonManager.addAddonListener(this);

View File

@ -135,12 +135,18 @@ browser.jar:
#endif
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/browser/preferences/in-content/check.png (preferences/in-content/check.png)
skin/classic/browser/preferences/in-content/icons.png (preferences/in-content/icons.png)
skin/classic/browser/preferences/in-content/header.png (preferences/in-content/header.png)
skin/classic/browser/preferences/in-content/dropdown.png (preferences/in-content/dropdown.png)
skin/classic/browser/preferences/in-content/sorter.png (preferences/in-content/sorter.png)
skin/classic/browser/preferences/in-content/dropdown-disabled.png (preferences/in-content/dropdown-disabled.png)
skin/classic/browser/preferences/in-content/check.png (../shared/incontentprefs/check.png)
skin/classic/browser/preferences/in-content/check@2x.png (../shared/incontentprefs/check@2x.png)
skin/classic/browser/preferences/in-content/icons.png (../shared/incontentprefs/icons.png)
skin/classic/browser/preferences/in-content/icons@2x.png (../shared/incontentprefs/icons@2x.png)
skin/classic/browser/preferences/in-content/header.png (../shared/incontentprefs/header.png)
skin/classic/browser/preferences/in-content/header@2x.png (../shared/incontentprefs/header@2x.png)
skin/classic/browser/preferences/in-content/dropdown.png (../shared/incontentprefs/dropdown.png)
skin/classic/browser/preferences/in-content/dropdown@2x.png (../shared/incontentprefs/dropdown@2x.png)
skin/classic/browser/preferences/in-content/sorter.png (../shared/incontentprefs/sorter.png)
skin/classic/browser/preferences/in-content/sorter@2x.png (../shared/incontentprefs/sorter@2x.png)
skin/classic/browser/preferences/in-content/dropdown-disabled.png (../shared/incontentprefs/dropdown-disabled.png)
skin/classic/browser/preferences/in-content/dropdown-disabled@2x.png (../shared/incontentprefs/dropdown-disabled@2x.png)
skin/classic/browser/preferences/applications.css (preferences/applications.css)
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/browser/social/services-16.png (social/services-16.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -2,7 +2,7 @@
- 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/. */
%include ../../../shared/in-content/preferences.css
%include ../../../shared/incontentprefs/preferences.css
button > .button-box,
menulist > .menulist-label-box {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 B

View File

@ -169,4 +169,8 @@ label.small {
margin: 0;
}
#fxa-pweng-help-link > image {
list-style-image: url("chrome://global/skin/icons/question-16.png");
}
%endif

View File

@ -223,14 +223,18 @@ browser.jar:
skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/browser/preferences/in-content/check.png (preferences/in-content/check.png)
skin/classic/browser/preferences/in-content/check@2x.png (preferences/in-content/check@2x.png)
skin/classic/browser/preferences/in-content/icons.png (preferences/in-content/icons.png)
skin/classic/browser/preferences/in-content/icons@2x.png (preferences/in-content/icons@2x.png)
skin/classic/browser/preferences/in-content/header.png (preferences/in-content/icons@2x.png)
skin/classic/browser/preferences/in-content/sorter.png (preferences/in-content/sorter.png)
skin/classic/browser/preferences/in-content/dropdown.png (preferences/in-content/dropdown.png)
skin/classic/browser/preferences/in-content/dropdown-disabled.png (preferences/in-content/dropdown-disabled.png)
skin/classic/browser/preferences/in-content/check.png (../shared/incontentprefs/check.png)
skin/classic/browser/preferences/in-content/check@2x.png (../shared/incontentprefs/check@2x.png)
skin/classic/browser/preferences/in-content/icons.png (../shared/incontentprefs/icons.png)
skin/classic/browser/preferences/in-content/icons@2x.png (../shared/incontentprefs/icons@2x.png)
skin/classic/browser/preferences/in-content/header.png (../shared/incontentprefs/header.png)
skin/classic/browser/preferences/in-content/header@2x.png (../shared/incontentprefs/header@2x.png)
skin/classic/browser/preferences/in-content/sorter.png (../shared/incontentprefs/sorter.png)
skin/classic/browser/preferences/in-content/sorter@2x.png (../shared/incontentprefs/sorter@2x.png)
skin/classic/browser/preferences/in-content/dropdown.png (../shared/incontentprefs/dropdown.png)
skin/classic/browser/preferences/in-content/dropdown@2x.png (../shared/incontentprefs/dropdown@2x.png)
skin/classic/browser/preferences/in-content/dropdown-disabled.png (../shared/incontentprefs/dropdown-disabled.png)
skin/classic/browser/preferences/in-content/dropdown-disabled@2x.png (../shared/incontentprefs/dropdown-disabled@2x.png)
skin/classic/browser/preferences/applications.css (preferences/applications.css)
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/browser/social/services-16.png (social/services-16.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@ -2,7 +2,7 @@
- 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/. */
%include ../../../shared/in-content/preferences.css
%include ../../../shared/incontentprefs/preferences.css
menulist:not([editable="true"]) > .menulist-dropmarker {
display: -moz-box;
@ -61,75 +61,3 @@ description {
font-size: 1.25rem;
line-height: 22px;
}
@media (min-resolution: 2dppx) {
checkbox:hover::before,
checkbox[checked]::before {
background-size: cover;
background-image: -moz-image-rect(url("chrome://browser/skin/preferences/in-content/check@2x.png"), 0, 30, 30, 0);
}
checkbox[checked]::before {
background-image: -moz-image-rect(url("chrome://browser/skin/preferences/in-content/check@2x.png"), 0, 60, 30, 30);
}
.category-icon {
list-style-image: url("chrome://browser/skin/preferences/in-content/icons@2x.png");
}
#category-general > .category-icon {
-moz-image-region: rect(0, 48px, 48px, 0);
}
#category-general[selected] > .category-icon {
-moz-image-region: rect(48px, 48px, 96px, 0);
}
#category-content > .category-icon {
-moz-image-region: rect(0, 96px, 48px, 48px);
}
#category-content[selected] > .category-icon {
-moz-image-region: rect(48px, 96px, 96px, 48px);
}
#category-application > .category-icon {
-moz-image-region: rect(0, 144px, 48px, 96px);
}
#category-application[selected] > .category-icon {
-moz-image-region: rect(48px, 144px, 96px, 96px);
}
#category-privacy > .category-icon {
-moz-image-region: rect(0, 192px, 48px, 144px);
}
#category-privacy[selected] > .category-icon {
-moz-image-region: rect(48px, 192px, 96px, 144px);
}
#category-security > .category-icon {
-moz-image-region: rect(0, 240px, 48px, 192px);
}
#category-security[selected] > .category-icon {
-moz-image-region: rect(48px, 240px, 96px, 192px);
}
#category-sync > .category-icon {
-moz-image-region: rect(0, 288px, 48px, 240px);
}
#category-sync[selected] > .category-icon {
-moz-image-region: rect(48px, 288px, 96px, 240px);
}
#category-advanced > .category-icon {
-moz-image-region: rect(0, 336px, 48px, 288px);
}
#category-advanced[selected] > .category-icon {
-moz-image-region: rect(48px, 336px, 96px, 288px);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 B

View File

@ -234,4 +234,16 @@ html|a.inline-link:-moz-focusring {
margin: 0;
}
#fxa-pweng-help-link > image {
width: 16px;
height: 16px;
list-style-image: url("chrome://global/skin/icons/question-16.png");
}
@media (min-resolution: 2dppx) {
#fxa-pweng-help-link > image {
list-style-image: url("chrome://global/skin/icons/question-32.png");
}
}
%endif

View File

@ -244,7 +244,6 @@ panelview:not([mainview]) .toolbarbutton-text,
toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-item),
.panelUI-grid .toolbarbutton-1,
toolbarpaletteitem[place="panel"]:not([haswideitem=true]),
.panel-customization-placeholder-child {
-moz-appearance: none;
-moz-box-orient: vertical;
@ -772,17 +771,8 @@ menuitem.panel-subview-footer@menuStateActive@,
}
#BMB_bookmarksPopup menupopup > .bookmarks-actions-menuseparator {
/* Hide bottom separator as the styled footer includes a top border serving the same purpose.
* We can't just use display: none here, otherwise scrollbox.xml will flip out and sometimes
* refuse to scroll for us (see bug 984156). Instead, we set it to visibility hidden, force
* a minimum height, and then negative-margin that single pixel into oblivion. That seems
* to be enough to make scrollbox happy.
*/
-moz-appearance: none;
visibility: hidden;
min-height: 1px;
margin: -1px 0 0;
border: none;
/* Hide bottom separator as the styled footer includes a top border serving the same purpose */
display: none;
}
/* Popups with only one item don't have a footer */
@ -918,10 +908,14 @@ toolbarpaletteitem[place="palette"] > #search-container {
/* Make direct siblings overlap borders: */
.toolbaritem-combined-buttons + .toolbaritem-combined-buttons@inAnyPanel@ {
margin-top: -1px;
border-top-color: transparent !important;
}
.toolbaritem-combined-buttons + .toolbaritem-combined-buttons@inAnyPanel@,
toolbarpaletteitem[haswideitem][place="panel"] + toolbarpaletteitem[haswideitem][place="panel"] {
margin-top: -1px;
}
.toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton {
border: 0;
padding: .5em;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -178,10 +178,10 @@ button[type="menu"] > .button-box > .button-menu-dropmarker {
-moz-margin-start: 10px;
padding: 0;
width: 10px;
height: 15px;
height: 16px;
border: none;
background-color: transparent;
list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown.png")
list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown.png");
}
.spinbuttons-button {
@ -232,6 +232,23 @@ menulist[disabled="true"]:not([editable="true"]) > .menulist-dropmarker {
list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown-disabled.png")
}
@media (min-resolution: 2dppx) {
menulist:not([editable="true"]) > .menulist-dropmarker,
button[type="menu"] > .button-box > .button-menu-dropmarker {
list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown@2x.png");
}
menulist[disabled="true"]:not([editable="true"]) > .menulist-dropmarker {
list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown-disabled@2x.png")
}
menulist:not([editable="true"]) > .menulist-dropmarker > .dropmarker-icon,
button[type="menu"] > .button-box > .button-menu-dropmarker > .dropmarker-icon {
width: 10px;
height: 16px;
}
}
menulist > menupopup,
button[type="menu"] > menupopup {
-moz-appearance: none;
@ -365,6 +382,18 @@ checkbox[checked]::before {
background-position: -15px 0;
}
@media (min-resolution: 2dppx) {
checkbox:hover::before {
background-size: cover;
background-image: -moz-image-rect(url("chrome://browser/skin/preferences/in-content/check@2x.png"), 0, 30, 30, 0);
}
checkbox[checked]::before {
background-size: cover;
background-image: -moz-image-rect(url("chrome://browser/skin/preferences/in-content/check@2x.png"), 0, 60, 30, 30);
}
}
.radio-check {
-moz-appearance: none;
width: 23px;
@ -509,6 +538,68 @@ radio[selected]::before {
-moz-image-region: rect(24px, 168px, 48px, 144px);
}
@media (min-resolution: 2dppx) {
.category-icon {
list-style-image: url("chrome://browser/skin/preferences/in-content/icons@2x.png");
}
#category-general > .category-icon {
-moz-image-region: rect(0, 48px, 48px, 0);
}
#category-general[selected] > .category-icon {
-moz-image-region: rect(48px, 48px, 96px, 0);
}
#category-content > .category-icon {
-moz-image-region: rect(0, 96px, 48px, 48px);
}
#category-content[selected] > .category-icon {
-moz-image-region: rect(48px, 96px, 96px, 48px);
}
#category-application > .category-icon {
-moz-image-region: rect(0, 144px, 48px, 96px);
}
#category-application[selected] > .category-icon {
-moz-image-region: rect(48px, 144px, 96px, 96px);
}
#category-privacy > .category-icon {
-moz-image-region: rect(0, 192px, 48px, 144px);
}
#category-privacy[selected] > .category-icon {
-moz-image-region: rect(48px, 192px, 96px, 144px);
}
#category-security > .category-icon {
-moz-image-region: rect(0, 240px, 48px, 192px);
}
#category-security[selected] > .category-icon {
-moz-image-region: rect(48px, 240px, 96px, 192px);
}
#category-sync > .category-icon {
-moz-image-region: rect(0, 288px, 48px, 240px);
}
#category-sync[selected] > .category-icon {
-moz-image-region: rect(48px, 288px, 96px, 240px);
}
#category-advanced > .category-icon {
-moz-image-region: rect(0, 336px, 48px, 288px);
}
#category-advanced[selected] > .category-icon {
-moz-image-region: rect(48px, 336px, 96px, 288px);
}
}
/* header */
.header {
@ -530,31 +621,65 @@ radio[selected]::before {
}
#header-general > .header-icon {
-moz-image-region: rect(3px, 45px, 45px, 3px);
-moz-image-region: rect(0, 40px, 40px, 0);
}
#header-content > .header-icon {
-moz-image-region: rect(3px, 93px, 45px, 51px);
-moz-image-region: rect(0, 80px, 40px, 40px);
}
#header-application > .header-icon {
-moz-image-region: rect(3px, 141px, 45px, 99px);
-moz-image-region: rect(0, 120px, 40px, 80px);
}
#header-privacy > .header-icon {
-moz-image-region: rect(3px, 189px, 45px, 147px);
-moz-image-region: rect(0, 160px, 40px, 120px);
}
#header-security > .header-icon {
-moz-image-region: rect(3px, 237px, 45px, 195px);
-moz-image-region: rect(0, 200px, 40px, 160px);
}
#header-sync > .header-icon {
-moz-image-region: rect(3px, 285px, 45px, 243px);
-moz-image-region: rect(0, 240px, 40px, 200px);
}
#header-advanced > .header-icon {
-moz-image-region: rect(3px, 333px, 45px, 291px);
-moz-image-region: rect(0, 280px, 40px, 240px);
}
@media (min-resolution: 2dppx) {
.header-icon {
list-style-image: url("chrome://browser/skin/preferences/in-content/header@2x.png");
}
#header-general > .header-icon {
-moz-image-region: rect(0, 80px, 80px, 0);
}
#header-content > .header-icon {
-moz-image-region: rect(0, 160px, 80px, 80px);
}
#header-application > .header-icon {
-moz-image-region: rect(0, 240px, 80px, 160px);
}
#header-privacy > .header-icon {
-moz-image-region: rect(0, 320px, 80px, 240px);
}
#header-security > .header-icon {
-moz-image-region: rect(0, 400px, 80px, 320px);
}
#header-sync > .header-icon {
-moz-image-region: rect(0, 480px, 80px, 400px);
}
#header-advanced > .header-icon {
-moz-image-region: rect(0, 560px, 80px, 480px);
}
}
.indent {
@ -645,7 +770,7 @@ filefield {
#typeColumn > .treecol-sortdirection[sortDirection=ascending],
#actionColumn > .treecol-sortdirection[sortDirection=ascending],
#typeColumn > .treecol-sortdirection[sortDirection=descending],
#actionColumn > .treecol-sortdirection[sortDirection=descending] {
#actionColumn > .treecol-sortdirection[sortDirection=descending] {
-moz-appearance: none;
list-style-image: url("chrome://browser/skin/preferences/in-content/sorter.png");
}
@ -655,6 +780,17 @@ filefield {
transform: scaleY(-1);
}
@media (min-resolution: 2dppx) {
#typeColumn > .treecol-sortdirection[sortDirection=ascending],
#actionColumn > .treecol-sortdirection[sortDirection=ascending],
#typeColumn > .treecol-sortdirection[sortDirection=descending],
#actionColumn > .treecol-sortdirection[sortDirection=descending] {
width: 12px;
height: 8px;
list-style-image: url("chrome://browser/skin/preferences/in-content/sorter@2x.png");
}
}
#handlersView > richlistitem {
min-height: 40px !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

View File

@ -58,8 +58,6 @@
border-color: hsla(210,54%,20%,.25) hsla(210,54%,20%,.27) hsla(210,54%,20%,.3);
box-shadow: 0 1px 0 hsla(0,0%,0%,.01) inset,
0 1px 0 hsla(0,0%,100%,.1);
transition-property: border-color;
transition-duration: 200ms;
}
#urlbar:not(:-moz-lwtheme)[focused],

View File

@ -160,14 +160,18 @@ browser.jar:
skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/browser/preferences/in-content/check.png (preferences/in-content/check.png)
skin/classic/browser/preferences/in-content/check@2x.png (preferences/in-content/check@2x.png)
skin/classic/browser/preferences/in-content/icons.png (preferences/in-content/icons.png)
skin/classic/browser/preferences/in-content/icons@2x.png (preferences/in-content/icons@2x.png)
skin/classic/browser/preferences/in-content/header.png (preferences/in-content/icons@2x.png)
skin/classic/browser/preferences/in-content/sorter.png (preferences/in-content/sorter.png)
skin/classic/browser/preferences/in-content/dropdown.png (preferences/in-content/dropdown.png)
skin/classic/browser/preferences/in-content/dropdown-disabled.png (preferences/in-content/dropdown-disabled.png)
skin/classic/browser/preferences/in-content/check.png (../shared/incontentprefs/check.png)
skin/classic/browser/preferences/in-content/check@2x.png (../shared/incontentprefs/check@2x.png)
skin/classic/browser/preferences/in-content/icons.png (../shared/incontentprefs/icons.png)
skin/classic/browser/preferences/in-content/icons@2x.png (../shared/incontentprefs/icons@2x.png)
skin/classic/browser/preferences/in-content/header.png (../shared/incontentprefs/header.png)
skin/classic/browser/preferences/in-content/header@2x.png (../shared/incontentprefs/header@2x.png)
skin/classic/browser/preferences/in-content/sorter.png (../shared/incontentprefs/sorter.png)
skin/classic/browser/preferences/in-content/sorter@2x.png (../shared/incontentprefs/sorter@2x.png)
skin/classic/browser/preferences/in-content/dropdown.png (../shared/incontentprefs/dropdown.png)
skin/classic/browser/preferences/in-content/dropdown@2x.png (../shared/incontentprefs/dropdown@2x.png)
skin/classic/browser/preferences/in-content/dropdown-disabled.png (../shared/incontentprefs/dropdown-disabled.png)
skin/classic/browser/preferences/in-content/dropdown-disabled@2x.png (../shared/incontentprefs/dropdown-disabled@2x.png)
skin/classic/browser/preferences/applications.css (preferences/applications.css)
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/browser/social/services-16.png (social/services-16.png)
@ -511,14 +515,18 @@ browser.jar:
skin/classic/aero/browser/preferences/saveFile.png (preferences/saveFile-aero.png)
* skin/classic/aero/browser/preferences/preferences.css (preferences/preferences.css)
* skin/classic/aero/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/aero/browser/preferences/in-content/check.png (preferences/in-content/check.png)
skin/classic/aero/browser/preferences/in-content/check@2x.png (preferences/in-content/check@2x.png)
skin/classic/aero/browser/preferences/in-content/icons.png (preferences/in-content/icons.png)
skin/classic/aero/browser/preferences/in-content/icons@2x.png (preferences/in-content/icons@2x.png)
skin/classic/aero/browser/preferences/in-content/header.png (preferences/in-content/icons@2x.png)
skin/classic/aero/browser/preferences/in-content/sorter.png (preferences/in-content/sorter.png)
skin/classic/aero/browser/preferences/in-content/dropdown.png (preferences/in-content/dropdown.png)
skin/classic/aero/browser/preferences/in-content/dropdown-disabled.png (preferences/in-content/dropdown-disabled.png)
skin/classic/aero/browser/preferences/in-content/check.png (../shared/incontentprefs/check.png)
skin/classic/aero/browser/preferences/in-content/check@2x.png (../shared/incontentprefs/check@2x.png)
skin/classic/aero/browser/preferences/in-content/icons.png (../shared/incontentprefs/icons.png)
skin/classic/aero/browser/preferences/in-content/icons@2x.png (../shared/incontentprefs/icons@2x.png)
skin/classic/aero/browser/preferences/in-content/header.png (../shared/incontentprefs/header.png)
skin/classic/aero/browser/preferences/in-content/header@2x.png (../shared/incontentprefs/header@2x.png)
skin/classic/aero/browser/preferences/in-content/sorter.png (../shared/incontentprefs/sorter.png)
skin/classic/aero/browser/preferences/in-content/sorter@2x.png (../shared/incontentprefs/sorter@2x.png)
skin/classic/aero/browser/preferences/in-content/dropdown.png (../shared/incontentprefs/dropdown.png)
skin/classic/aero/browser/preferences/in-content/dropdown@2x.png (../shared/incontentprefs/dropdown@2x.png)
skin/classic/aero/browser/preferences/in-content/dropdown-disabled.png (../shared/incontentprefs/dropdown-disabled.png)
skin/classic/aero/browser/preferences/in-content/dropdown-disabled@2x.png (../shared/incontentprefs/dropdown-disabled@2x.png)
skin/classic/aero/browser/preferences/applications.css (preferences/applications.css)
skin/classic/aero/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/aero/browser/social/services-16.png (social/services-16.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@ -2,7 +2,7 @@
- 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/. */
%include ../../../shared/in-content/preferences.css
%include ../../../shared/incontentprefs/preferences.css
menulist:not([editable="true"]) > .menulist-dropmarker {
margin-top: 1px;
@ -30,75 +30,3 @@ radio[selected]::before {
.actionsMenu > .menulist-label-box > .menulist-icon {
-moz-margin-end: 9px;
}
@media (min-resolution: 2dppx) {
checkbox:hover::before,
checkbox[checked]::before {
background-size: cover;
background-image: -moz-image-rect(url("chrome://browser/skin/preferences/in-content/check@2x.png"), 0, 30, 30, 0);
}
checkbox[checked]::before {
background-image: -moz-image-rect(url("chrome://browser/skin/preferences/in-content/check@2x.png"), 0, 60, 30, 30);
}
.category-icon {
list-style-image: url("chrome://browser/skin/preferences/in-content/icons@2x.png");
}
#category-general > .category-icon {
-moz-image-region: rect(0, 48px, 48px, 0);
}
#category-general[selected] > .category-icon {
-moz-image-region: rect(48px, 48px, 96px, 0);
}
#category-content > .category-icon {
-moz-image-region: rect(0, 96px, 48px, 48px);
}
#category-content[selected] > .category-icon {
-moz-image-region: rect(48px, 96px, 96px, 48px);
}
#category-application > .category-icon {
-moz-image-region: rect(0, 144px, 48px, 96px);
}
#category-application[selected] > .category-icon {
-moz-image-region: rect(48px, 144px, 96px, 96px);
}
#category-privacy > .category-icon {
-moz-image-region: rect(0, 192px, 48px, 144px);
}
#category-privacy[selected] > .category-icon {
-moz-image-region: rect(48px, 192px, 96px, 144px);
}
#category-security > .category-icon {
-moz-image-region: rect(0, 240px, 48px, 192px);
}
#category-security[selected] > .category-icon {
-moz-image-region: rect(48px, 240px, 96px, 192px);
}
#category-sync > .category-icon {
-moz-image-region: rect(0, 288px, 48px, 240px);
}
#category-sync[selected] > .category-icon {
-moz-image-region: rect(48px, 288px, 96px, 240px);
}
#category-advanced > .category-icon {
-moz-image-region: rect(0, 336px, 48px, 288px);
}
#category-advanced[selected] > .category-icon {
-moz-image-region: rect(48px, 336px, 96px, 288px);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 B

View File

@ -159,4 +159,8 @@ label.small {
margin: 0;
}
#fxa-pweng-help-link > image {
list-style-image: url("chrome://global/skin/icons/question-16.png");
}
%endif

View File

@ -2485,6 +2485,8 @@ XULDocument::PrepareToWalk()
// Block onload until we've finished building the complete
// document content model.
BlockOnload();
nsContentSink::NotifyDocElementCreated(this);
}
// There'd better not be anything on the context stack at this

View File

@ -5,6 +5,7 @@ support-files =
overlay2_bug335375.xul
window_bug583948.xul
window_bug757137.xul
window_documentnotification.xul
[test_bug199692.xul]
[test_bug311681.xul]
@ -20,3 +21,4 @@ support-files =
[test_bug583948.xul]
[test_bug640158_overlay_persist.xul]
[test_bug757137.xul]
[test_documentnotification.xul]

View File

@ -0,0 +1,40 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<body xmlns="http://www.w3.org/1999/xhtml">
<div id="content" style="display: none"/>
</body>
<script>
SimpleTest.waitForExplicitFinish();
var seenNotification = false;
function notify(subject, topic, data) {
seenNotification = true;
is(topic, "document-element-inserted", "Should be the right notification");
is(subject, otherWindow.document, "Should have been notified about the right window");
ok(subject.documentElement, "documentElement should be defined");
}
var obs = Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService)
obs.addObserver(notify, "document-element-inserted", false);
var otherWindow = window.open("window_documentnotification.xul", "_new", "chrome");
otherWindow.addEventListener("load", function() {
ok(seenNotification, "Should have seen the document-element-inserted")
obs.removeObserver(notify, "document-element-inserted");
window.close();
SimpleTest.waitForFocus(function() {
SimpleTest.finish();
});
});
</script>
</window>

View File

@ -0,0 +1,6 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<label value="window_documentnotification.xul"/>
</window>

View File

@ -27,7 +27,7 @@ class nsDOMNavigationTiming;
[ptr] native nsViewPtr(nsView);
[ptr] native nsDOMNavigationTimingPtr(nsDOMNavigationTiming);
[scriptable, builtinclass, uuid(4fc2512b-87c7-4a37-9fac-cee0f116bfdf)]
[scriptable, builtinclass, uuid(0cb321bd-5b38-4586-8fcd-d43b366886fb)]
interface nsIContentViewer : nsISupports
{
@ -49,6 +49,11 @@ interface nsIContentViewer : nsISupports
*/
boolean permitUnload([optional] in boolean aCallerClosesWindow);
/**
* Exposes whether we're blocked in a call to permitUnload.
*/
readonly attribute boolean inPermitUnload;
/**
* As above, but this passes around the aShouldPrompt argument to keep
* track of whether the user has responded to a prompt.

View File

@ -326,6 +326,17 @@ void CleanupOSFileConstants()
#define INT_CONSTANT(name) \
{ #name, INT_TO_JSVAL(name) }
/**
* Define a simple read-only property holding an unsigned integer.
*
* @param name The name of the constant. Used both as the JS name for the
* constant and to access its value. Must be defined.
*
* Produces a |ConstantSpec|.
*/
#define UINT_CONSTANT(name) \
{ #name, UINT_TO_JSVAL((name)) }
/**
* End marker for ConstantSpec
*/
@ -682,7 +693,7 @@ static const dom::ConstantSpec gWinProperties[] =
INT_CONSTANT(FILE_END),
// SetFilePointer error constant
INT_CONSTANT(INVALID_SET_FILE_POINTER),
UINT_CONSTANT(INVALID_SET_FILE_POINTER),
// File attributes
INT_CONSTANT(FILE_ATTRIBUTE_DIRECTORY),

View File

@ -23,6 +23,7 @@
#include "nsStyleSet.h"
#include "nsCSSStyleSheet.h"
#include "nsIFrame.h"
#include "nsIWritablePropertyBag2.h"
#include "nsSubDocumentFrame.h"
#include "nsILinkHandler.h"
@ -1158,6 +1159,14 @@ nsDocumentViewer::PermitUnloadInternal(bool aCallerClosesWindow,
nsCOMPtr<nsIPrompt> prompt = do_GetInterface(docShell);
if (prompt) {
nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt);
if (promptBag) {
bool isTabModalPromptAllowed;
GetIsTabModalPromptAllowed(&isTabModalPromptAllowed);
promptBag->SetPropertyAsBool(NS_LITERAL_STRING("allowTabModal"),
isTabModalPromptAllowed);
}
nsXPIDLString title, message, stayLabel, leaveLabel;
rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
"OnBeforeUnloadTitle",
@ -1200,7 +1209,19 @@ nsDocumentViewer::PermitUnloadInternal(bool aCallerClosesWindow,
leaveLabel, stayLabel, nullptr, nullptr,
&dummy, &buttonPressed);
mInPermitUnloadPrompt = false;
NS_ENSURE_SUCCESS(rv, rv);
// If the prompt aborted, we tell our consumer that it is not allowed
// to unload the page. One reason that prompts abort is that the user
// performed some action that caused the page to unload while our prompt
// was active. In those cases we don't want our consumer to also unload
// the page.
//
// XXX: Are there other cases where prompts can abort? Is it ok to
// prevent unloading the page in those cases?
if (NS_FAILED(rv)) {
*aPermitUnload = false;
return NS_OK;
}
// Button 0 == leave, button 1 == stay
*aPermitUnload = (buttonPressed == 0);
@ -1247,6 +1268,13 @@ nsDocumentViewer::GetBeforeUnloadFiring(bool* aInEvent)
return NS_OK;
}
NS_IMETHODIMP
nsDocumentViewer::GetInPermitUnload(bool* aInEvent)
{
*aInEvent = mInPermitUnloadPrompt;
return NS_OK;
}
NS_IMETHODIMP
nsDocumentViewer::ResetCloseWindow()
{
@ -4313,7 +4341,7 @@ nsDocumentViewer::GetHistoryEntry(nsISHEntry **aHistoryEntry)
NS_IMETHODIMP
nsDocumentViewer::GetIsTabModalPromptAllowed(bool *aAllowed)
{
*aAllowed = !(mInPermitUnload || mHidden);
*aAllowed = !mHidden;
return NS_OK;
}

View File

@ -13,11 +13,13 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.StyleSpan;
public class DataReportingNotification {
@ -28,15 +30,50 @@ public class DataReportingNotification {
private static final String PREFS_POLICY_NOTIFIED_TIME = "datareporting.policy.dataSubmissionPolicyNotifiedTime";
private static final String PREFS_POLICY_VERSION = "datareporting.policy.dataSubmissionPolicyVersion";
private static final int DATA_REPORTING_VERSION = 1;
private static final int DATA_REPORTING_VERSION = 2;
public static void checkAndNotifyPolicy(Context context) {
SharedPreferences dataPrefs = GeckoSharedPrefs.forApp(context);
final int currentVersion = dataPrefs.getInt(PREFS_POLICY_VERSION, -1);
// Notify if user has not been notified or if policy version has changed.
if ((!dataPrefs.contains(PREFS_POLICY_NOTIFIED_TIME)) ||
(DATA_REPORTING_VERSION != dataPrefs.getInt(PREFS_POLICY_VERSION, -1))) {
if (currentVersion < 1) {
// This is a first run, so notify user about data policy.
notifyDataPolicy(context, dataPrefs);
// If healthreport is enabled, set default preference value.
if (AppConstants.MOZ_SERVICES_HEALTHREPORT) {
SharedPreferences.Editor editor = dataPrefs.edit();
editor.putBoolean(GeckoPreferences.PREFS_HEALTHREPORT_UPLOAD_ENABLED, true);
editor.commit();
}
return;
}
if (currentVersion == 1) {
// Redisplay notification only for Beta because version 2 updates Beta policy and update version.
if (TextUtils.equals("beta", AppConstants.MOZ_UPDATE_CHANNEL)) {
notifyDataPolicy(context, dataPrefs);
} else {
// Silently update the version.
SharedPreferences.Editor editor = dataPrefs.edit();
editor.putInt(PREFS_POLICY_VERSION, DATA_REPORTING_VERSION);
editor.commit();
}
return;
}
if (currentVersion >= DATA_REPORTING_VERSION) {
// Do nothing, we're at a current (or future) version.
return;
}
}
/**
* Launch a notification of the data policy, and record notification time and version.
*/
private static void notifyDataPolicy(Context context, SharedPreferences sharedPrefs) {
boolean result = false;
try {
// Launch main App to launch Data choices when notification is clicked.
Intent prefIntent = new Intent(GeckoApp.ACTION_LAUNCH_SETTINGS);
prefIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.BROWSER_INTENT_CLASS_NAME);
@ -45,21 +82,22 @@ public class DataReportingNotification {
prefIntent.putExtra(ALERT_NAME_DATAREPORTING_NOTIFICATION, true);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, prefIntent, PendingIntent.FLAG_UPDATE_CURRENT);
final Resources resources = context.getResources();
// Create and send notification.
String notificationTitle = context.getResources().getString(R.string.datareporting_notification_title);
String notificationTitle = resources.getString(R.string.datareporting_notification_title);
String notificationSummary;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
notificationSummary = context.getResources().getString(R.string.datareporting_notification_action);
notificationSummary = resources.getString(R.string.datareporting_notification_action);
} else {
// Display partial version of Big Style notification for supporting devices.
notificationSummary = context.getResources().getString(R.string.datareporting_notification_summary);
// Display partial version of Big Style notification for supporting devices.
notificationSummary = resources.getString(R.string.datareporting_notification_summary);
}
String notificationAction = context.getResources().getString(R.string.datareporting_notification_action);
String notificationBigSummary = context.getResources().getString(R.string.datareporting_notification_summary);
String notificationAction = resources.getString(R.string.datareporting_notification_action);
String notificationBigSummary = resources.getString(R.string.datareporting_notification_summary);
// Make styled ticker text for display in notification bar.
String tickerString = context.getResources().getString(R.string.datareporting_notification_ticker_text);
String tickerString = resources.getString(R.string.datareporting_notification_ticker_text);
SpannableString tickerText = new SpannableString(tickerString);
// Bold the notification title of the ticker text, which is the same string as notificationTitle.
tickerText.setSpan(new StyleSpan(Typeface.BOLD), 0, notificationTitle.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
@ -81,17 +119,16 @@ public class DataReportingNotification {
notificationManager.notify(notificationID, notification);
// Record version and notification time.
SharedPreferences.Editor editor = dataPrefs.edit();
SharedPreferences.Editor editor = sharedPrefs.edit();
long now = System.currentTimeMillis();
editor.putLong(PREFS_POLICY_NOTIFIED_TIME, now);
editor.putInt(PREFS_POLICY_VERSION, DATA_REPORTING_VERSION);
// If healthreport is enabled, set default preference value.
if (AppConstants.MOZ_SERVICES_HEALTHREPORT) {
editor.putBoolean(GeckoPreferences.PREFS_HEALTHREPORT_UPLOAD_ENABLED, true);
}
editor.commit();
result = true;
} finally {
// We want to track any errors, so record notification outcome.
final String notificationEvent = TelemetryContract.Event.POLICY_NOTIFICATION_SUCCESS + result;
Telemetry.sendUIEvent(notificationEvent);
}
}
}

View File

@ -14,7 +14,10 @@ public interface TelemetryContract {
* Holds event names. Intended for use with
* Telemetry.sendUIEvent() as the "action" parameter.
*/
public interface Event {}
public interface Event {
// Outcome of data policy notification: can be true or false.
public static final String POLICY_NOTIFICATION_SUCCESS = "policynotification.success.1:";
}
/**
* Holds event methods. Intended for use in

View File

@ -9,7 +9,7 @@ import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.Utils;
public abstract class State {
public static final long CURRENT_VERSION = 1L;
public static final long CURRENT_VERSION = 2L;
public enum StateLabel {
Engaged,

View File

@ -7,18 +7,65 @@ package org.mozilla.gecko.fxa.login;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
import org.mozilla.gecko.browserid.DSACryptoImplementation;
import org.mozilla.gecko.browserid.RSACryptoImplementation;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.login.State.StateLabel;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.Utils;
/**
* Create {@link State} instances from serialized representations.
* <p>
* Version 1 recognizes 5 state labels (Engaged, Cohabiting, Married, Separated,
* Doghouse). In the Cohabiting and Married states, the associated key pairs are
* always RSA key pairs.
* <p>
* Version 2 is identical to version 1, except that in the Cohabiting and
* Married states, the associated keypairs are always DSA key pairs.
*/
public class StateFactory {
private static final String LOG_TAG = StateFactory.class.getSimpleName();
private static final int KEY_PAIR_SIZE_IN_BITS_V1 = 1024;
public static BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException {
// New key pairs are always DSA.
return DSACryptoImplementation.generateKeyPair(KEY_PAIR_SIZE_IN_BITS_V1);
}
protected static BrowserIDKeyPair keyPairFromJSONObjectV1(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
// V1 key pairs are RSA.
return RSACryptoImplementation.fromJSONObject(o);
}
protected static BrowserIDKeyPair keyPairFromJSONObjectV2(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
// V2 key pairs are DSA.
return DSACryptoImplementation.fromJSONObject(o);
}
public static State fromJSONObject(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
Long version = o.getLong("version");
if (version == null || version.intValue() != 1) {
throw new IllegalStateException("version must be 1");
if (version == null) {
throw new IllegalStateException("version must not be null");
}
final int v = version.intValue();
if (v == 2) {
// The most common case is the most recent version.
return fromJSONObjectV2(stateLabel, o);
}
if (v == 1) {
final State state = fromJSONObjectV1(stateLabel, o);
return migrateV1toV2(stateLabel, state);
}
throw new IllegalStateException("version must be in {1, 2}");
}
protected static State fromJSONObjectV1(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
switch (stateLabel) {
case Engaged:
return new Engaged(
@ -35,7 +82,7 @@ public class StateFactory {
Utils.hex2Byte(o.getString("sessionToken")),
Utils.hex2Byte(o.getString("kA")),
Utils.hex2Byte(o.getString("kB")),
RSACryptoImplementation.fromJSONObject(o.getObject("keyPair")));
keyPairFromJSONObjectV1(o.getObject("keyPair")));
case Married:
return new Married(
o.getString("email"),
@ -43,7 +90,7 @@ public class StateFactory {
Utils.hex2Byte(o.getString("sessionToken")),
Utils.hex2Byte(o.getString("kA")),
Utils.hex2Byte(o.getString("kB")),
RSACryptoImplementation.fromJSONObject(o.getObject("keyPair")),
keyPairFromJSONObjectV1(o.getObject("keyPair")),
o.getString("certificate"));
case Separated:
return new Separated(
@ -59,4 +106,81 @@ public class StateFactory {
throw new IllegalStateException("unrecognized state label: " + stateLabel);
}
}
/**
* Exactly the same as {@link fromJSONObjectV1}, except that all key pairs are DSA key pairs.
*/
protected static State fromJSONObjectV2(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
switch (stateLabel) {
case Cohabiting:
return new Cohabiting(
o.getString("email"),
o.getString("uid"),
Utils.hex2Byte(o.getString("sessionToken")),
Utils.hex2Byte(o.getString("kA")),
Utils.hex2Byte(o.getString("kB")),
keyPairFromJSONObjectV2(o.getObject("keyPair")));
case Married:
return new Married(
o.getString("email"),
o.getString("uid"),
Utils.hex2Byte(o.getString("sessionToken")),
Utils.hex2Byte(o.getString("kA")),
Utils.hex2Byte(o.getString("kB")),
keyPairFromJSONObjectV2(o.getObject("keyPair")),
o.getString("certificate"));
default:
return fromJSONObjectV1(stateLabel, o);
}
}
protected static void logMigration(State from, State to) {
if (!FxAccountConstants.LOG_PERSONAL_INFORMATION) {
return;
}
try {
FxAccountConstants.pii(LOG_TAG, "V1 persisted state is: " + from.toJSONObject().toJSONString());
} catch (Exception e) {
Logger.warn(LOG_TAG, "Error producing JSON representation of V1 state.", e);
}
FxAccountConstants.pii(LOG_TAG, "Generated new V2 state: " + to.toJSONObject().toJSONString());
}
protected static State migrateV1toV2(StateLabel stateLabel, State state) throws NoSuchAlgorithmException {
if (state == null) {
// This should never happen, but let's be careful.
Logger.error(LOG_TAG, "Got null state in migrateV1toV2; returning null.");
return state;
}
Logger.info(LOG_TAG, "Migrating V1 persisted State to V2; stateLabel: " + stateLabel);
// In V1, we use an RSA keyPair. In V2, we use a DSA keyPair. Only
// Cohabiting and Married states have a persisted keyPair at all; all
// other states need no conversion at all.
switch (stateLabel) {
case Cohabiting: {
// In the Cohabiting state, we can just generate a new key pair and move on.
final Cohabiting cohabiting = (Cohabiting) state;
final BrowserIDKeyPair keyPair = generateKeyPair();
final State migrated = new Cohabiting(cohabiting.email, cohabiting.uid, cohabiting.sessionToken, cohabiting.kA, cohabiting.kB, keyPair);
logMigration(cohabiting, migrated);
return migrated;
}
case Married: {
// In the Married state, we cannot only change the key pair: the stored
// certificate signs the public key of the now obsolete key pair. We
// regress to the Cohabiting state; the next time we sync, we should
// advance back to Married.
final Married married = (Married) state;
final BrowserIDKeyPair keyPair = generateKeyPair();
final State migrated = new Cohabiting(married.email, married.uid, married.sessionToken, married.kA, married.kB, keyPair);
logMigration(married, migrated);
return migrated;
}
default:
// Otherwise, V1 and V2 states are identical.
return state;
}
}
}

View File

@ -20,7 +20,6 @@ import org.mozilla.gecko.background.fxa.FxAccountClient20;
import org.mozilla.gecko.background.fxa.SkewHandler;
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
import org.mozilla.gecko.browserid.JSONWebTokenUtils;
import org.mozilla.gecko.browserid.RSACryptoImplementation;
import org.mozilla.gecko.browserid.verifier.BrowserIDRemoteVerifierClient;
import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierDelegate;
import org.mozilla.gecko.fxa.FirefoxAccounts;
@ -34,6 +33,7 @@ import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
import org.mozilla.gecko.fxa.login.Married;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.fxa.login.State.StateLabel;
import org.mozilla.gecko.fxa.login.StateFactory;
import org.mozilla.gecko.sync.BackoffHandler;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.GlobalSession;
@ -537,7 +537,7 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
@Override
public BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException {
return RSACryptoImplementation.generateKeyPair(1024);
return StateFactory.generateKeyPair();
}
@Override

View File

@ -23,7 +23,6 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@ -325,7 +324,6 @@ public class LayerView extends FrameLayout implements Tabs.OnTabsChangedListener
SurfaceHolder holder = mSurfaceView.getHolder();
holder.addCallback(new SurfaceListener());
holder.setFormat(PixelFormat.RGB_565);
}
}

View File

@ -22,6 +22,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
@ -86,6 +87,11 @@ abstract class HomeFragment extends Fragment {
menu.setHeaderTitle(info.getDisplayTitle());
// Hide ununsed menu items.
menu.findItem(R.id.top_sites_edit).setVisible(false);
menu.findItem(R.id.top_sites_pin).setVisible(false);
menu.findItem(R.id.top_sites_unpin).setVisible(false);
// Hide the "Edit" menuitem if this item isn't a bookmark,
// or if this is a reading list item.
if (!info.hasBookmarkId() || info.isInReadingList()) {
@ -152,7 +158,10 @@ abstract class HomeFragment extends Fragment {
flags |= Tabs.LOADURL_PRIVATE;
final String url = (info.isInReadingList() ? ReaderModeUtils.getAboutReaderForUrl(info.url) : info.url);
Tabs.getInstance().loadUrl(url, flags);
// Some pinned site items have "user-entered" urls. URLs entered in the PinSiteDialog are wrapped in
// a special URI until we can get a valid URL. If the url is a user-entered url, decode the URL before loading it.
Tabs.getInstance().loadUrl(decodeUserEnteredUrl(url), flags);
Toast.makeText(context, R.string.new_tab_opened, Toast.LENGTH_SHORT).show();
return true;
}
@ -218,6 +227,23 @@ abstract class HomeFragment extends Fragment {
return mCanLoadHint;
}
/**
* Given a url with a user-entered scheme, extract the
* scheme-specific component. For e.g, given "user-entered://www.google.com",
* this method returns "//www.google.com". If the passed url
* does not have a user-entered scheme, the same url will be returned.
*
* @param url to be decoded
* @return url component entered by user
*/
public static String decodeUserEnteredUrl(String url) {
Uri uri = Uri.parse(url);
if ("user-entered".equals(uri.getScheme())) {
return uri.getSchemeSpecificPart();
}
return url;
}
protected abstract void load();
protected boolean canLoad() {

View File

@ -215,13 +215,13 @@ public class ReadingListPanel extends HomeFragment {
@Override
public void bindView(View view, Context context, Cursor cursor) {
final TwoLinePageRow row = (TwoLinePageRow) view;
final ReadingListRow row = (ReadingListRow) view;
row.updateFromCursor(cursor);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(parent.getContext()).inflate(R.layout.bookmark_item_row, parent, false);
return LayoutInflater.from(parent.getContext()).inflate(R.layout.reading_list_item_row, parent, false);
}
}

View File

@ -0,0 +1,43 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.home;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.home.TwoLinePageRow;
import android.content.Context;
import android.util.AttributeSet;
public class ReadingListRow extends TwoLinePageRow {
public ReadingListRow(Context context) {
this(context, null);
}
public ReadingListRow(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void updateDisplayedUrl() {
String pageUrl = getUrl();
boolean isPrivate = Tabs.getInstance().getSelectedTab().isPrivate();
Tab tab = Tabs.getInstance().getFirstTabForUrl(pageUrl, isPrivate);
if (tab != null && AboutPages.isAboutReader(tab.getURL())) {
setUrl(R.string.switch_to_tab);
setSwitchToTabIcon(R.drawable.ic_url_bar_tab);
} else {
setUrl(pageUrl);
setSwitchToTabIcon(NO_ICON);
}
}
}

View File

@ -12,7 +12,6 @@ import org.mozilla.gecko.ThumbnailHelper;
import org.mozilla.gecko.db.BrowserDB.TopSitesCursorWrapper;
import org.mozilla.gecko.db.BrowserDB.URLColumns;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.util.StringUtils;
import android.content.Context;
import android.content.res.TypedArray;
@ -105,7 +104,7 @@ public class TopSitesGridView extends GridView {
TopSitesGridItemView row = (TopSitesGridItemView) view;
// Decode "user-entered" URLs before loading them.
String url = TopSitesPanel.decodeUserEnteredUrl(row.getUrl());
String url = HomeFragment.decodeUserEnteredUrl(row.getUrl());
// If the url is empty, the user can pin a site.
// If not, navigate to the page given by the url.
@ -125,7 +124,14 @@ public class TopSitesGridView extends GridView {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
Cursor cursor = (Cursor) parent.getItemAtPosition(position);
mContextMenuInfo = new TopSitesGridContextMenuInfo(view, position, id, cursor);
if (cursor == null) {
mContextMenuInfo = null;
return false;
}
mContextMenuInfo = new TopSitesGridContextMenuInfo(view, position, id);
updateContextMenuFromCursor(mContextMenuInfo, cursor);
return showContextMenuForChild(TopSitesGridView.this);
}
});
@ -221,6 +227,18 @@ public class TopSitesGridView extends GridView {
return mContextMenuInfo;
}
/*
* Update the fields of a TopSitesGridContextMenuInfo object
* from a cursor.
*
* @param info context menu info object to be updated
* @param cursor used to update the context menu info object
*/
private void updateContextMenuFromCursor(TopSitesGridContextMenuInfo info, Cursor cursor) {
info.url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
info.title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
info.isPinned = ((TopSitesCursorWrapper) cursor).isPinned();
}
/**
* Set an url open listener to be used by this view.
*
@ -240,29 +258,13 @@ public class TopSitesGridView extends GridView {
}
/**
* A ContextMenuInfo for TopBoomarksView that adds details from the cursor.
* Stores information regarding the creation of the context menu for a GridView item.
*/
public static class TopSitesGridContextMenuInfo extends AdapterContextMenuInfo {
public static class TopSitesGridContextMenuInfo extends HomeContextMenuInfo {
public boolean isPinned = false;
public String url;
public String title;
public boolean isPinned;
public TopSitesGridContextMenuInfo(View targetView, int position, long id, Cursor cursor) {
public TopSitesGridContextMenuInfo(View targetView, int position, long id) {
super(targetView, position, id);
if (cursor == null) {
return;
}
url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
isPinned = ((TopSitesCursorWrapper) cursor).isPinned();
}
public String getDisplayTitle() {
return TextUtils.isEmpty(title) ?
StringUtils.stripCommonSubdomains(StringUtils.stripScheme(url, StringUtils.UrlFlags.STRIP_HTTPS)) : title;
}
}
}

View File

@ -10,9 +10,7 @@ import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.db.BrowserContract.Combined;
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
import org.mozilla.gecko.db.BrowserDB;
@ -30,7 +28,6 @@ import org.mozilla.gecko.util.ThreadUtils;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
@ -52,7 +49,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
/**
* Fragment that displays frecency search results in a ListView.
@ -267,17 +263,21 @@ public class TopSitesPanel extends HomeFragment {
return;
}
// HomeFragment will handle the default case.
if (menuInfo instanceof HomeContextMenuInfo) {
super.onCreateContextMenu(menu, view, menuInfo);
}
if (!(menuInfo instanceof TopSitesGridContextMenuInfo)) {
// Long pressed item was not a Top Sites GridView item. Superclass
// can handle this.
super.onCreateContextMenu(menu, view, menuInfo);
return;
}
// Long pressed item was a Top Sites GridView item, handle it.
MenuInflater inflater = new MenuInflater(view.getContext());
inflater.inflate(R.menu.top_sites_contextmenu, menu);
inflater.inflate(R.menu.home_contextmenu, menu);
// Hide ununsed menu items.
menu.findItem(R.id.home_open_in_reader).setVisible(false);
menu.findItem(R.id.home_edit_bookmark).setVisible(false);
menu.findItem(R.id.home_remove).setVisible(false);
TopSitesGridContextMenuInfo info = (TopSitesGridContextMenuInfo) menuInfo;
menu.setHeaderTitle(info.getDisplayTitle());
@ -289,8 +289,8 @@ public class TopSitesPanel extends HomeFragment {
menu.findItem(R.id.top_sites_unpin).setVisible(false);
}
} else {
menu.findItem(R.id.top_sites_open_new_tab).setVisible(false);
menu.findItem(R.id.top_sites_open_private_tab).setVisible(false);
menu.findItem(R.id.home_open_new_tab).setVisible(false);
menu.findItem(R.id.home_open_private_tab).setVisible(false);
menu.findItem(R.id.top_sites_pin).setVisible(false);
menu.findItem(R.id.top_sites_unpin).setVisible(false);
}
@ -298,9 +298,13 @@ public class TopSitesPanel extends HomeFragment {
@Override
public boolean onContextItemSelected(MenuItem item) {
if (super.onContextItemSelected(item)) {
// HomeFragment was able to handle to selected item.
return true;
}
ContextMenuInfo menuInfo = item.getMenuInfo();
// HomeFragment will handle the default case.
if (menuInfo == null || !(menuInfo instanceof TopSitesGridContextMenuInfo)) {
return false;
}
@ -309,21 +313,6 @@ public class TopSitesPanel extends HomeFragment {
final Activity activity = getActivity();
final int itemId = item.getItemId();
if (itemId == R.id.top_sites_open_new_tab || itemId == R.id.top_sites_open_private_tab) {
if (info.url == null) {
Log.e(LOGTAG, "Can't open in new tab because URL is null");
return false;
}
int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND;
if (item.getItemId() == R.id.top_sites_open_private_tab)
flags |= Tabs.LOADURL_PRIVATE;
// Decode "user-entered" URLs before loading them.
Tabs.getInstance().loadUrl(decodeUserEnteredUrl(info.url), flags);
Toast.makeText(activity, R.string.new_tab_opened, Toast.LENGTH_SHORT).show();
return true;
}
if (itemId == R.id.top_sites_pin) {
final String url = info.url;
@ -361,28 +350,6 @@ public class TopSitesPanel extends HomeFragment {
return true;
}
if (itemId == R.id.home_share) {
if (info.url == null) {
Log.w(LOGTAG, "Share not enabled for context menu because URL is null.");
return false;
} else {
GeckoAppShell.openUriExternal(info.url, SHARE_MIME_TYPE, "", "",
Intent.ACTION_SEND, info.getDisplayTitle());
return true;
}
}
if (itemId == R.id.home_add_to_launcher) {
if (info.url == null) {
Log.w(LOGTAG, "Not enabling 'Add to home page' because URL is null.");
return false;
}
// Fetch an icon big enough for use as a home screen icon.
Favicons.getPreferredSizeFaviconForPage(info.url, new GeckoAppShell.CreateShortcutFaviconLoadedListener(info.url, info.getDisplayTitle()));
return true;
}
return false;
}
@ -403,14 +370,6 @@ public class TopSitesPanel extends HomeFragment {
return Uri.fromParts("user-entered", url, null).toString();
}
static String decodeUserEnteredUrl(String url) {
Uri uri = Uri.parse(url);
if ("user-entered".equals(uri.getScheme())) {
return uri.getSchemeSpecificPart();
}
return url;
}
/**
* Listener for editing pinned sites.
*/

View File

@ -30,7 +30,7 @@ import android.widget.TextView;
public class TwoLinePageRow extends LinearLayout
implements Tabs.OnTabsChangedListener {
private static final int NO_ICON = 0;
protected static final int NO_ICON = 0;
private final TextView mTitle;
private final TextView mUrl;
@ -122,15 +122,19 @@ public class TwoLinePageRow extends LinearLayout
mTitle.setText(text);
}
private void setUrl(String text) {
protected void setUrl(String text) {
mUrl.setText(text);
}
private void setUrl(int stringId) {
protected void setUrl(int stringId) {
mUrl.setText(stringId);
}
private void setSwitchToTabIcon(int iconId) {
protected String getUrl() {
return mPageUrl;
}
protected void setSwitchToTabIcon(int iconId) {
if (mSwitchToTabIconId == iconId) {
return;
}
@ -159,10 +163,10 @@ public class TwoLinePageRow extends LinearLayout
/**
* Replaces the page URL with "Switch to tab" if there is already a tab open with that URL.
* Only looks for tabs that are either private or non-private, depending on the current
* Only looks for tabs that are either private or non-private, depending on the current
* selected tab.
*/
private void updateDisplayedUrl() {
protected void updateDisplayedUrl() {
boolean isPrivate = Tabs.getInstance().getSelectedTab().isPrivate();
Tab tab = Tabs.getInstance().getFirstTabForUrl(mPageUrl, isPrivate);
if (!mShowIcons || tab == null) {

View File

@ -268,6 +268,7 @@ gbjar.sources += [
'home/PanelViewItemHandler.java',
'home/PinSiteDialog.java',
'home/ReadingListPanel.java',
'home/ReadingListRow.java',
'home/SearchEngine.java',
'home/SearchEngineRow.java',
'home/SearchLoader.java',

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<org.mozilla.gecko.home.ReadingListRow xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.BookmarkItemView"
android:layout_width="fill_parent"
android:layout_height="@dimen/page_row_height"
android:minHeight="@dimen/page_row_height"/>

View File

@ -17,6 +17,15 @@
<item android:id="@+id/home_share"
android:title="@string/contextmenu_share"/>
<item android:id="@+id/top_sites_edit"
android:title="@string/contextmenu_top_sites_edit"/>
<item android:id="@+id/top_sites_pin"
android:title="@string/contextmenu_top_sites_pin"/>
<item android:id="@+id/top_sites_unpin"
android:title="@string/contextmenu_top_sites_unpin"/>
<item android:id="@+id/home_edit_bookmark"
android:title="@string/contextmenu_edit_bookmark"/>

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/top_sites_open_new_tab"
android:title="@string/contextmenu_open_new_tab"/>
<item android:id="@+id/top_sites_open_private_tab"
android:title="@string/contextmenu_open_private_tab"/>
<item android:id="@+id/home_share"
android:title="@string/contextmenu_share"/>
<item android:id="@+id/top_sites_edit"
android:title="@string/contextmenu_top_sites_edit"/>
<item android:id="@+id/top_sites_pin"
android:title="@string/contextmenu_top_sites_pin"/>
<item android:id="@+id/top_sites_unpin"
android:title="@string/contextmenu_top_sites_unpin"/>
<item android:id="@+id/home_add_to_launcher"
android:title="@string/contextmenu_add_to_launcher"/>
</menu>

View File

@ -32,6 +32,7 @@ Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://services-common/hawkrequest.js");
Cu.import("resource://services-common/observers.js");
Cu.import("resource://gre/modules/Promise.jsm");
/*
@ -75,6 +76,10 @@ this.HawkClient.prototype = {
retryAfter = retryAfter ? parseInt(retryAfter) : retryAfter;
if (retryAfter) {
errorObj.retryAfter = retryAfter;
// and notify observers of the retry interval
if (this.observerPrefix) {
Observers.notify(this.observerPrefix + ":backoff:interval", retryAfter);
}
}
return errorObj;
},
@ -154,6 +159,11 @@ this.HawkClient.prototype = {
" - Status text: " + restResponse.statusText,
" - Response text: " + restResponse.body);
// All responses may have backoff headers, which are a server-side safety
// valve to allow slowing down clients without hurting performance.
self._maybeNotifyBackoff(restResponse, "x-weave-backoff");
self._maybeNotifyBackoff(restResponse, "x-backoff");
if (error) {
// When things really blow up, reconstruct an error object that follows
// the general format of the server on error responses.
@ -162,7 +172,7 @@ this.HawkClient.prototype = {
self._updateClockOffset(restResponse.headers["date"]);
if (status === 401 && retryOK) {
if (status === 401 && retryOK && !("retry-after" in restResponse.headers)) {
// Retry once if we were rejected due to a bad timestamp.
// Clock offset is adjusted already in the top of this function.
log.debug("Received 401 for " + path + ": retrying");
@ -209,6 +219,35 @@ this.HawkClient.prototype = {
return deferred.promise;
},
/*
* The prefix used for all notifications sent by this module. This
* allows the handler of notifications to be sure they are handling
* notifications for the service they expect.
*
* If not set, no notifications will be sent.
*/
observerPrefix: null,
// Given an optional header value, notify that a backoff has been requested.
_maybeNotifyBackoff: function (response, headerName) {
if (!this.observerPrefix) {
return;
}
let headerVal = response.headers[headerName];
if (!headerVal) {
return;
}
let backoffInterval;
try {
backoffInterval = parseInt(headerVal, 10);
} catch (ex) {
this._log.error("hawkclient response had invalid backoff value in '" +
headerName + "' header: " + headerVal);
return;
}
Observers.notify(this.observerPrefix + ":backoff:interval", backoffInterval);
},
// override points for testing.
newHAWKAuthenticatedRESTRequest: function(uri, credentials, extra) {
return new HAWKAuthenticatedRESTRequest(uri, credentials, extra);

View File

@ -329,7 +329,8 @@ TokenServerClient.prototype = {
return;
}
// Any response status can have an X-Backoff header.
// Any response status can have X-Backoff or X-Weave-Backoff headers.
this._maybeNotifyBackoff(response, "x-weave-backoff");
this._maybeNotifyBackoff(response, "x-backoff");
// The service shouldn't have any 3xx, so we don't need to handle those.
@ -413,8 +414,20 @@ TokenServerClient.prototype = {
});
},
/*
* The prefix used for all notifications sent by this module. This
* allows the handler of notifications to be sure they are handling
* notifications for the service they expect.
*
* If not set, no notifications will be sent.
*/
observerPrefix: null,
// Given an optional header value, notify that a backoff has been requested.
_maybeNotifyBackoff: function (response, headerName) {
if (!this.observerPrefix) {
return;
}
let headerVal = response.headers[headerName];
if (!headerVal) {
return;
@ -427,7 +440,7 @@ TokenServerClient.prototype = {
headerName + "' header: " + headerVal);
return;
}
Observers.notify("tokenserver:backoff:interval", backoffInterval);
Observers.notify(this.observerPrefix + ":backoff:interval", backoffInterval);
},
// override points for testing.

View File

@ -460,9 +460,26 @@ FxAccountsInternal.prototype = {
},
signOut: function signOut() {
this.abortExistingFlow();
this.currentAccountState.signedInUser = null; // clear in-memory cache
return this.signedInUserStorage.set(null).then(() => {
let currentState = this.currentAccountState;
let fxAccountsClient = this.fxAccountsClient;
let sessionToken;
return currentState.getUserAccountData().then(data => {
// Save the session token for use in the call to signOut below.
sessionToken = data && data.sessionToken;
this.abortExistingFlow();
this.currentAccountState.signedInUser = null; // clear in-memory cache
return this.signedInUserStorage.set(null);
}).then(() => {
// Wrap this in a promise so *any* errors in signOut won't
// block the local sign out. This is *not* returned.
Promise.resolve().then(() => {
// This can happen in the background and shouldn't block
// the user from signing out. The server must tolerate
// clients just disappearing, so this call should be best effort.
return fxAccountsClient.signOut(sessionToken);
}).then(null, err => {
log.error("Error during remote sign out of Firefox Accounts: " + err);
});
this.notifyObservers(ONLOGOUT_NOTIFICATION);
});
},
@ -497,15 +514,18 @@ FxAccountsInternal.prototype = {
}
if (!currentState.whenKeysReadyDeferred) {
currentState.whenKeysReadyDeferred = Promise.defer();
this.fetchAndUnwrapKeys(data.keyFetchToken).then(data => {
if (!data.kA || !data.kB) {
currentState.whenKeysReadyDeferred.reject(
new Error("user data missing kA or kB")
);
return;
}
currentState.whenKeysReadyDeferred.resolve(data);
});
this.fetchAndUnwrapKeys(data.keyFetchToken).then(
data => {
if (!data.kA || !data.kB) {
currentState.whenKeysReadyDeferred.reject(
new Error("user data missing kA or kB")
);
return;
}
currentState.whenKeysReadyDeferred.resolve(data);
},
err => currentState.whenKeysReadyDeferred.reject(err)
);
}
return currentState.whenKeysReadyDeferred.promise;
}).then(result => currentState.resolve(result));

View File

@ -23,6 +23,7 @@ this.FxAccountsClient = function(host = HOST) {
// The FxA auth server expects requests to certain endpoints to be authorized
// using Hawk.
this.hawk = new HawkClient(host);
this.hawk.observerPrefix = "FxA:hawk";
// Manage server backoff state. C.f.
// https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#backoff-protocol

View File

@ -77,6 +77,8 @@ function MockFxAccountsClient() {
this.signCertificate = function() { throw "no" };
this.signOut = function() { return Promise.resolve(); };
FxAccountsClient.apply(this);
}
MockFxAccountsClient.prototype = {
@ -537,6 +539,45 @@ add_test(function test_resend_email() {
});
});
add_test(function test_sign_out() {
do_test_pending();
let fxa = new MockFxAccounts();
let remoteSignOutCalled = false;
let client = fxa.internal.fxAccountsClient;
client.signOut = function() { remoteSignOutCalled = true; return Promise.resolve(); };
makeObserver(ONLOGOUT_NOTIFICATION, function() {
log.debug("test_sign_out_with_remote_error observed onlogout");
// user should be undefined after sign out
fxa.internal.getUserAccountData().then(user => {
do_check_eq(user, null);
do_check_true(remoteSignOutCalled);
do_test_finished();
run_next_test();
});
});
fxa.signOut();
});
add_test(function test_sign_out_with_remote_error() {
do_test_pending();
let fxa = new MockFxAccounts();
let client = fxa.internal.fxAccountsClient;
let remoteSignOutCalled = false;
// Force remote sign out to trigger an error
client.signOut = function() { remoteSignOutCalled = true; throw "Remote sign out error"; };
makeObserver(ONLOGOUT_NOTIFICATION, function() {
log.debug("test_sign_out_with_remote_error observed onlogout");
// user should be undefined after sign out
fxa.internal.getUserAccountData().then(user => {
do_check_eq(user, null);
do_check_true(remoteSignOutCalled);
do_test_finished();
run_next_test();
});
});
fxa.signOut();
});
/*
* End of tests.
* Utility functions follow.

View File

@ -387,6 +387,21 @@ thisPingDate
version
Integer version of this payload format. Currently only 1 is defined.
clientID
An identifier that identifies the client that is submitting data.
This property may not be present in older clients.
See :ref:`healthreport_identifiers` for more info on identifiers.
clientIDVersion
Integer version associated with the generation semantics for the
``clientID``.
If the value is ``1``, ``clientID`` is a randomly-generated UUID.
This property may not be present in older clients.
data
Object holding data constituting health report.

View File

@ -0,0 +1,83 @@
.. _healthreport_identifiers:
===========
Identifiers
===========
Firefox Health Report records some identifiers to keep track of clients
and uploaded documents.
Identifier Types
================
Document/Upload IDs
-------------------
A random UUID called the *Document ID* or *Upload ID* is generated when the FHR
client creates or uploads a new document.
When clients generate a new *Document ID*, they persist this ID to disk
**before** the upload attempt.
As part of the upload, the client sends all old *Document IDs* to the server
and asks the server to delete them. In well-behaving clients, the server
has a single record for each client with a randomly-changing *Document ID*.
Client IDs
----------
A *Client ID* is an identifier that **attempts** to uniquely identify an
individual FHR client. Please note the emphasis on *attempts* in that last
sentence: *Client IDs* do not guarantee uniqueness.
The *Client ID* is generated when the client first runs or as needed.
The *Client ID* is transferred to the server as part of every upload. The
server is thus able to affiliate multiple document uploads with a single
*Client ID*.
Client ID Versions
^^^^^^^^^^^^^^^^^^
The semantics for how a *Client ID* is generated are versioned.
Version 1
The *Client ID* is a randomly-generated UUID.
History of Identifiers
======================
In the beginning, there were just *Document IDs*. The thinking was clients
would clean up after themselves and leave at most 1 active document on the
server.
Unfortunately, this did not work out. Using brute force analysis to
deduplicate records on the server, a number of interesting patterns emerged.
Orphaning
Clients would upload a new payload while not deleting the old payload.
Divergent records
Records would share data up to a certain date and then the data would
almost completely diverge. This appears to be indicative of profile
copying.
Rollback
Records would share data up to a certain date. Each record in this set
would contain data for a day or two but no extra data. This could be
explained by filesystem rollback on the client.
A significant percentage of the records on the server belonged to
misbehaving clients. Identifying these records was extremely resource
intensive and error-prone. These records were undermining the ability
to use Firefox Health Report data.
Thus, the *Client ID* was born. The intent of the *Client ID* was to
uniquely identify clients so the extreme effort required and the
questionable reliability of deduplicating server data would become
problems of the past.
The *Client ID* was originally a randomly-generated UUID (version 1). This
allowed detection of orphaning and rollback. However, these version 1
*Client IDs* were still susceptible to use on multiple profiles and
machines if the profile was copied.

View File

@ -24,6 +24,7 @@ are very specific to what the Firefox Health Report feature requires.
architecture
dataformat
identifiers
Legal and Privacy Concerns
==========================

Some files were not shown because too many files have changed in this diff Show More