Merge fx-team to m-c. a=merge

CLOSED TREE
This commit is contained in:
Ryan VanderMeulen 2015-08-27 11:56:51 -04:00
commit 5435d38ece
66 changed files with 904 additions and 282 deletions

4
.gitignore vendored
View File

@ -52,8 +52,8 @@ parser/html/java/javaparser/
.settings/
# Python virtualenv artifacts.
python/psutil/*.so
python/psutil/*.pyd
python/psutil/**/*.so
python/psutil/**/*.pyd
python/psutil/build/
# Ignore chrome.manifest files from the devtools loader

View File

@ -9,6 +9,7 @@
*/
const GRID_BOTTOM_EXTRA = 7; // title's line-height extends 7px past the margin
const GRID_WIDTH_EXTRA = 1; // provide 1px buffer to allow for rounding error
const SPONSORED_TAG_BUFFER = 2; // 2px buffer to clip off top of sponsored tag
/**
* This singleton represents the grid that contains all sites.
@ -175,6 +176,15 @@ let gGrid = {
this._siteFragment.appendChild(site);
},
/**
* Test a tile at a given position for being pinned or history
* @param position Position in sites array
*/
_isHistoricalTile: function Grid_isHistoricalTile(aPos) {
let site = this.sites[aPos];
return site && (site.isPinned() || site.link && site.link.type == "history");
},
/**
* Make sure the correct number of rows and columns are visible
*/
@ -195,9 +205,50 @@ let gGrid = {
parseFloat(getComputedStyle(refCell).marginBottom);
this._cellWidth = refCell.offsetWidth + this._cellMargin;
}
this._node.style.height = this._computeHeight() + "px";
this._node.style.maxHeight = this._node.style.height;
let searchContainer = document.querySelector("#newtab-search-container");
// Save search-container margin height
if (this._searchContainerMargin === undefined) {
this._searchContainerMargin = parseFloat(getComputedStyle(searchContainer).marginBottom) +
parseFloat(getComputedStyle(searchContainer).marginTop);
}
// Find the number of rows we can place into view port
let availHeight = document.documentElement.clientHeight - this._cellMargin -
searchContainer.offsetHeight - this._searchContainerMargin;
let visibleRows = Math.floor(availHeight / this._cellHeight);
// Find the number of columns that fit into view port
let maxGridWidth = gGridPrefs.gridColumns * this._cellWidth + GRID_WIDTH_EXTRA;
// available width is current grid width, but no greater than maxGridWidth
let availWidth = Math.min(document.querySelector("#newtab-grid").clientWidth,
maxGridWidth);
// finally get the number of columns we can fit into view port
let gridColumns = Math.floor(availWidth / this._cellWidth);
// walk sites backwords until a pinned or history tile is found or visibleRows reached
let tileIndex = Math.min(gGridPrefs.gridRows * gridColumns, this.sites.length) - 1;
while (tileIndex >= visibleRows * gridColumns) {
if (this._isHistoricalTile(tileIndex)) {
break;
}
tileIndex --;
}
// Compute the actual number of grid rows we will display (potentially
// with a scroll bar). tileIndex now points to a historical tile with
// heighest index or to the last index of the visible row, if none found
// Dividing tileIndex by number of tiles in a column gives the rows
let gridRows = Math.floor(tileIndex / gridColumns) + 1;
// we need to set grid width, for otherwise the scrollbar may shrink
// the grid when shown and cause grid layout to be different from
// what being computed above. This, in turn, may cause scrollbar shown
// for directory tiles, and introduce jitter when grid width is aligned
// exactly on the column boundary
this._node.style.width = gridColumns * this._cellWidth + "px";
this._node.style.maxWidth = gGridPrefs.gridColumns * this._cellWidth +
GRID_WIDTH_EXTRA + "px";
this._node.style.height = this._computeHeight() + "px";
this._node.style.maxHeight = this._computeHeight(gridRows) - SPONSORED_TAG_BUFFER + "px";
}
};

View File

@ -49,3 +49,4 @@ support-files =
[browser_newtab_update.js]
[browser_newtab_bug1145428.js]
[browser_newtab_bug1178586.js]
[browser_newtab_bug1194895.js]

View File

@ -0,0 +1,141 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const PRELOAD_PREF = "browser.newtab.preload";
const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
function populateDirectoryTiles() {
let directoryTiles = [];
let i = 0;
while (i++ < 14) {
directoryTiles.push({
directoryId: i,
url: "http://example" + i + ".com/",
enhancedImageURI: "data:image/png;base64,helloWORLD",
title: "dirtitle" + i,
type: "affiliate"
});
}
return directoryTiles;
}
gDirectorySource = "data:application/json," + JSON.stringify({
"directory": populateDirectoryTiles()
});
function runTests() {
let origEnhanced = NewTabUtils.allPages.enhanced;
let origCompareLinks = NewTabUtils.links.compareLinks;
registerCleanupFunction(() => {
Services.prefs.clearUserPref(PRELOAD_PREF);
Services.prefs.clearUserPref(PREF_NEWTAB_ROWS);
Services.prefs.clearUserPref(PREF_NEWTAB_COLUMNS);
NewTabUtils.allPages.enhanced = origEnhanced;
NewTabUtils.links.compareLinks = origCompareLinks;
});
// turn off preload to ensure grid updates on every setLinks
Services.prefs.setBoolPref(PRELOAD_PREF, false);
// set newtab to have three columns only
Services.prefs.setIntPref(PREF_NEWTAB_COLUMNS, 3);
Services.prefs.setIntPref(PREF_NEWTAB_ROWS, 5);
yield addNewTabPageTab();
yield customizeNewTabPage("enhanced"); // Toggle enhanced off
// Testing history tiles
// two rows of tiles should always fit on any screen
yield setLinks("0,1,2,3,4,5");
yield addNewTabPageTab();
// should do not see scrollbar since tiles fit into visible space
checkGrid("0,1,2,3,4,5");
ok(!hasScrollbar(), "no scrollbar");
// add enough tiles to cause extra two rows and observe scrollbar
yield setLinks("0,1,2,3,4,5,6,7,8,9");
yield addNewTabPageTab();
checkGrid("0,1,2,3,4,5,6,7,8,9");
ok(hasScrollbar(), "document has scrollbar");
// pin the last tile to make it stay at the bottom of the newtab
pinCell(9);
// block first 6 tiles, which should not remove the scroll bar
// since the last tile is pinned in the nineth position
for (let i = 0; i < 6; i++) {
yield blockCell(0);
}
yield addNewTabPageTab();
checkGrid("6,7,8,,,,,,,9p");
ok(hasScrollbar(), "document has scrollbar when tile is pinned to the last row");
// unpin the site: this will move tile up and make scrollbar disappear
yield unpinCell(9);
yield addNewTabPageTab();
checkGrid("6,7,8,9");
ok(!hasScrollbar(), "no scrollbar when bottom row tile is unpinned");
// reset everything to clean slate
NewTabUtils.restore();
// Testing directory tiles
yield customizeNewTabPage("enhanced"); // Toggle enhanced on
// setup page with no history tiles to test directory only display
yield setLinks([]);
yield addNewTabPageTab();
ok(!hasScrollbar(), "no scrollbar for directory tiles");
// introduce one history tile - it should occupy the last
// available slot at the bottom of newtab and cause scrollbar
yield setLinks("41");
yield addNewTabPageTab();
ok(hasScrollbar(), "adding low frecency history site causes scrollbar");
// set PREF_NEWTAB_ROWS to 4, that should clip off the history tile
// and remove scroll bar
Services.prefs.setIntPref(PREF_NEWTAB_ROWS, 4);
yield addNewTabPageTab();
ok(!hasScrollbar(), "no scrollbar if history tiles falls past max rows");
// restore max rows and watch scrollbar re-appear
Services.prefs.setIntPref(PREF_NEWTAB_ROWS, 5);
yield addNewTabPageTab();
ok(hasScrollbar(), "scrollbar is back when max rows allow for bottom history tile");
// block that history tile, and watch scrollbar disappear
yield blockCell(14);
yield addNewTabPageTab();
ok(!hasScrollbar(), "no scrollbar after bottom history tiles is blocked");
// Test well-populated user history - newtab has highly-frecent history sites
// redefine compareLinks to always choose history tiles first
NewTabUtils.links.compareLinks = function (aLink1, aLink2) {
if (aLink1.type == aLink2.type) {
return aLink2.frecency - aLink1.frecency ||
aLink2.lastVisitDate - aLink1.lastVisitDate;
}
else {
if (aLink2.type == "history") {
return 1;
}
else {
return -1;
}
}
};
// add a row of history tiles, directory tiles will be clipped off, hence no scrollbar
yield setLinks("31,32,33");
yield addNewTabPageTab();
ok(!hasScrollbar(), "no scrollbar when directory tiles follow history tiles");
// fill first four rows with history tiles and observer scrollbar
yield setLinks("30,31,32,33,34,35,36,37,38,39");
yield addNewTabPageTab();
ok(hasScrollbar(), "scrollbar appears when history tiles need extra row");
}

View File

@ -785,3 +785,11 @@ function customizeNewTabPage(aTheme) {
promise.then(TestRunner.next);
}
/**
* Reports presence of a scrollbar
*/
function hasScrollbar() {
let docElement = getContentDocument().documentElement;
return docElement.scrollHeight > docElement.clientHeight;
}

View File

@ -77,4 +77,5 @@ skip-if = !crashreporter
[browser_pageInfo_plugins.js]
[browser_pluginCrashReportNonDeterminism.js]
skip-if = !crashreporter || os == 'linux' # Bug 1152811
[browser_private_clicktoplay.js]

View File

@ -0,0 +1,235 @@
let rootDir = getRootDirectory(gTestPath);
const gTestRoot = rootDir;
const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
let gTestBrowser = null;
let gNextTest = null;
let gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
Components.utils.import("resource://gre/modules/Services.jsm");
let gPrivateWindow = null;
let gPrivateBrowser = null;
function pageLoad(aEvent) {
// The plugin events are async dispatched and can come after the load event
// This just allows the events to fire before we then go on to test the states
executeSoon(gNextTest);
gNextTest = null;
}
function prepareTest(nextTest, url, window) {
gNextTest = nextTest;
if (!window)
window = gTestBrowser.contentWindow;
window.location = url;
}
function finishTest() {
clearAllPluginPermissions();
gTestBrowser.removeEventListener("load", pageLoad, true);
gBrowser.removeCurrentTab();
if (gPrivateWindow) {
gPrivateWindow.close();
}
window.focus();
finish();
}
function createPrivateWindow(nextTest, url) {
gPrivateWindow = OpenBrowserWindow({private: true});
ok(!!gPrivateWindow, "should have created a private window.");
whenDelayedStartupFinished(gPrivateWindow, function() {
gPrivateBrowser = gPrivateWindow.getBrowser().selectedBrowser;
gPrivateBrowser.addEventListener("load", pageLoad, true);
gNextTest = function() {
prepareTest(nextTest, url, gPrivateBrowser.contentWindow);
};
});
}
function whenDelayedStartupFinished(aWindow, aCallback) {
Services.obs.addObserver(function observer(aSubject, aTopic) {
if (aWindow == aSubject) {
Services.obs.removeObserver(observer, aTopic);
executeSoon(aCallback);
}
}, "browser-delayed-startup-finished", false);
}
function test() {
waitForExplicitFinish();
registerCleanupFunction(function() {
clearAllPluginPermissions();
getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_ENABLED;
});
let newTab = gBrowser.addTab();
gBrowser.selectedTab = newTab;
gTestBrowser = gBrowser.selectedBrowser;
gTestBrowser.addEventListener("load", pageLoad, true);
Services.prefs.setBoolPref("plugins.click_to_play", true);
getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
gNextTest = test1a;
}
function test1a() {
createPrivateWindow(test1b, gHttpTestRoot + "plugin_test.html");
}
function test1b() {
let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser);
ok(popupNotification, "Test 1b, Should have a click-to-play notification");
let plugin = gPrivateBrowser.contentDocument.getElementById("test");
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
ok(!objLoadingContent.activated, "Test 1b, Plugin should not be activated");
// Check the button status
let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel,
"Shown");
popupNotification.reshow();
promiseShown.then(() => {
let button1 = gPrivateWindow.PopupNotifications.panel.firstChild._primaryButton;
let button2 = gPrivateWindow.PopupNotifications.panel.firstChild._secondaryButton;
is(button1.getAttribute("action"), "_singleActivateNow", "Test 1b, Blocked plugin in private window should have a activate now button");
ok(button2.hidden, "Test 1b, Blocked plugin in a private window should not have a secondary button")
gPrivateWindow.close();
prepareTest(test2a, gHttpTestRoot + "plugin_test.html");
});
}
function test2a() {
// enable test plugin on this site
let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
ok(popupNotification, "Test 2a, Should have a click-to-play notification");
let plugin = gTestBrowser.contentDocument.getElementById("test");
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
ok(!objLoadingContent.activated, "Test 2a, Plugin should not be activated");
// Simulate clicking the "Allow Now" button.
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
"Shown");
popupNotification.reshow();
promiseShown.then(() => {
PopupNotifications.panel.firstChild._secondaryButton.click();
let condition = function() objLoadingContent.activated;
waitForCondition(condition, test2b, "Test 2a, Waited too long for plugin to activate");
});
}
function test2b() {
createPrivateWindow(test2c, gHttpTestRoot + "plugin_test.html");
}
function test2c() {
let promise = TestUtils.topicObserved("PopupNotifications-updateNotShowing");
promise.then(() => {
let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser);
ok(popupNotification, "Test 2c, Should have a click-to-play notification");
let plugin = gPrivateBrowser.contentDocument.getElementById("test");
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
ok(objLoadingContent.activated, "Test 2c, Plugin should be activated");
// Check the button status
let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel,
"Shown");
popupNotification.reshow();
promiseShown.then(() => {
let buttonContainer = gPrivateWindow.PopupNotifications.panel.firstChild._buttonContainer;
ok(buttonContainer.hidden, "Test 2c, Activated plugin in a private window should not have visible buttons");
clearAllPluginPermissions();
gPrivateWindow.close();
prepareTest(test3a, gHttpTestRoot + "plugin_test.html");
});
});
}
function test3a() {
// enable test plugin on this site
let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
ok(popupNotification, "Test 3a, Should have a click-to-play notification");
let plugin = gTestBrowser.contentDocument.getElementById("test");
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
ok(!objLoadingContent.activated, "Test 3a, Plugin should not be activated");
// Simulate clicking the "Allow Always" button.
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
"Shown");
popupNotification.reshow();
promiseShown.then(() => {
PopupNotifications.panel.firstChild._secondaryButton.click();
let condition = function() objLoadingContent.activated;
waitForCondition(condition, test3b, "Test 3a, Waited too long for plugin to activate");
});
}
function test3b() {
createPrivateWindow(test3c, gHttpTestRoot + "plugin_test.html");
}
function test3c() {
let promise = TestUtils.topicObserved("PopupNotifications-updateNotShowing");
promise.then(() => {
let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser);
ok(popupNotification, "Test 3c, Should have a click-to-play notification");
// Check the button status
let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel,
"Shown");
popupNotification.reshow();
promiseShown.then(() => {
let buttonContainer = gPrivateWindow.PopupNotifications.panel.firstChild._buttonContainer;
ok(buttonContainer.hidden, "Test 3c, Activated plugin in a private window should not have visible buttons");
prepareTest(test3d, gHttpTestRoot + "plugin_two_types.html", gPrivateBrowser.contentWindow);
});
});
}
function test3d() {
let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser);
ok(popupNotification, "Test 3d, Should have a click-to-play notification");
// Check the list item status
let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel,
"Shown");
popupNotification.reshow();
promiseShown.then(() => {
let doc = gPrivateWindow.document;
for (let item of gPrivateWindow.PopupNotifications.panel.firstChild.childNodes) {
let allowalways = doc.getAnonymousElementByAttribute(item, "anonid", "allowalways");
ok(allowalways, "Test 3d, should have list item for allow always");
let allownow = doc.getAnonymousElementByAttribute(item, "anonid", "allownow");
ok(allownow, "Test 3d, should have list item for allow now");
let block = doc.getAnonymousElementByAttribute(item, "anonid", "block");
ok(block, "Test 3d, should have list item for block");
if (item.action.pluginName === "Test") {
is(item.value, "allowalways", "Test 3d, Plugin should bet set to 'allow always'");
ok(!allowalways.hidden, "Test 3d, Plugin set to 'always allow' should have a visible 'always allow' action.");
ok(allownow.hidden, "Test 3d, Plugin set to 'always allow' should have an invisible 'allow now' action.");
ok(block.hidden, "Test 3d, Plugin set to 'always allow' should have an invisible 'block' action.");
} else if (item.action.pluginName === "Second Test") {
is(item.value, "block", "Test 3d, Second plugin should bet set to 'block'");
ok(allowalways.hidden, "Test 3d, Plugin set to 'block' should have a visible 'always allow' action.");
ok(!allownow.hidden, "Test 3d, Plugin set to 'block' should have a visible 'allow now' action.");
ok(!block.hidden, "Test 3d, Plugin set to 'block' should have a visible 'block' action.");
} else {
ok(false, "Test 3d, Unexpected plugin '"+item.action.pluginName+"'");
}
}
finishTest();
});
}

View File

@ -2402,6 +2402,20 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
}
document.getAnonymousElementByAttribute(this, "anonid", "center-item-warning-label").value = warningString;
let chromeWin = window.QueryInterface(Ci.nsIDOMChromeWindow);
let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(chromeWin);
if (isWindowPrivate) {
// TODO: temporary compromise of hiding some privacy leaks, remove once bug 892487 is fixed
let allowalways = document.getAnonymousElementByAttribute(this, "anonid", "allowalways");
let block = document.getAnonymousElementByAttribute(this, "anonid", "block");
let allownow = document.getAnonymousElementByAttribute(this, "anonid", "allownow");
allowalways.hidden = curState !== "allowalways";
block.hidden = curState !== "block";
allownow.hidden = curState === "allowalways";
}
if (url || linkHandler) {
link.value = linkString;
if (url) {
@ -2577,6 +2591,8 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
<body><![CDATA[
var action = this._items[0].action;
var prePath = action.pluginPermissionPrePath;
let chromeWin = window.QueryInterface(Ci.nsIDOMChromeWindow);
let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(chromeWin);
let label, linkLabel, linkUrl, button1, button2;
@ -2615,6 +2631,11 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
default:
Cu.reportError(Error("Unexpected blocklist state"));
}
// TODO: temporary compromise, remove this once bug 892487 is fixed
if (isWindowPrivate) {
this._buttonContainer.hidden = true;
}
}
else if (action.pluginTag.enabledState == Ci.nsIPluginTag.STATE_DISABLED) {
let linkElement =
@ -2670,6 +2691,12 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
default:
Cu.reportError(Error("Unexpected blocklist state"));
}
// TODO: temporary compromise, remove this once bug 892487 is fixed
if (isWindowPrivate) {
button1.default = true;
this._secondaryButton.hidden = true;
}
}
this._setupDescription(label, action.pluginName, prePath);
this._setupLink(linkLabel, action.detailsLink);

View File

@ -491,6 +491,7 @@ loop.roomViews = (function(mozL10n) {
disabled: checked,
label: checkboxLabel,
onChange: this.handleCheckboxChange,
useEllipsis: true,
value: location}),
React.createElement("form", {onSubmit: this.handleFormSubmit},
React.createElement("input", {className: "room-context-name",
@ -508,7 +509,7 @@ loop.roomViews = (function(mozL10n) {
React.createElement("textarea", {className: "room-context-comments",
onKeyDown: this.handleTextareaKeyDown,
placeholder: mozL10n.get("context_edit_comments_placeholder"),
rows: "3", type: "text",
rows: "2", type: "text",
valueLink: this.linkState("newRoomDescription")})
),
React.createElement("button", {className: "btn btn-info",

View File

@ -491,6 +491,7 @@ loop.roomViews = (function(mozL10n) {
disabled={checked}
label={checkboxLabel}
onChange={this.handleCheckboxChange}
useEllipsis={true}
value={location} />
<form onSubmit={this.handleFormSubmit}>
<input className="room-context-name"
@ -508,7 +509,7 @@ loop.roomViews = (function(mozL10n) {
<textarea className="room-context-comments"
onKeyDown={this.handleTextareaKeyDown}
placeholder={mozL10n.get("context_edit_comments_placeholder")}
rows="3" type="text"
rows="2" type="text"
valueLink={this.linkState("newRoomDescription")} />
</form>
<button className="btn btn-info"

View File

@ -524,6 +524,12 @@ html[dir="rtl"] .checkbox {
background-image: url("../img/check.svg#check-disabled");
}
.checkbox-label.ellipsis {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
/* ContextUrlView classes */
.context-content {

View File

@ -897,6 +897,10 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
background-color: #dbf7ff;
}
.showing-room-name > .text-chat-entries > .text-chat-scroller > .context-url-view-wrapper {
padding-top: 0;
}
.room-context {
background: rgba(0,0,0,.8);
border-top: 2px solid #444;
@ -945,6 +949,7 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
.room-context > .checkbox-wrapper {
margin-bottom: .5em;
width: 100%;
}
.room-context-label {
@ -1638,7 +1643,6 @@ html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
/* 18px for indent of .text-chat-arrow, 1px for border of .text-chat-entry > p,
0.5rem for padding of .text-chat-entry > p */
padding: calc(18px - 1px - 0.5rem);
padding-bottom: 0px;
}
.text-chat-entry.special > p {

View File

@ -462,7 +462,6 @@ loop.shared.actions = (function() {
// roomContextUrls: Array - Optional.
// roomDescription: String - Optional.
// roomName: String - Optional.
roomOwner: String,
roomToken: String,
roomUrl: String,
socialShareProviders: Array
@ -477,7 +476,6 @@ loop.shared.actions = (function() {
UpdateRoomInfo: Action.define("updateRoomInfo", {
// description: String - Optional.
// roomName: String - Optional.
roomOwner: String,
roomUrl: String
// urls: Array - Optional.
// See https://wiki.mozilla.org/Loop/Architecture/Context#Format_of_context.value

View File

@ -273,7 +273,6 @@ loop.store.ActiveRoomStore = (function() {
roomContextUrls: roomData.decryptedContext.urls,
roomDescription: roomData.decryptedContext.description,
roomName: roomData.decryptedContext.roomName,
roomOwner: roomData.roomOwner,
roomUrl: roomData.roomUrl,
socialShareProviders: this._mozLoop.getSocialShareProviders()
}));
@ -325,7 +324,6 @@ loop.store.ActiveRoomStore = (function() {
}
var roomInfoData = new sharedActions.UpdateRoomInfo({
roomOwner: result.roomOwner,
roomUrl: result.roomUrl
});
@ -396,7 +394,6 @@ loop.store.ActiveRoomStore = (function() {
roomContextUrls: actionData.roomContextUrls,
roomDescription: actionData.roomDescription,
roomName: actionData.roomName,
roomOwner: actionData.roomOwner,
roomState: ROOM_STATES.READY,
roomToken: actionData.roomToken,
roomUrl: actionData.roomUrl,
@ -420,7 +417,6 @@ loop.store.ActiveRoomStore = (function() {
*/
updateRoomInfo: function(actionData) {
var newState = {
roomOwner: actionData.roomOwner,
roomUrl: actionData.roomUrl
};
// Iterate over the optional fields that _may_ be present on the actionData
@ -456,7 +452,6 @@ loop.store.ActiveRoomStore = (function() {
urls: roomData.decryptedContext.urls,
description: roomData.decryptedContext.description,
roomName: roomData.decryptedContext.roomName,
roomOwner: roomData.roomOwner,
roomUrl: roomData.roomUrl
}));
},

View File

@ -378,9 +378,13 @@ loop.shared.views.chat = (function(mozL10n) {
render: function() {
var messageList;
var showingRoomName = false;
if (this.props.showRoomName) {
messageList = this.state.messageList;
showingRoomName = this.state.messageList.some(function(item) {
return item.contentType === CHAT_CONTENT_TYPES.ROOM_NAME;
});
} else {
messageList = this.state.messageList.filter(function(item) {
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL ||
@ -394,6 +398,7 @@ loop.shared.views.chat = (function(mozL10n) {
});
var textChatViewClasses = React.addons.classSet({
"showing-room-name": showingRoomName,
"text-chat-view": true,
"text-chat-disabled": !this.state.textChatEnabled,
"text-chat-entries-empty": !messageList.length

View File

@ -378,9 +378,13 @@ loop.shared.views.chat = (function(mozL10n) {
render: function() {
var messageList;
var showingRoomName = false;
if (this.props.showRoomName) {
messageList = this.state.messageList;
showingRoomName = this.state.messageList.some(function(item) {
return item.contentType === CHAT_CONTENT_TYPES.ROOM_NAME;
});
} else {
messageList = this.state.messageList.filter(function(item) {
return item.type !== CHAT_MESSAGE_TYPES.SPECIAL ||
@ -394,6 +398,7 @@ loop.shared.views.chat = (function(mozL10n) {
});
var textChatViewClasses = React.addons.classSet({
"showing-room-name": showingRoomName,
"text-chat-view": true,
"text-chat-disabled": !this.state.textChatEnabled,
"text-chat-entries-empty": !messageList.length

View File

@ -857,6 +857,9 @@ loop.shared.views = (function(_, mozL10n) {
disabled: React.PropTypes.bool,
label: React.PropTypes.string,
onChange: React.PropTypes.func.isRequired,
// If true, this will cause the label to be cut off at the end of the
// first line with an ellipsis, and a tooltip supplied.
useEllipsis: React.PropTypes.bool,
// If `value` is not supplied, the consumer should rely on the boolean
// `checked` state changes.
value: React.PropTypes.string
@ -868,6 +871,7 @@ loop.shared.views = (function(_, mozL10n) {
checked: false,
disabled: false,
label: null,
useEllipsis: false,
value: ""
};
},
@ -910,6 +914,11 @@ loop.shared.views = (function(_, mozL10n) {
checked: this.state.checked,
disabled: this.props.disabled
};
var labelClasses = {
"checkbox-label": true,
"ellipsis": this.props.useEllipsis
};
if (this.props.additionalClass) {
wrapperClasses[this.props.additionalClass] = true;
}
@ -918,9 +927,13 @@ loop.shared.views = (function(_, mozL10n) {
disabled: this.props.disabled,
onClick: this._handleClick},
React.createElement("div", {className: cx(checkClasses)}),
this.props.label ?
React.createElement("label", null, this.props.label) :
null
this.props.label ?
React.createElement("div", {className: cx(labelClasses),
title: this.props.useEllipsis ? this.props.label : ""},
this.props.label
) : null
)
);
}

View File

@ -857,6 +857,9 @@ loop.shared.views = (function(_, mozL10n) {
disabled: React.PropTypes.bool,
label: React.PropTypes.string,
onChange: React.PropTypes.func.isRequired,
// If true, this will cause the label to be cut off at the end of the
// first line with an ellipsis, and a tooltip supplied.
useEllipsis: React.PropTypes.bool,
// If `value` is not supplied, the consumer should rely on the boolean
// `checked` state changes.
value: React.PropTypes.string
@ -868,6 +871,7 @@ loop.shared.views = (function(_, mozL10n) {
checked: false,
disabled: false,
label: null,
useEllipsis: false,
value: ""
};
},
@ -910,6 +914,11 @@ loop.shared.views = (function(_, mozL10n) {
checked: this.state.checked,
disabled: this.props.disabled
};
var labelClasses = {
"checkbox-label": true,
"ellipsis": this.props.useEllipsis
};
if (this.props.additionalClass) {
wrapperClasses[this.props.additionalClass] = true;
}
@ -918,9 +927,13 @@ loop.shared.views = (function(_, mozL10n) {
disabled={this.props.disabled}
onClick={this._handleClick}>
<div className={cx(checkClasses)} />
{this.props.label ?
<label>{this.props.label}</label> :
null}
{
this.props.label ?
<div className={cx(labelClasses)}
title={this.props.useEllipsis ? this.props.label : ""}>
{this.props.label}
</div> : null
}
</div>
);
}

View File

@ -91,7 +91,6 @@ loop.StandaloneMozLoop = (function(mozL10n) {
try {
// We currently only require things we need rather than everything possible.
callback(null, validate(responseData, {
roomOwner: String,
roomUrl: String
}));
} catch (err) {

View File

@ -310,7 +310,6 @@ describe("loop.store.ActiveRoomStore", function () {
decryptedContext: {
roomName: "Monkeys"
},
roomOwner: "Alfred",
roomUrl: "http://invalid"
};
@ -353,7 +352,6 @@ describe("loop.store.ActiveRoomStore", function () {
roomDescription: undefined,
roomToken: fakeToken,
roomName: fakeRoomData.decryptedContext.roomName,
roomOwner: fakeRoomData.roomOwner,
roomUrl: fakeRoomData.roomUrl,
socialShareProviders: []
}));
@ -426,7 +424,6 @@ describe("loop.store.ActiveRoomStore", function () {
it("should dispatch an UpdateRoomInfo message with 'no data' failure if neither roomName nor context are supplied", function() {
fakeMozLoop.rooms.get.callsArgWith(1, null, {
roomOwner: "Dan",
roomUrl: "http://invalid"
});
@ -436,7 +433,6 @@ describe("loop.store.ActiveRoomStore", function () {
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo({
roomInfoFailure: ROOM_INFO_FAILURES.NO_DATA,
roomOwner: "Dan",
roomState: ROOM_STATES.READY,
roomUrl: "http://invalid"
}));
@ -446,8 +442,7 @@ describe("loop.store.ActiveRoomStore", function () {
it("should dispatch UpdateRoomInfo if mozLoop.rooms.get is successful", function() {
var roomDetails = {
roomName: "fakeName",
roomUrl: "http://invalid",
roomOwner: "gavin"
roomUrl: "http://invalid"
};
fakeMozLoop.rooms.get.callsArgWith(1, null, roomDetails);
@ -470,12 +465,10 @@ describe("loop.store.ActiveRoomStore", function () {
context: {
value: "fakeContext"
},
roomUrl: "http://invalid",
roomOwner: "Mark"
roomUrl: "http://invalid"
};
expectedDetails = {
roomUrl: "http://invalid",
roomOwner: "Mark"
roomUrl: "http://invalid"
};
fakeMozLoop.rooms.get.callsArgWith(1, null, roomDetails);
@ -597,7 +590,6 @@ describe("loop.store.ActiveRoomStore", function () {
beforeEach(function() {
fakeRoomInfo = {
roomName: "Its a room",
roomOwner: "Me",
roomToken: "fakeToken",
roomUrl: "http://invalid",
socialShareProviders: []
@ -615,7 +607,6 @@ describe("loop.store.ActiveRoomStore", function () {
var state = store.getStoreState();
expect(state.roomName).eql(fakeRoomInfo.roomName);
expect(state.roomOwner).eql(fakeRoomInfo.roomOwner);
expect(state.roomToken).eql(fakeRoomInfo.roomToken);
expect(state.roomUrl).eql(fakeRoomInfo.roomUrl);
expect(state.socialShareProviders).eql([]);
@ -628,7 +619,6 @@ describe("loop.store.ActiveRoomStore", function () {
beforeEach(function() {
fakeRoomInfo = {
roomName: "Its a room",
roomOwner: "Me",
roomUrl: "http://invalid",
urls: [{
description: "fake site",
@ -643,7 +633,6 @@ describe("loop.store.ActiveRoomStore", function () {
var state = store.getStoreState();
expect(state.roomName).eql(fakeRoomInfo.roomName);
expect(state.roomOwner).eql(fakeRoomInfo.roomOwner);
expect(state.roomUrl).eql(fakeRoomInfo.roomUrl);
expect(state.roomContextUrls).eql(fakeRoomInfo.urls);
});
@ -1503,7 +1492,6 @@ describe("loop.store.ActiveRoomStore", function () {
beforeEach(function() {
store.setupRoomInfo(new sharedActions.SetupRoomInfo({
roomName: "Its a room",
roomOwner: "Me",
roomToken: "fakeToken",
roomUrl: "http://invalid",
socialShareProviders: []
@ -1521,7 +1509,6 @@ describe("loop.store.ActiveRoomStore", function () {
fake: "url"
}
},
roomOwner: "you",
roomUrl: "original"
};
@ -1532,7 +1519,6 @@ describe("loop.store.ActiveRoomStore", function () {
new sharedActions.UpdateRoomInfo({
description: "fakeDescription",
roomName: fakeRoomData.decryptedContext.roomName,
roomOwner: fakeRoomData.roomOwner,
roomUrl: fakeRoomData.roomUrl,
urls: {
fake: "url"
@ -1549,7 +1535,6 @@ describe("loop.store.ActiveRoomStore", function () {
fake: "url"
}
},
roomOwner: "you",
roomUrl: "original"
};
@ -1564,7 +1549,6 @@ describe("loop.store.ActiveRoomStore", function () {
decryptedContext: {
roomName: "Its a room"
},
roomOwner: "Me",
roomToken: "fakeToken",
roomUrl: "http://invalid"
};

View File

@ -156,7 +156,6 @@ describe("loop.store.TextChatStore", function () {
it("should add the room name to the list", function() {
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "Let's share!",
roomOwner: "Mark",
roomUrl: "fake"
}));
@ -173,7 +172,6 @@ describe("loop.store.TextChatStore", function () {
it("should add the context to the list", function() {
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "Let's share!",
roomOwner: "Mark",
roomUrl: "fake",
urls: [{
description: "A wonderful event",
@ -206,7 +204,6 @@ describe("loop.store.TextChatStore", function () {
it("should not add more than one context message", function() {
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomOwner: "Mark",
roomUrl: "fake",
urls: [{
description: "A wonderful event",
@ -228,7 +225,6 @@ describe("loop.store.TextChatStore", function () {
}]);
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomOwner: "Mark",
roomUrl: "fake",
urls: [{
description: "A wonderful event2",
@ -253,7 +249,6 @@ describe("loop.store.TextChatStore", function () {
it("should not dispatch a LoopChatMessageAppended event", function() {
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "Let's share!",
roomOwner: "Mark",
roomUrl: "fake"
}));

View File

@ -453,6 +453,40 @@ describe("loop.shared.views.TextChatView", function () {
expect(view.getDOMNode().classList.contains("text-chat-entries-empty")).eql(false);
});
it("should add a showing room name class when the view shows room names and it has a room name", function() {
view = mountTestComponent({
showRoomName: true
});
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "Study",
roomUrl: "Fake"
}));
expect(view.getDOMNode().classList.contains("showing-room-name")).eql(true);
});
it("shouldn't add a showing room name class when the view doesn't show room names", function() {
view = mountTestComponent({
showRoomName: false
});
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "Study",
roomUrl: "Fake"
}));
expect(view.getDOMNode().classList.contains("showing-room-name")).eql(false);
});
it("shouldn't add a showing room name class when the view doesn't have a name", function() {
view = mountTestComponent({
showRoomName: true
});
expect(view.getDOMNode().classList.contains("showing-room-name")).eql(false);
});
it("should show timestamps from msgs sent more than 1 min apart", function() {
view = mountTestComponent();
@ -540,7 +574,6 @@ describe("loop.shared.views.TextChatView", function () {
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "A wonderful surprise!",
roomOwner: "Chris",
roomUrl: "Fake"
}));
@ -558,7 +591,6 @@ describe("loop.shared.views.TextChatView", function () {
store.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "A Very Long Conversation Name",
roomOwner: "fake",
roomUrl: "http://showcase",
urls: [{
description: "A wonderful page!",

View File

@ -941,7 +941,7 @@ describe("loop.shared.views", function() {
view = mountTestComponent({ label: "Some label" });
var node = view.getDOMNode();
expect(node.lastChild.localName).to.eql("label");
expect(node.lastChild.localName).to.eql("div");
expect(node.lastChild.textContent).to.eql("Some label");
});
@ -974,6 +974,26 @@ describe("loop.shared.views", function() {
var checkbox = view.getDOMNode().querySelector(".checkbox");
expect(checkbox.classList.contains("checked")).eql(false);
});
it("should add an ellipsis class when the prop is set", function() {
view = mountTestComponent({
label: "Some label",
useEllipsis: true
});
var label = view.getDOMNode().querySelector(".checkbox-label");
expect(label.classList.contains("ellipsis")).eql(true);
});
it("should not add an ellipsis class when the prop is not set", function() {
view = mountTestComponent({
label: "Some label",
useEllipsis: false
});
var label = view.getDOMNode().querySelector(".checkbox-label");
expect(label.classList.contains("ellipsis")).eql(false);
});
});
describe("#_handleClick", function() {

View File

@ -83,8 +83,7 @@ describe("loop.StandaloneMozLoop", function() {
var roomDetails = {
roomName: "fakeName",
roomUrl: "http://invalid",
roomOwner: "gavin"
roomUrl: "http://invalid"
};
requests[0].respond(200, {"Content-Type": "application/json"},

View File

@ -15,7 +15,6 @@ var fakeRooms = [
}]
},
"roomUrl": "http://localhost:3000/rooms/_nxD4V4FflQ",
"roomOwner": "Alexis",
"maxSize": 2,
"creationTime": 1405517546,
"ctime": 1405517546,
@ -28,7 +27,6 @@ var fakeRooms = [
"roomName": "Second Room Name"
},
"roomUrl": "http://localhost:3000/rooms/QzBbvGmIZWU",
"roomOwner": "Alexis",
"maxSize": 2,
"creationTime": 1405517546,
"ctime": 1405517546,
@ -41,7 +39,6 @@ var fakeRooms = [
"roomName": "UX Discussion"
},
"roomUrl": "http://localhost:3000/rooms/3jKS_Els9IU",
"roomOwner": "Alexis",
"maxSize": 2,
"clientMaxSize": 2,
"creationTime": 1405517546,

View File

@ -385,7 +385,6 @@
// Update the text chat store with the room info.
textChatStore.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "A Very Long Conversation Name",
roomOwner: "fake",
roomUrl: "http://showcase",
urls: [{
description: "A wonderful page!",

View File

@ -385,7 +385,6 @@
// Update the text chat store with the room info.
textChatStore.updateRoomInfo(new sharedActions.UpdateRoomInfo({
roomName: "A Very Long Conversation Name",
roomOwner: "fake",
roomUrl: "http://showcase",
urls: [{
description: "A wonderful page!",

View File

@ -27,7 +27,6 @@ support-files =
[browser_bookmarkProperties_addKeywordForThisSearch.js]
[browser_bookmarkProperties_readOnlyRoot.js]
[browser_bookmarksProperties.js]
skip-if = (os == 'win' && os_version == "6.2") # Bug 1178709
[browser_drag_bookmarks_on_toolbar.js]
skip-if = e10s # Bug ?????? - test fails - "Number of dragged items should be the same. - Got 0, expected 1"
[browser_forgetthissite_single.js]

View File

@ -404,9 +404,8 @@ function open_properties_dialog() {
if (aTopic != "domwindowopened")
return;
ww.unregisterNotification(windowObserver);
var win = aSubject.QueryInterface(Ci.nsIDOMWindow);
win.addEventListener("focus", function (event) {
win.removeEventListener("focus", arguments.callee, false);
let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
waitForFocus(() => {
// Windows has been loaded, execute our test now.
executeSoon(function () {
// Ensure overlay is loaded
@ -418,7 +417,7 @@ function open_properties_dialog() {
ok(false, "An error occured during test run: " + ex.message);
}
});
}, false);
}, win);
}
ww.registerNotification(windowObserver);

View File

@ -260,7 +260,7 @@ let gSyncPane = {
gSyncPane.signIn();
return false;
});
setEventListener("verifiedManage", "command",
setEventListener("verifiedManage", "click",
gSyncPane.manageFirefoxAccount);
setEventListener("fxaUnlinkButton", "click", function () {
gSyncPane.unlinkFirefoxAccount(true);

View File

@ -36,9 +36,9 @@ body[globalTpEnabled] .showGlobalTpDisabled {
background-size: 47px 26px;
padding-inline-start: 87px;
color: white;
font-size: 24pt;
font-size: 1.5em;
font-weight: 200;
line-height: 60pt;
line-height: 2.5em;
}
#main {
@ -62,7 +62,7 @@ body[globalTpEnabled] .showGlobalTpDisabled {
}
ul {
margin-bottom: 0;
margin: 0;
padding-inline-start: 8px;
}
@ -100,10 +100,11 @@ li {
}
#list-area > div {
margin-inline-end: 1em;
margin-inline-end: 3em;
}
.list-header {
margin-bottom: 0.4em;
font-weight: bold;
}
@ -156,8 +157,7 @@ li {
}
#startTour {
display: inline-block;
width: 16em;
display: block;
border-radius: 2px;
background-color: var(--in-content-primary-button-background);
color: var(--in-content-selected-text);

View File

@ -34,7 +34,7 @@
<div id="bar" class="showPrivate">&privateBrowsing.title;</div>
<div id="main" class="showPrivate">
<div id="privateBrowsingSection"
style="width: &aboutPrivateBrowsing.width;">
style="width: &aboutPrivateBrowsing.width1;">
<div class="sectionHeader">&aboutPrivateBrowsing.title;</div>
<p>&aboutPrivateBrowsing.subtitle;</p>
<div id="list-area">
@ -59,7 +59,7 @@
<a id="learnMore" target="_blank">&aboutPrivateBrowsing.learnMore;</a>
</div>
<div id="trackingProtectionSection"
style="width: &trackingProtection.width;">
style="width: &trackingProtection.width1;">
<div class="sectionHeader">&trackingProtection.title;
<span id="tpEnabled"
style="width: &trackingProtection.state.width;"

View File

@ -237,7 +237,7 @@ HTMLBreadcrumbs.prototype = {
// We make sure that the targeted node is selected
// because we want to use the nodemenu that only works
// for inspector.selection
this.selection.setNodeFront(node, "breadcrumbs");
this.navigateTo(node);
// Build a list of extra menu items that will be appended at the end of the
// inspector node context menu.
@ -262,10 +262,10 @@ HTMLBreadcrumbs.prototype = {
item.setAttribute("type", "radio");
item.setAttribute("label", this.prettyPrintNodeAsText(nodes[i]));
let selection = this.selection;
let self = this;
item.onmouseup = (function(node) {
return function() {
selection.setNodeFront(node, "breadcrumbs");
self.navigateTo(node);
};
})(nodes[i]);
@ -353,39 +353,37 @@ HTMLBreadcrumbs.prototype = {
* @param {DOMEvent} event.
*/
handleKeyPress: function(event) {
let node = null;
this._keyPromise = this._keyPromise || promise.resolve(null);
let navigate = promise.resolve(null);
this._keyPromise = (this._keyPromise || promise.resolve(null)).then(() => {
switch (event.keyCode) {
case this.chromeWin.KeyEvent.DOM_VK_LEFT:
if (this.currentIndex != 0) {
node = promise.resolve(this.nodeHierarchy[this.currentIndex - 1].node);
navigate = promise.resolve(
this.nodeHierarchy[this.currentIndex - 1].node);
}
break;
case this.chromeWin.KeyEvent.DOM_VK_RIGHT:
if (this.currentIndex < this.nodeHierarchy.length - 1) {
node = promise.resolve(this.nodeHierarchy[this.currentIndex + 1].node);
navigate = promise.resolve(
this.nodeHierarchy[this.currentIndex + 1].node);
}
break;
case this.chromeWin.KeyEvent.DOM_VK_UP:
node = this.walker.previousSibling(this.selection.nodeFront, {
navigate = this.walker.previousSibling(this.selection.nodeFront, {
whatToShow: Ci.nsIDOMNodeFilter.SHOW_ELEMENT
});
break;
case this.chromeWin.KeyEvent.DOM_VK_DOWN:
node = this.walker.nextSibling(this.selection.nodeFront, {
navigate = this.walker.nextSibling(this.selection.nodeFront, {
whatToShow: Ci.nsIDOMNodeFilter.SHOW_ELEMENT
});
break;
}
return node.then((node) => {
if (node) {
this.selection.setNodeFront(node, "breadcrumbs");
}
});
return navigate.then(node => this.navigateTo(node));
});
event.preventDefault();
event.stopPropagation();
},
@ -470,6 +468,14 @@ HTMLBreadcrumbs.prototype = {
}
},
navigateTo: function(node) {
if (node) {
this.selection.setNodeFront(node, "breadcrumbs");
} else {
this.inspector.emit("breadcrumbs-navigation-cancelled");
}
},
/**
* Build a button representing the node.
* @param {NodeFront} node The node from the page.
@ -490,7 +496,7 @@ HTMLBreadcrumbs.prototype = {
};
button.onBreadcrumbsClick = () => {
this.selection.setNodeFront(node, "breadcrumbs");
this.navigateTo(node);
};
button.onBreadcrumbsHover = () => {

View File

@ -31,6 +31,7 @@ support-files =
[browser_inspector_breadcrumbs.js]
[browser_inspector_breadcrumbs_highlight_hover.js]
[browser_inspector_breadcrumbs_keybinding.js]
[browser_inspector_breadcrumbs_menu.js]
[browser_inspector_breadcrumbs_mutations.js]
[browser_inspector_delete-selected-node-01.js]

View File

@ -0,0 +1,109 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the breadcrumbs keybindings work.
const TEST_URI = TEST_URL_ROOT + "doc_inspector_breadcrumbs.html";
const TEST_DATA = [{
desc: "Pressing left should select the parent <body>",
key: "VK_LEFT",
newSelection: "body"
}, {
desc: "Pressing left again should select the parent <html>",
key: "VK_LEFT",
newSelection: "html"
}, {
desc: "Pressing left again should stay on root <html>",
key: "VK_LEFT",
newSelection: "html"
}, {
desc: "Pressing right should go down to <body>",
key: "VK_RIGHT",
newSelection: "body"
}, {
desc: "Pressing right again should go down to #i2",
key: "VK_RIGHT",
newSelection: "#i2"
}, {
desc: "Continue down to #i21",
key: "VK_RIGHT",
newSelection: "#i21"
}, {
desc: "Continue down to #i211",
key: "VK_RIGHT",
newSelection: "#i211"
}, {
desc: "Continue down to #i2111",
key: "VK_RIGHT",
newSelection: "#i2111"
}, {
desc: "Pressing right once more should stay at leaf node #i2111",
key: "VK_RIGHT",
newSelection: "#i2111"
}, {
desc: "Go back to #i211",
key: "VK_LEFT",
newSelection: "#i211"
}, {
desc: "Go back to #i21",
key: "VK_LEFT",
newSelection: "#i21"
}, {
desc: "Pressing down should move to next sibling #i22",
key: "VK_DOWN",
newSelection: "#i22"
}, {
desc: "Pressing up should move to previous sibling #i21",
key: "VK_UP",
newSelection: "#i21"
}, {
desc: "Pressing up again should stay on #i21 as there's no previous sibling",
key: "VK_UP",
newSelection: "#i21"
}, {
desc: "Going back down to #i22",
key: "VK_DOWN",
newSelection: "#i22"
}, {
desc: "Pressing down again should stay on #i22 as there's no next sibling",
key: "VK_DOWN",
newSelection: "#i22"
}];
add_task(function*() {
let {inspector} = yield openInspectorForURL(TEST_URI);
info("Selecting the test node");
yield selectNode("#i2", inspector);
info("Clicking on the corresponding breadcrumbs node to focus it");
let container = inspector.panelDoc.getElementById("inspector-breadcrumbs");
let button = container.querySelector("button[checked]");
button.click();
let currentSelection = "#id2";
for (let {desc, key, newSelection} of TEST_DATA) {
info(desc);
let onUpdated;
if (newSelection !== currentSelection) {
info("Expecting a new node to be selected");
onUpdated = inspector.once("breadcrumbs-updated");
} else {
info("Expecting the same node to remain selected");
onUpdated = inspector.once("breadcrumbs-navigation-cancelled");
}
EventUtils.synthesizeKey(key, {});
yield onUpdated;
let newNodeFront = yield getNodeFront(newSelection, inspector);
is(newNodeFront, inspector.selection.nodeFront,
"The current selection is correct");
currentSelection = newSelection;
}
});

View File

@ -1056,6 +1056,10 @@ let UI = {
},
updateToolboxFullscreenState: function() {
if (projectList.sidebarsEnabled) {
return;
}
let panel = document.querySelector("#deck").selectedPanel;
let nbox = document.querySelector("#notificationbox");
if (panel && panel.id == "deck-panel-details" &&

View File

@ -51,7 +51,7 @@
ok(win.UI.toolboxIframe, "Toolbox iframe exists");
let nbox = win.document.querySelector("#notificationbox");
ok(nbox.hasAttribute("toolboxfullscreen"), "Toolbox is fullsreen");
ok(!nbox.hasAttribute("toolboxfullscreen"), "Toolbox is not fullscreen");
win.Cmds.showRuntimeDetails();

View File

@ -8,10 +8,13 @@
<!ENTITY privateBrowsing.title "Private Browsing">
<!-- LOCALIZATION NOTE (aboutPrivateBrowsing.width):
Width of the Private Browsing section.
<!-- LOCALIZATION NOTE (aboutPrivateBrowsing.width1):
Width of the Private Browsing section. This should depend primarily on the
length of the headers and text, but should be roughly 1.5 times the width
of the Tracking Protection section, and in general not much larger than
30em to prevent the sections from wrapping on smaller window sizes.
-->
<!ENTITY aboutPrivateBrowsing.width "25em">
<!ENTITY aboutPrivateBrowsing.width1 "30em">
<!-- LOCALIZATION NOTE (aboutPrivateBrowsing.subtitle,
aboutPrivateBrowsing.info.forgotten, aboutPrivateBrowsing.info.kept):
@ -36,11 +39,15 @@
<!ENTITY aboutPrivateBrowsing.note1 "Please note that your employer or Internet service provider can still track the pages you visit.">
<!ENTITY aboutPrivateBrowsing.learnMore "Learn More.">
<!-- LOCALIZATION NOTE (trackingProtection.width):
Width of the Tracking Protection section. This should be enough to
accommodate the title as well as the enabled or disabled indicator.
<!-- LOCALIZATION NOTE (trackingProtection.width1):
Width of the Tracking Protection section. It is fine for the enabled or
disabled indicator or the words in the title to wrap to the next line, but
you can expand or reduce this section to fit better, as long as the width
of the Private Browsing section is roughly 1.5 times the width of this one.
Note that the required space may vary between platforms because fonts are
different, so testing on Windows, Mac, and Linux is encouraged.
-->
<!ENTITY trackingProtection.width "22em">
<!ENTITY trackingProtection.width1 "22em">
<!ENTITY trackingProtection.title "Tracking Protection">
<!-- LOCALIZATION NOTE (trackingProtection.state.width):

View File

@ -546,6 +546,10 @@ box.requests-menu-status[code^="5"] {
-moz-padding-start: 1em;
}
.theme-dark #security-error-message {
color: var(--theme-selection-color);
}
#security-tabpanel {
overflow: auto;
}

View File

@ -28,6 +28,9 @@
padding: 3px 5px;
margin-inline-end: 4px;
overflow: hidden;
/* The latter two properties have a transition to handle the delayed hiding of
the forward button when hovered. */
transition: background-color 150ms ease, padding-left, padding-right;
}
#identity-box:hover,
@ -57,17 +60,15 @@
border-radius: 0;
}
@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar > #identity-box {
transition: padding-left, padding-right;
}
@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
padding-inline-start: calc(var(--backbutton-urlbar-overlap) + 4px);
}
@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
/* forward button hiding is delayed when hovered */
transition-delay: 100s;
/* Forward button hiding is delayed when hovered, so we should use the same
delay for the identity box. We handle both horizontal paddings (for LTR and
RTL), the latter two delays here are for padding-left and padding-right. */
transition-delay: 0s, 100s, 100s;
}
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {

View File

@ -115,7 +115,7 @@
.newtab-cell:not([ignorehover]) .newtab-link:hover,
.newtab-site[dragged] {
border: 2px solid white;
box-shadow: 0 0 6px 2px #4cb1ff;
box-shadow: 0 0 6px 1px #add6ff;
margin: -2px;
}
@ -168,8 +168,8 @@
.newtab-site:hover .newtab-title {
color: white;
background-color: black;
border: 1px solid black;
background-color: #333;
border: 1px solid #333;
border-top: 1px solid white;
}

View File

@ -438,9 +438,8 @@
#tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([visuallyselected]):not([beforehovered]):not(:hover)::after {
width: 1px;
-moz-margin-start: -1px;
padding-top: calc(var(--tab-separator-margin) + 1px);
padding-bottom: var(--tab-separator-margin);
background-clip: content-box;
margin-top: calc(var(--tab-separator-margin) + 1px);
margin-bottom: var(--tab-separator-margin);
background-color: currentColor;
opacity: var(--tab-separator-opacity);
content: "";

View File

@ -8,22 +8,23 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.db.BrowserContract.Clients;
import org.mozilla.gecko.db.BrowserContract.Tabs;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
public class TabsProvider extends SharedBrowserDatabaseProvider {
private static final long ONE_DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
private static final long ONE_WEEK_IN_MILLISECONDS = 7 * ONE_DAY_IN_MILLISECONDS;
private static final long THREE_WEEKS_IN_MILLISECONDS = 3 * ONE_WEEK_IN_MILLISECONDS;
static final String TABLE_TABS = "tabs";
static final String TABLE_CLIENTS = "clients";
@ -33,6 +34,31 @@ public class TabsProvider extends SharedBrowserDatabaseProvider {
static final int CLIENTS_ID = 603;
static final int CLIENTS_RECENCY = 604;
// Exclude clients that are more than three weeks old and also any duplicates that are older than one week old.
static final String EXCLUDE_STALE_CLIENTS_SUBQUERY =
"(SELECT " + Clients.GUID +
", " + Clients.NAME +
", " + Clients.LAST_MODIFIED +
", " + Clients.DEVICE_TYPE +
" FROM " + TABLE_CLIENTS +
" WHERE " + Clients.LAST_MODIFIED + " > %1$s " +
" GROUP BY " + Clients.NAME +
" UNION ALL " +
" SELECT c." + Clients.GUID + " AS " + Clients.GUID +
", c." + Clients.NAME + " AS " + Clients.NAME +
", c." + Clients.LAST_MODIFIED + " AS " + Clients.LAST_MODIFIED +
", c." + Clients.DEVICE_TYPE + " AS " + Clients.DEVICE_TYPE +
" FROM " + TABLE_CLIENTS + " AS c " +
" JOIN (" +
" SELECT " + Clients.GUID +
", " + "MAX( " + Clients.LAST_MODIFIED + ") AS " + Clients.LAST_MODIFIED +
" FROM " + TABLE_CLIENTS +
" WHERE (" + Clients.LAST_MODIFIED + " < %1$s" + " AND " + Clients.LAST_MODIFIED + " > %2$s) AND " +
Clients.NAME + " NOT IN " + "( SELECT " + Clients.NAME + " FROM " + TABLE_CLIENTS + " WHERE " + Clients.LAST_MODIFIED + " > %1$s)" +
" GROUP BY " + Clients.NAME +
") AS c2" +
" ON c." + Clients.GUID + " = c2." + Clients.GUID + ")";
static final String DEFAULT_TABS_SORT_ORDER = Clients.LAST_MODIFIED + " DESC, " + Tabs.LAST_USED + " DESC";
static final String DEFAULT_CLIENTS_SORT_ORDER = Clients.LAST_MODIFIED + " DESC";
static final String DEFAULT_CLIENTS_RECENCY_SORT_ORDER = "COALESCE(MAX(" + Tabs.LAST_USED + "), " + Clients.LAST_MODIFIED + ") DESC";
@ -295,8 +321,15 @@ public class TabsProvider extends SharedBrowserDatabaseProvider {
debug("Using sort order " + sortOrder + ".");
}
final long oneWeekAgo = System.currentTimeMillis() - ONE_WEEK_IN_MILLISECONDS;
final long threeWeeksAgo = System.currentTimeMillis() - THREE_WEEKS_IN_MILLISECONDS;
final String excludeStaleClientsTable = String.format(EXCLUDE_STALE_CLIENTS_SUBQUERY, oneWeekAgo, threeWeeksAgo);
qb.setProjectionMap(CLIENTS_RECENCY_PROJECTION_MAP);
qb.setTables(TABLE_CLIENTS + " LEFT OUTER JOIN " + TABLE_TABS +
// Use a subquery to quietly exclude stale duplicate client records.
qb.setTables(excludeStaleClientsTable + " AS " + TABLE_CLIENTS + " LEFT OUTER JOIN " + TABLE_TABS +
" ON (" + projectColumn(TABLE_CLIENTS, Clients.GUID) +
" = " + projectColumn(TABLE_TABS,Tabs.CLIENT_GUID) + ")");
groupBy = projectColumn(TABLE_CLIENTS, Clients.GUID);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 B

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 190 B

View File

@ -7,12 +7,12 @@
<!-- pressed state -->
<item android:state_pressed="true"
android:drawable="@drawable/tablet_tab_close_active"/>
android:drawable="@drawable/tab_close_active"/>
<item android:state_checked="true"
android:drawable="@drawable/tablet_tab_close_active"/>
android:drawable="@drawable/tab_close_active"/>
<!-- normal mode -->
<item android:drawable="@drawable/tablet_tab_close"/>
<item android:drawable="@drawable/tab_close"/>
</selector>

View File

@ -46,7 +46,7 @@
android:background="@android:color/transparent"
android:scaleType="center"
android:contentDescription="@string/close_tab"
android:src="@drawable/tablet_tab_close"
android:src="@drawable/tab_close"
android:duplicateParentState="true"/>
</merge>

View File

@ -1,72 +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/. -->
<org.mozilla.gecko.tabs.TabsLayoutItemView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/TabsItem"
android:focusable="true"
android:id="@+id/info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="6dip"
android:paddingBottom="6dip"
android:paddingLeft="1dip"
android:paddingRight="1dip"
android:gravity="center">
<!-- We set state_private on this View dynamically in TabsListLayout. -->
<org.mozilla.gecko.widget.TabThumbnailWrapper
android:id="@+id/wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="6dip"
android:padding="4dip"
android:background="@drawable/tab_thumbnail"
android:duplicateParentState="true">
<org.mozilla.gecko.tabs.TabsPanelThumbnailView android:id="@+id/thumbnail"
android:layout_width="@dimen/tab_thumbnail_width"
android:layout_height="@dimen/tab_thumbnail_height"/>
<LinearLayout android:layout_width="@dimen/tab_thumbnail_width"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="#EFFF"
android:layout_below="@id/thumbnail"
android:duplicateParentState="true">
<TextView android:id="@+id/title"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:padding="4dip"
style="@style/TabLayoutItemTextAppearance"
android:textSize="12sp"
android:textColor="@color/placeholder_active_grey"
android:singleLine="true"
android:duplicateParentState="true"/>
<ImageButton android:id="@+id/audio_playing"
android:visibility="gone"
android:layout_width="20dip"
android:layout_height="match_parent"
android:background="@drawable/action_bar_button_inverse"
android:scaleType="center"
android:contentDescription="@string/tab_audio_playing"
android:src="@drawable/tab_audio_playing"/>
<ImageButton android:id="@+id/close"
style="@style/TabsItemClose"
android:layout_width="32dip"
android:layout_height="match_parent"
android:background="@drawable/action_bar_button_inverse"
android:scaleType="center"
android:contentDescription="@string/close_tab"
android:src="@drawable/tab_close"/>
</LinearLayout>
</org.mozilla.gecko.widget.TabThumbnailWrapper>
</org.mozilla.gecko.tabs.TabsLayoutItemView>

View File

@ -1,72 +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/. -->
<org.mozilla.gecko.tabs.TabsLayoutItemView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/TabsItem"
android:focusable="true"
android:id="@+id/info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="12dip"
android:paddingTop="6dip"
android:paddingBottom="6dip"
android:background="@drawable/tab_row">
<!-- We set state_private on this View dynamically in TabsListLayout. -->
<org.mozilla.gecko.widget.TabThumbnailWrapper
android:id="@+id/wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dip"
android:background="@drawable/tab_thumbnail"
android:duplicateParentState="true">
<org.mozilla.gecko.tabs.TabsPanelThumbnailView android:id="@+id/thumbnail"
android:layout_width="@dimen/tab_thumbnail_width"
android:layout_height="@dimen/tab_thumbnail_height"/>
</org.mozilla.gecko.widget.TabThumbnailWrapper>
<LinearLayout android:layout_width="0dip"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_weight="1.0"
android:paddingTop="4dip"
android:paddingLeft="8dip"
android:paddingRight="4dip">
<TextView android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1.0"
style="@style/TabLayoutItemTextAppearance"
android:textColor="#FFFFFFFF"
android:textSize="14sp"
android:singleLine="false"
android:maxLines="4"
android:duplicateParentState="true"/>
<ImageButton android:id="@+id/audio_playing"
android:visibility="gone"
android:layout_width="20dip"
android:layout_height="20dip"
android:gravity="bottom"
android:background="@drawable/action_bar_button_inverse"
android:scaleType="center"
android:contentDescription="@string/tab_audio_playing"
android:src="@drawable/tab_audio_playing"/>
</LinearLayout>
<ImageButton android:id="@+id/close"
style="@style/TabsItemClose"
android:layout_width="34dip"
android:layout_height="match_parent"
android:background="@drawable/action_bar_button_inverse"
android:scaleType="center"
android:contentDescription="@string/close_tab"
android:src="@drawable/tab_close"/>
</org.mozilla.gecko.tabs.TabsLayoutItemView>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<item type="layout" name="tabs_layout_item_view">@layout/tabs_item_cell</item>
</resources>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<item type="layout" name="tabs_layout_item_view">@layout/tabs_item_cell</item>
</resources>

View File

@ -4,13 +4,10 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<item type="layout" name="tabs_layout_item_view">@layout/tabs_item_row</item>
<!-- These items are v11+ resources but are referenced in code shipped with
API 9 builds. Since v11+ resources don't ship on API 9 builds, in order
for the resource ID to be found (and thus compilation to succeed), we
provide dummy values below. -->
<item type="layout" name="tab_strip">@null</item>
<item type="layout" name="tablet_tabs_item_cell">@null</item>
<item type="layout" name="tabs_panel_back_button">@null</item>
</resources>

View File

@ -8,6 +8,7 @@ package org.mozilla.gecko.restrictions;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.restrictions.RestrictedProfileConfiguration;
import org.mozilla.gecko.restrictions.Restriction;
import org.mozilla.gecko.sync.setup.Constants;
import android.annotation.TargetApi;
import android.app.Activity;
@ -53,6 +54,10 @@ public class RestrictionProvider extends BroadcastReceiver {
ArrayList<RestrictionEntry> entries = new ArrayList<RestrictionEntry>();
for (Restriction restriction : RestrictedProfileConfiguration.DEFAULT_RESTRICTIONS) {
if (restriction == Restriction.DISALLOW_LOCATION_SERVICE && !AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED) {
continue;
}
RestrictionEntry entry = createRestrictionEntryWithDefaultValue(context, restriction,
oldRestrictions.getBoolean(restriction.name, true));
entries.add(entry);

View File

@ -421,7 +421,7 @@ class TabsGridLayout extends GridView
final private Button.OnClickListener mCloseClickListener;
public TabsGridLayoutAdapter(Context context) {
super(context, R.layout.tablet_tabs_item_cell);
super(context, R.layout.tabs_layout_item_view);
mCloseClickListener = new Button.OnClickListener() {
@Override

View File

@ -102,9 +102,7 @@ public class TabsLayoutItemView extends LinearLayout
mAudioPlayingButton = (ImageView) findViewById(R.id.audio_playing);
mThumbnailWrapper = (TabThumbnailWrapper) findViewById(R.id.wrapper);
if (HardwareUtils.isTablet()) {
growCloseButtonHitArea();
}
growCloseButtonHitArea();
mAudioPlayingButton.setOnClickListener(new View.OnClickListener() {
@Override

View File

@ -6,21 +6,25 @@ package org.mozilla.tests.browser.junit3;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.test.InstrumentationTestCase;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.background.db.CursorDumper;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.LocalTabsAccessor;
import org.mozilla.gecko.db.RemoteClient;
import org.mozilla.gecko.db.TabsAccessor;
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
import java.util.List;
public class TestRemoteTabs extends InstrumentationTestCase {
private static final long ONE_DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
private static final long ONE_WEEK_IN_MILLISECONDS = 7 * ONE_DAY_IN_MILLISECONDS;
private static final long THREE_WEEKS_IN_MILLISECONDS = 3 * ONE_WEEK_IN_MILLISECONDS;
public void testGetClientsWithoutTabsByRecencyFromCursor() throws Exception {
final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
final ContentResolver cr = getInstrumentation().getTargetContext().getContentResolver();
@ -124,4 +128,89 @@ public class TestRemoteTabs extends InstrumentationTestCase {
cpc.release();
}
}
public void testGetRecentRemoteClientsUpToOneWeekOld() throws Exception {
final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
final Context context = getInstrumentation().getTargetContext();
final String profileName = GeckoProfile.get(context).getName();
final ContentResolver cr = context.getContentResolver();
final ContentProviderClient cpc = cr.acquireContentProviderClient(uri);
final LocalTabsAccessor accessor = new LocalTabsAccessor(profileName);
try {
// Start Clean
cpc.delete(uri, null, null);
final Cursor allClients = cpc.query(uri, null, null, null, null);
try {
assertEquals(0, allClients.getCount());
} finally {
allClients.close();
}
// Insert a local and remote1 client record, neither with tabs.
final long now = System.currentTimeMillis();
// Local client has GUID = null.
final ContentValues local = new ContentValues();
local.put(BrowserContract.Clients.NAME, "local");
local.put(BrowserContract.Clients.LAST_MODIFIED, now + 1);
// Remote clients have GUID != null.
final ContentValues remote1 = new ContentValues();
remote1.put(BrowserContract.Clients.GUID, "guid1");
remote1.put(BrowserContract.Clients.NAME, "remote1");
remote1.put(BrowserContract.Clients.LAST_MODIFIED, now + 2);
// Insert a Remote Client that is 6 days old.
final ContentValues remote2 = new ContentValues();
remote2.put(BrowserContract.Clients.GUID, "guid2");
remote2.put(BrowserContract.Clients.NAME, "remote2");
remote2.put(BrowserContract.Clients.LAST_MODIFIED, now - ONE_WEEK_IN_MILLISECONDS + ONE_DAY_IN_MILLISECONDS);
// Insert a Remote Client with the same name as previous but with more than 3 weeks old
final ContentValues remote3 = new ContentValues();
remote3.put(BrowserContract.Clients.GUID, "guid21");
remote3.put(BrowserContract.Clients.NAME, "remote2");
remote3.put(BrowserContract.Clients.LAST_MODIFIED, now - THREE_WEEKS_IN_MILLISECONDS - ONE_DAY_IN_MILLISECONDS);
// Insert another remote client with the same name as previous but with 3 weeks - 1 day old.
final ContentValues remote4 = new ContentValues();
remote4.put(BrowserContract.Clients.GUID, "guid22");
remote4.put(BrowserContract.Clients.NAME, "remote2");
remote4.put(BrowserContract.Clients.LAST_MODIFIED, now - THREE_WEEKS_IN_MILLISECONDS + ONE_DAY_IN_MILLISECONDS);
// Insert a Remote Client that is exactly one week old.
final ContentValues remote5 = new ContentValues();
remote5.put(BrowserContract.Clients.GUID, "guid3");
remote5.put(BrowserContract.Clients.NAME, "remote3");
remote5.put(BrowserContract.Clients.LAST_MODIFIED, now - ONE_WEEK_IN_MILLISECONDS);
ContentValues[] values = new ContentValues[]{local, remote1, remote2, remote3, remote4, remote5};
int inserted = cpc.bulkInsert(uri, values);
assertEquals(values.length, inserted);
final Cursor remoteClients =
accessor.getRemoteClientsByRecencyCursor(context);
try {
CursorDumper.dumpCursor(remoteClients);
// Local client is not included.
// (remote1, guid1), (remote2, guid2), (remote3, guid3) are expected.
assertEquals(3, remoteClients.getCount());
// Check the inner data, according to recency.
List<RemoteClient> recentRemoteClientsList =
accessor.getClientsWithoutTabsByRecencyFromCursor(remoteClients);
assertEquals(3, recentRemoteClientsList.size());
assertEquals("remote1", recentRemoteClientsList.get(0).name);
assertEquals("guid1", recentRemoteClientsList.get(0).guid);
assertEquals("remote2", recentRemoteClientsList.get(1).name);
assertEquals("guid2", recentRemoteClientsList.get(1).guid);
assertEquals("remote3", recentRemoteClientsList.get(2).name);
assertEquals("guid3", recentRemoteClientsList.get(2).guid);
} finally {
remoteClients.close();
}
} finally {
cpc.release();
}
}
}

View File

@ -79,8 +79,9 @@ Services.cpmm.addMessageListener("performance-stats-service-release", function(m
if (!isContent) {
return;
}
// Keep only the probes that do not appear in the payload
let probes = gMonitor.getProbeNames
let probes = gMonitor.probeNames
.filter(x => msg.data.payload.indexOf(x) == -1);
gMonitor = PerformanceStats.getMonitor(probes);
});

View File

@ -41,6 +41,7 @@ Structure::
loadPath: <string>, // where the engine line is located; missing if no default
submissionURL: <string> // missing if no default or for user-installed engines
},
searchCohort: <string>, // optional, contains an identifier for any active search A/B experiments
e10sEnabled: <bool>, // whether e10s is on, i.e. browser tabs open by default in a different process
telemetryEnabled: <bool>, // false on failure
isInOptoutSample: <bool>, // whether this client is part of the opt-out sample