Merge mozilla-central to mozilla-inbound

This commit is contained in:
Dorel Luca 2018-05-11 01:04:57 +03:00
commit 956b982878
40 changed files with 692 additions and 191 deletions

View File

@ -455,6 +455,7 @@ pref("browser.link.open_newwindow.disabled_in_fullscreen", false);
#endif
// Tabbed browser
pref("browser.tabs.multiselect", false);
pref("browser.tabs.20FpsThrobber", false);
pref("browser.tabs.30FpsThrobber", false);
pref("browser.tabs.closeTabByDblclick", false);

View File

@ -36,6 +36,10 @@
white-space: nowrap;
}
.tab-label[multiselected] {
font-weight: bold;
}
.tab-label-container {
overflow: hidden;
}

View File

@ -134,6 +134,8 @@ window._gBrowser = {
_removingTabs: [],
_multiSelectedTabsMap: new WeakMap(),
/**
* Tab close requests are ignored if the window is closing anyway,
* e.g. when holding Ctrl+W.
@ -3605,6 +3607,39 @@ window._gBrowser = {
return SessionStore.duplicateTab(window, aTab, 0, aRestoreTabImmediately);
},
addToMultiSelectedTabs(aTab) {
if (aTab.multiselected) {
return;
}
aTab.setAttribute("multiselected", "true");
this._multiSelectedTabsMap.set(aTab, null);
},
removeFromMultiSelectedTabs(aTab) {
if (!aTab.multiselected) {
return;
}
aTab.removeAttribute("multiselected");
this._multiSelectedTabsMap.delete(aTab);
},
clearMultiSelectedTabs() {
const selectedTabs = ChromeUtils.nondeterministicGetWeakMapKeys(this._multiSelectedTabsMap);
for (let tab of selectedTabs) {
if (tab.isConnected && tab.multiselected) {
tab.removeAttribute("multiselected");
}
}
this._multiSelectedTabsMap = new WeakMap();
},
multiSelectedTabsCount() {
return ChromeUtils.nondeterministicGetWeakMapKeys(this._multiSelectedTabsMap)
.filter(tab => tab.isConnected)
.length;
},
activateBrowserForPrintPreview(aBrowser) {
this._printPreviewBrowsers.add(aBrowser);
if (this._switcher) {

View File

@ -1581,7 +1581,7 @@
onunderflow="this.removeAttribute('textoverflow');"
flex="1">
<xul:label class="tab-text tab-label"
xbl:inherits="xbl:text=label,accesskey,fadein,pinned,selected=visuallyselected,attention"
xbl:inherits="xbl:text=label,accesskey,fadein,pinned,selected=visuallyselected,attention,multiselected"
role="presentation"/>
</xul:hbox>
<xul:image xbl:inherits="soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected,activemedia-blocked"
@ -1662,6 +1662,11 @@
return this.getAttribute("muted") == "true";
</getter>
</property>
<property name="multiselected" readonly="true">
<getter>
return this.getAttribute("multiselected") == "true";
</getter>
</property>
<!--
Describes how the tab ended up in this mute state. May be any of:
@ -1958,11 +1963,17 @@
if (this.selected) {
this.style.MozUserFocus = "ignore";
} else if (this.mOverCloseButton ||
this._overPlayingIcon) {
} else {
// When browser.tabs.multiselect config is set to false,
// then we ignore the state of multi-selection keys (Ctrl/Cmd).
const tabSelectionToggled = Services.prefs.getBoolPref("browser.tabs.multiselect") &&
event.getModifierState("Accel");
if (this.mOverCloseButton || this._overPlayingIcon || tabSelectionToggled) {
// Prevent tabbox.xml from selecting the tab.
event.stopPropagation();
}
}
]]>
</handler>
<handler event="mouseup">
@ -1970,6 +1981,22 @@
</handler>
<handler event="click" button="0"><![CDATA[
if (Services.prefs.getBoolPref("browser.tabs.multiselect")) {
const tabSelectionToggled = event.getModifierState("Accel");
if (tabSelectionToggled) {
if (this.multiselected) {
gBrowser.removeFromMultiSelectedTabs(this);
} else {
gBrowser.addToMultiSelectedTabs(this);
}
return;
} else if (gBrowser.multiSelectedTabsCount() > 0) {
// Tabs were previously multi-selected and user clicks on a tab
// without holding Ctrl/Cmd Key
gBrowser.clearMultiSelectedTabs();
}
}
if (this._overPlayingIcon) {
this.toggleMuteAudio();
return;

View File

@ -258,7 +258,9 @@ function processCSSRules(sheet) {
// Note: CSSStyleRule.cssText always has double quotes around URLs even
// when the original CSS file didn't.
let urls = rule.cssText.match(/url\("[^"]*"\)/g);
let props = rule.cssText.match(/(var\()?(--[\w\-]+)/g);
// Extract props by searching all "--" preceeded by "var(" or a non-word
// character.
let props = rule.cssText.match(/(var\(|\W)(--[\w\-]+)/g);
if (!urls && !props)
continue;
@ -286,12 +288,17 @@ function processCSSRules(sheet) {
prop = prop.substring(4);
let prevValue = customPropsToReferencesMap.get(prop) || 0;
customPropsToReferencesMap.set(prop, prevValue + 1);
} else if (!customPropsToReferencesMap.has(prop)) {
} else {
// Remove the extra non-word character captured by the regular
// expression.
prop = prop.substring(1);
if (!customPropsToReferencesMap.has(prop)) {
customPropsToReferencesMap.set(prop, undefined);
}
}
}
}
}
function chromeFileExists(aURI) {
let available = 0;

View File

@ -40,3 +40,4 @@ skip-if = (debug && os == 'mac') || (debug && os == 'linux' && bits == 64) #Bug
[browser_visibleTabs_contextMenu.js]
[browser_open_newtab_start_observer_notification.js]
[browser_bug_1387976_restore_lazy_tab_browser_muted_state.js]
[browser_multiselect_tabs_using_Ctrl.js]

View File

@ -0,0 +1,89 @@
const PREF_MULTISELECT_TABS = "browser.tabs.multiselect";
async function triggerClickOn(target, options) {
let promise = BrowserTestUtils.waitForEvent(target, "click");
if (AppConstants.platform == "macosx") {
options = { metaKey: options.ctrlKey };
}
EventUtils.synthesizeMouseAtCenter(target, options);
return promise;
}
async function addTab() {
const tab = BrowserTestUtils.addTab(gBrowser, "http://mochi.test:8888/");
const browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
return tab;
}
add_task(async function clickWithoutPrefSet() {
let tab = await addTab();
let mSelectedTabs = gBrowser._multiSelectedTabsMap;
isnot(gBrowser.selectedTab, tab, "Tab doesn't have focus");
await triggerClickOn(tab, { ctrlKey: true });
ok(!tab.multiselected && !mSelectedTabs.has(tab),
"Multi-select tab doesn't work when multi-select pref is not set");
is(gBrowser.selectedTab, tab,
"Tab has focus, selected tab has changed after Ctrl/Cmd + click");
BrowserTestUtils.removeTab(tab);
});
add_task(async function clickWithPrefSet() {
await SpecialPowers.pushPrefEnv({
set: [
[PREF_MULTISELECT_TABS, true]
]
});
let mSelectedTabs = gBrowser._multiSelectedTabsMap;
const initialFocusedTab = gBrowser.selectedTab;
const tab = await addTab();
await triggerClickOn(tab, { ctrlKey: true });
ok(tab.multiselected && mSelectedTabs.has(tab), "Tab should be (multi) selected after click");
isnot(gBrowser.selectedTab, tab, "Multi-selected tab is not focused");
is(gBrowser.selectedTab, initialFocusedTab, "Focused tab doesn't change");
await triggerClickOn(tab, { ctrlKey: true });
ok(!tab.multiselected && !mSelectedTabs.has(tab), "Tab is not selected anymore");
is(gBrowser.selectedTab, initialFocusedTab, "Focused tab still doesn't change");
BrowserTestUtils.removeTab(tab);
});
add_task(async function clearSelection() {
await SpecialPowers.pushPrefEnv({
set: [
[PREF_MULTISELECT_TABS, true]
]
});
const tab1 = await addTab();
const tab2 = await addTab();
const tab3 = await addTab();
info("We select tab1 and tab2 with ctrl key down");
await triggerClickOn(tab1, { ctrlKey: true });
await triggerClickOn(tab2, { ctrlKey: true });
ok(tab1.multiselected && gBrowser._multiSelectedTabsMap.has(tab1), "Tab1 is (multi) selected");
ok(tab2.multiselected && gBrowser._multiSelectedTabsMap.has(tab2), "Tab2 is (multi) selected");
is(gBrowser.multiSelectedTabsCount(), 2, "Two tabs selected");
isnot(tab3, gBrowser.selectedTab, "Tab3 doesn't have focus");
info("We select tab3 with Ctrl key up");
await triggerClickOn(tab3, { ctrlKey: false });
ok(!tab1.multiselected, "Tab1 is unselected");
ok(!tab2.multiselected, "Tab2 is unselected");
is(gBrowser.multiSelectedTabsCount(), 0, "Selection is cleared");
is(tab3, gBrowser.selectedTab, "Tab3 has focus");
BrowserTestUtils.removeTab(tab1);
BrowserTestUtils.removeTab(tab2);
BrowserTestUtils.removeTab(tab3);
});

View File

@ -5,17 +5,26 @@
"use strict";
const kTimeoutInMS = 20000;
let gZoomResetButton;
async function waitForZoom(zoom) {
if (parseInt(gZoomResetButton.label) == zoom) {
return;
}
await promiseAttributeMutation(gZoomResetButton, "label", v => {
return parseInt(v) == zoom;
});
}
// Bug 934951 - Zoom controls percentage label doesn't update when it's in the toolbar and you navigate.
add_task(async function() {
CustomizableUI.addWidgetToArea("zoom-controls", CustomizableUI.AREA_NAVBAR);
gZoomResetButton = document.getElementById("zoom-reset-button");
let tab1 = BrowserTestUtils.addTab(gBrowser, "about:mozilla");
await BrowserTestUtils.browserLoaded(tab1.linkedBrowser);
let tab2 = BrowserTestUtils.addTab(gBrowser, "about:robots");
await BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
gBrowser.selectedTab = tab1;
let zoomResetButton = document.getElementById("zoom-reset-button");
registerCleanupFunction(() => {
info("Cleaning up.");
@ -24,68 +33,33 @@ add_task(async function() {
gBrowser.removeTab(tab1);
});
is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:mozilla");
let zoomChangePromise = BrowserTestUtils.waitForEvent(window, "FullZoomChange");
is(parseInt(gZoomResetButton.label, 10), 100, "Default zoom is 100% for about:mozilla");
FullZoom.enlarge();
await zoomChangePromise;
is(parseInt(zoomResetButton.label, 10), 110, "Zoom is changed to 110% for about:mozilla");
await waitForZoom(110);
is(parseInt(gZoomResetButton.label, 10), 110, "Zoom is changed to 110% for about:mozilla");
let tabSelectPromise = promiseObserverNotification("browser-fullZoom:location-change");
let tabSelectPromise = TestUtils.topicObserved("browser-fullZoom:location-change");
gBrowser.selectedTab = tab2;
await tabSelectPromise;
await new Promise(resolve => executeSoon(resolve));
is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:robots");
await waitForZoom(100);
is(parseInt(gZoomResetButton.label, 10), 100, "Default zoom is 100% for about:robots");
gBrowser.selectedTab = tab1;
let zoomResetPromise = BrowserTestUtils.waitForEvent(window, "FullZoomChange");
await waitForZoom(110);
FullZoom.reset();
await zoomResetPromise;
is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:mozilla");
await waitForZoom(100);
is(parseInt(gZoomResetButton.label, 10), 100, "Default zoom is 100% for about:mozilla");
// Test zoom label updates while navigating pages in the same tab.
FullZoom.enlarge();
await zoomChangePromise;
is(parseInt(zoomResetButton.label, 10), 110, "Zoom is changed to 110% for about:mozilla");
let attributeChangePromise = promiseAttributeMutation(zoomResetButton, "label", (v) => {
return parseInt(v, 10) == 100;
});
await waitForZoom(110);
is(parseInt(gZoomResetButton.label, 10), 110, "Zoom is changed to 110% for about:mozilla");
await promiseTabLoadEvent(tab1, "about:home");
await attributeChangePromise;
is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:home");
await promiseTabHistoryNavigation(-1, function() {
return parseInt(zoomResetButton.label, 10) == 110;
});
is(parseInt(zoomResetButton.label, 10), 110, "Zoom is still 110% for about:mozilla");
await waitForZoom(100);
is(parseInt(gZoomResetButton.label, 10), 100, "Default zoom is 100% for about:home");
gBrowser.selectedBrowser.goBack();
await waitForZoom(110);
is(parseInt(gZoomResetButton.label, 10), 110, "Zoom is still 110% for about:mozilla");
FullZoom.reset();
});
function promiseObserverNotification(aObserver) {
return new Promise((resolve, reject) => {
function notificationCallback(e) {
Services.obs.removeObserver(notificationCallback, aObserver);
clearTimeout(timeoutId);
resolve();
}
let timeoutId = setTimeout(() => {
Services.obs.removeObserver(notificationCallback, aObserver);
reject("Notification '" + aObserver + "' did not happen within 20 seconds.");
}, kTimeoutInMS);
Services.obs.addObserver(notificationCallback, aObserver);
});
}
function promiseTabSelect() {
return new Promise((resolve, reject) => {
let container = window.gBrowser.tabContainer;
let timeoutId = setTimeout(() => {
container.removeEventListener("TabSelect", callback);
reject("TabSelect did not happen within 20 seconds");
}, kTimeoutInMS);
function callback(e) {
container.removeEventListener("TabSelect", callback);
clearTimeout(timeoutId);
executeSoon(resolve);
}
container.addEventListener("TabSelect", callback);
});
}

View File

@ -26,7 +26,6 @@ registerCleanupFunction(() => Services.prefs.clearUserPref("browser.uiCustomizat
var {synthesizeDragStart, synthesizeDrop} = EventUtils;
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const kTabEventFailureTimeoutInMs = 20000;
const kForceOverflowWidthPx = 300;
@ -378,40 +377,6 @@ function promiseTabLoadEvent(aTab, aURL) {
return BrowserTestUtils.browserLoaded(browser);
}
/**
* Navigate back or forward in tab history and wait for it to finish.
*
* @param aDirection Number to indicate to move backward or forward in history.
* @param aConditionFn Function that returns the result of an evaluated condition
* that needs to be `true` to resolve the promise.
* @return {Promise} resolved when navigation has finished.
*/
function promiseTabHistoryNavigation(aDirection = -1, aConditionFn) {
return new Promise((resolve, reject) => {
let timeoutId = setTimeout(() => {
gBrowser.removeEventListener("pageshow", listener, true);
reject("Pageshow did not happen within " + kTabEventFailureTimeoutInMs + "ms");
}, kTabEventFailureTimeoutInMs);
function listener(event) {
gBrowser.removeEventListener("pageshow", listener, true);
clearTimeout(timeoutId);
if (aConditionFn) {
waitForCondition(aConditionFn).then(() => resolve(),
aReason => reject(aReason));
} else {
resolve();
}
}
gBrowser.addEventListener("pageshow", listener, true);
gBrowser.contentWindowAsCPOW.history.go(aDirection);
});
}
/**
* Wait for an attribute on a node to change
*

View File

@ -129,7 +129,6 @@ add_task(async function test_delete() {
extension.sendMessage("delete-all");
[historyClearedCount, removedUrls] = await extension.awaitMessage("history-cleared");
equal(PlacesUtils.history.hasHistoryEntries, false, "history is empty");
equal(historyClearedCount, 2, "onVisitRemoved called for each clearing of history");
equal(removedUrls.length, 3, "onVisitRemoved called the expected number of times");
for (let i = 1; i < 3; ++i) {

View File

@ -13,6 +13,7 @@ support-files =
[browser_addBookmarkForFrame.js]
[browser_bookmark_add_tags.js]
[browser_bookmark_backup_export_import.js]
[browser_bookmark_change_location.js]
[browser_bookmark_folder_moveability.js]
[browser_bookmark_load_in_sidebar.js]

View File

@ -0,0 +1,142 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests bookmarks backup export/import as JSON file.
*/
const BASE_URL = "http://example.com/";
const PLACES = [
{
guid: PlacesUtils.bookmarks.menuGuid,
prefix: "In Menu",
total: 5
},
{
guid: PlacesUtils.bookmarks.toolbarGuid,
prefix: "In Toolbar",
total: 7
},
{
guid: PlacesUtils.bookmarks.unfiledGuid,
prefix: "In Other",
total: 8
}
];
var importExportPicker, saveDir, actualBookmarks;
async function generateTestBookmarks() {
actualBookmarks = [];
for (let place of PLACES) {
let currentPlaceChildren = [];
for (let i = 1; i <= place.total; i++) {
currentPlaceChildren.push({
url: `${BASE_URL}${i}`,
title: `${place.prefix} Bookmark: ${i}`
});
}
await PlacesUtils.bookmarks.insertTree({
guid: place.guid,
children: currentPlaceChildren
});
actualBookmarks = actualBookmarks.concat(currentPlaceChildren);
}
}
async function validateImportedBookmarksByParent(parentGuid, expectedChildrenTotal) {
let currentPlace = PLACES.filter((elem) => {
return elem.guid === parentGuid.toString();
})[0];
let bookmarksTree = await PlacesUtils.promiseBookmarksTree(parentGuid);
Assert.equal(bookmarksTree.children.length, expectedChildrenTotal, `Imported bookmarks length should be ${expectedChildrenTotal}`);
for (let importedBookmark of bookmarksTree.children) {
Assert.equal(importedBookmark.type, PlacesUtils.TYPE_X_MOZ_PLACE, `Exported bookmarks should be of type bookmark`);
let doesTitleContain = importedBookmark.title.toString().includes(`${currentPlace.prefix} Bookmark`);
Assert.equal(doesTitleContain, true, `Bookmark title should contain text: ${currentPlace.prefix} Bookmark`);
let doesUriContains = importedBookmark.uri.toString().includes(BASE_URL);
Assert.equal(doesUriContains, true, "Bookmark uri should contain base url");
}
}
async function validateImportedBookmarks(fromPlaces) {
for (let i = 0; i < fromPlaces.length; i++) {
let parentContainer = fromPlaces[i];
await validateImportedBookmarksByParent(parentContainer.guid, parentContainer.total);
}
}
async function promiseImportExport(aWindow) {
saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
saveDir.append("temp-bookmarks-export");
if (!saveDir.exists()) {
saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
}
importExportPicker.displayDirectory = saveDir;
return new Promise(resolve => {
importExportPicker.showCallback = async () => {
let fileName = "bookmarks-backup.json";
let destFile = saveDir.clone();
destFile.append(fileName);
importExportPicker.setFiles([destFile]);
resolve(destFile);
};
});
}
add_task(async function setup() {
await promisePlacesInitComplete();
await PlacesUtils.bookmarks.eraseEverything();
await generateTestBookmarks();
importExportPicker = SpecialPowers.MockFilePicker;
importExportPicker.init(window);
registerCleanupFunction(async () => {
importExportPicker.cleanup();
await PlacesUtils.bookmarks.eraseEverything();
});
});
add_task(async function test_export_json() {
let libraryWindow = await promiseLibrary();
libraryWindow.document.querySelector("#maintenanceButtonPopup #backupBookmarks").click();
let backupFile = await promiseImportExport();
await BrowserTestUtils.waitForCondition(backupFile.exists);
await promiseLibraryClosed(libraryWindow);
await PlacesUtils.bookmarks.eraseEverything();
});
add_task(async function test_import_json() {
let libraryWindow = await promiseLibrary();
libraryWindow.document.querySelector("#maintenanceButtonPopup #restoreFromFile").click();
await promiseImportExport();
await BrowserTestUtils.promiseAlertDialogOpen("accept");
let restored = 0;
let promiseBookmarksRestored = PlacesTestUtils.waitForNotification("onItemAdded", () => {
restored++;
return restored === actualBookmarks.length;
});
await promiseBookmarksRestored;
await validateImportedBookmarks(PLACES);
await promiseLibraryClosed(libraryWindow);
registerCleanupFunction(async () => {
if (saveDir) {
saveDir.remove(true);
}
});
});

View File

@ -25,6 +25,17 @@ XPCOMUtils.defineLazyPreferenceGetter(details, "image.mem.shared", "image.mem.sh
details.buildID = Services.appinfo.appBuildID;
details.channel = AppConstants.MOZ_UPDATE_CHANNEL;
Object.defineProperty(details, "blockList", {
// We don't want this property to end up in the stringified details
enumerable: false,
get() {
let trackingTable = Services.prefs.getCharPref("urlclassifier.trackingTable");
// If content-track-digest256 is in the tracking table,
// the user has enabled the strict list.
return trackingTable.includes("content") ? "strict" : "basic";
}
});
if (AppConstants.platform == "linux") {
XPCOMUtils.defineLazyPreferenceGetter(details, "layers.acceleration.force-enabled", "layers.acceleration.force-enabled", false);
}
@ -59,8 +70,9 @@ let WebCompatReporter = {
},
// This method injects a framescript that should send back a screenshot blob
// of the top-level window of the currently selected tab, resolved as a
// Promise.
// of the top-level window of the currently selected tab, and some other details
// about the tab (url, tracking protection + mixed content blocking status)
// resolved as a Promise.
getScreenshot(gBrowser) {
const FRAMESCRIPT = "chrome://webcompat-reporter/content/tab-frame.js";
const TABDATA_MESSAGE = "WebCompat:SendTabData";
@ -90,6 +102,14 @@ let WebCompatReporter = {
let win = Services.wm.getMostRecentWindow("navigator:browser");
const WEBCOMPAT_ORIGIN = new win.URL(WebCompatReporter.endpoint).origin;
// Grab the relevant tab environment details that might change per site
details["mixed active content blocked"] = tabData.hasMixedActiveContentBlocked;
details["mixed passive content blocked"] = tabData.hasMixedDisplayContentBlocked;
details["tracking content blocked"] = tabData.hasTrackingContentBlocked ?
`true (${details.blockList})` : "false";
// question: do i add a label for basic vs strict?
let params = new URLSearchParams();
params.append("url", `${tabData.url}`);
params.append("src", "desktop-reporter");
@ -99,6 +119,10 @@ let WebCompatReporter = {
params.append("label", "type-webrender-enabled");
}
if (tabData.hasTrackingContentBlocked) {
params.append("label", `type-tracking-protection-${details.blockList}`);
}
let tab = gBrowser.loadOneTab(
`${WebCompatReporter.endpoint}?${params}`,
{inBackground: false, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()});

View File

@ -22,7 +22,13 @@ let getScreenshot = function(win) {
ctx.scale(dpr, dpr);
ctx.drawWindow(win, x, y, w, h, "#fff");
canvas.toBlob(blob => {
resolve({url, blob});
resolve({
blob,
hasMixedActiveContentBlocked: docShell.hasMixedActiveContentBlocked,
hasMixedDisplayContentBlocked: docShell.hasMixedDisplayContentBlocked,
url,
hasTrackingContentBlocked: docShell.hasTrackingContentBlocked
});
});
} catch (ex) {
// CanvasRenderingContext2D.drawWindow can fail depending on memory or

View File

@ -56,11 +56,17 @@ async function openNewTabAndApplicationPanel(url) {
}
async function unregisterAllWorkers(client) {
info("Wait until all workers have a valid registrationActor");
let workers;
await asyncWaitUntil(async function() {
workers = await client.mainRoot.listAllWorkers();
const allWorkersRegistered =
workers.service.every(worker => !!worker.registrationActor);
return allWorkersRegistered;
});
info("Unregister all service workers");
let { service } = await client.mainRoot.listAllWorkers();
for (let worker of service) {
for (let worker of workers.service) {
await client.request({
to: worker.registrationActor,
type: "unregister"

View File

@ -464,6 +464,19 @@ function waitUntil(predicate, interval = 10) {
});
}
/**
* Variant of waitUntil that accepts a predicate returning a promise.
*/
async function asyncWaitUntil(predicate, interval = 10) {
let success = await predicate();
while (!success) {
// Wait for X milliseconds.
await new Promise(resolve => setTimeout(resolve, interval));
// Test the predicate again.
success = await predicate();
}
}
/**
* Takes a string `script` and evaluates it directly in the content
* in potentially a different process.

View File

@ -1869,8 +1869,10 @@ EventStateManager::IsEventOutsideDragThreshold(WidgetInputEvent* aEvent) const
sPixelThresholdY = 5;
}
auto touchEvent = aEvent->AsTouchEvent();
LayoutDeviceIntPoint pt = aEvent->mWidget->WidgetToScreenOffset() +
(aEvent->AsTouchEvent() ? aEvent->AsTouchEvent()->mTouches[0]->mRefPoint
((touchEvent && !touchEvent->mTouches.IsEmpty())
? aEvent->AsTouchEvent()->mTouches[0]->mRefPoint
: aEvent->mRefPoint);
LayoutDeviceIntPoint distance = pt - mGestureDownPoint;
return

View File

@ -735,7 +735,7 @@ var interfaceNamesInGlobalScope =
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "OfflineAudioContext", insecureContext: true},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "OfflineResourceList", insecureContext: !isEarlyBetaOrEarlier},
{name: "OfflineResourceList", insecureContext: false},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "Option", insecureContext: true},
// IMPORTANT: Do not change this list without review from a DOM peer!

View File

@ -0,0 +1,13 @@
<style>
* {
shape-margin: 33%;
shape-outside: ellipse(20% 47% at 66% 79%)
}
.cl {
border-right-style: dashed;
float: right;
border-bottom-style: dashed;
}
</style>
<button>
<del class="cl">

View File

@ -682,3 +682,4 @@ load 1427824.html
load 1431781.html
load 1431781-2.html
asserts(6) load 1458028.html # bug 847368
load 1459697.html

View File

@ -822,9 +822,11 @@ nsFloatManager::EllipseShapeInfo::EllipseShapeInfo(const nsPoint& aCenter,
dfType usedMargin5X = CalcUsedShapeMargin5X(aShapeMargin,
aAppUnitsPerDevPixel);
// Calculate the bounds of one quadrant of the ellipse, in integer device
// pixels. These bounds are equal to the rectangle defined by the radii,
// plus the shape-margin value in both dimensions.
const LayoutDeviceIntSize bounds =
LayoutDevicePixel::FromAppUnitsRounded(mRadii,
aAppUnitsPerDevPixel) +
LayoutDevicePixel::FromAppUnitsRounded(mRadii, aAppUnitsPerDevPixel) +
LayoutDeviceIntSize(usedMargin5X / 5, usedMargin5X / 5);
// Since our distance field is computed with a 5x5 neighborhood, but only
@ -863,7 +865,7 @@ nsFloatManager::EllipseShapeInfo::EllipseShapeInfo(const nsPoint& aCenter,
// Find the i intercept of the ellipse edge for this block row, and
// adjust it to compensate for the expansion of the inline dimension.
// If we're in the expanded region, or if we're using a b that's more
// than the bStart of the ellipse, the intercept is nscoord_MIN.
// than the bEnd of the ellipse, the intercept is nscoord_MIN.
const int32_t iIntercept = (bIsInExpandedRegion ||
bIsMoreThanEllipseBEnd) ? nscoord_MIN :
iExpand + NSAppUnitsToIntPixels(
@ -918,15 +920,23 @@ nsFloatManager::EllipseShapeInfo::EllipseShapeInfo(const nsPoint& aCenter,
if (df[index] <= usedMargin5X) {
MOZ_ASSERT(iMax < (int32_t)i);
iMax = i;
} else {
// Since we're computing the bottom-right quadrant, there's no way
// for a later i value in this row to be within the usedMargin5X
// value. Likewise, every row beyond us will encounter this
// condition with an i value less than or equal to our i value now.
// Since our chamfer only looks upward and leftward, we can stop
// calculating for the rest of the row, because the distance field
// values there will never be looked at in a later row's chamfer
// calculation.
break;
}
}
}
NS_WARNING_ASSERTION(bIsInExpandedRegion || iMax > nscoord_MIN,
"Once past the expanded region, we should always "
"find a pixel within the shape-margin distance for "
"each block row.");
// It's very likely, though not guaranteed that we will find an pixel
// within the shape-margin distance for each block row. This may not
// always be true due to rounding errors.
if (iMax > nscoord_MIN) {
// Origin for this interval is at the center of the ellipse, adjusted
// in the positive block direction by bInAppUnits.
@ -959,7 +969,7 @@ nsFloatManager::EllipseShapeInfo::LineEdge(const nscoord aBStart,
// We are checking against our intervals. Make sure we have some.
if (mIntervals.IsEmpty()) {
NS_WARNING("With mShapeMargin > 0, we can't proceed without intervals.");
return 0;
return aIsLineLeft ? nscoord_MAX : nscoord_MIN;
}
// Map aBStart and aBEnd into our intervals. Our intervals are calculated
@ -992,12 +1002,26 @@ nsFloatManager::EllipseShapeInfo::LineEdge(const nscoord aBStart,
MOZ_ASSERT(bSmallestWithinIntervals >= mCenter.y &&
bSmallestWithinIntervals < BEnd(),
"We should have a block value within the intervals.");
"We should have a block value within the float area.");
size_t index = MinIntervalIndexContainingY(mIntervals,
bSmallestWithinIntervals);
MOZ_ASSERT(index < mIntervals.Length(),
"We should have found a matching interval for this block value.");
if (index >= mIntervals.Length()) {
// This indicates that our intervals don't cover the block value
// bSmallestWithinIntervals. This can happen when rounding error in the
// distance field calculation resulted in the last block pixel row not
// contributing to the float area. As long as we're within one block pixel
// past the last interval, this is an expected outcome.
#ifdef DEBUG
nscoord onePixelPastLastInterval =
mIntervals[mIntervals.Length() - 1].YMost() +
mIntervals[mIntervals.Length() - 1].Height();
NS_WARNING_ASSERTION(bSmallestWithinIntervals < onePixelPastLastInterval,
"We should have found a matching interval for this "
"block value.");
#endif
return aIsLineLeft ? nscoord_MAX : nscoord_MIN;
}
// The interval is storing the line right value. If aIsLineLeft is true,
// return the line right value reflected about the center. Since this is

View File

@ -82,13 +82,8 @@ pref("browser.cache.max_shutdown_io_lag", 2);
pref("browser.cache.offline.enable", true);
// Nightly and Early Beta will have AppCache disabled by default
// Stable will remain enabled until Firefox 62.
#ifdef EARLY_BETA_OR_EARLIER
// AppCache over insecure connection is disabled by default
pref("browser.cache.offline.insecure.enable", false);
#else
pref("browser.cache.offline.insecure.enable", true);
#endif
// enable offline apps by default, disable prompt
pref("offline-apps.allow_by_default", true);

View File

@ -1067,7 +1067,12 @@ nsSocketTransportService::Run()
// socket detach handlers get processed.
NS_ProcessPendingEvents(mRawThread);
// Stopping the SLL threads can generate new events, so we need to
// process them before nulling out gSocketThread, otherwise we can get
// !onSocketThread assertions.
psm::StopSSLServerCertVerificationThreads();
NS_ProcessPendingEvents(mRawThread);
gSocketThread = nullptr;
SOCKET_LOG(("STS thread exit\n"));

View File

@ -294515,6 +294515,11 @@
{}
]
],
"server-timing/test_server_timing.https.html.sub.headers": [
[
{}
]
],
"service-workers/OWNERS": [
[
{}
@ -365190,24 +365195,48 @@
{}
]
],
"server-timing/navigation_timing_idl.https.html": [
[
"/server-timing/navigation_timing_idl.https.html",
{}
]
],
"server-timing/resource_timing_idl.html": [
[
"/server-timing/resource_timing_idl.html",
{}
]
],
"server-timing/resource_timing_idl.https.html": [
[
"/server-timing/resource_timing_idl.https.html",
{}
]
],
"server-timing/server_timing_header-parsing.html": [
[
"/server-timing/server_timing_header-parsing.html",
{}
]
],
"server-timing/server_timing_header-parsing.https.html": [
[
"/server-timing/server_timing_header-parsing.https.html",
{}
]
],
"server-timing/test_server_timing.html": [
[
"/server-timing/test_server_timing.html",
{}
]
],
"server-timing/test_server_timing.https.html": [
[
"/server-timing/test_server_timing.https.html",
{}
]
],
"service-workers/cache-storage/common.https.html": [
[
"/service-workers/cache-storage/common.https.html",
@ -600431,10 +600460,18 @@
"191f42a92f0ac135de816275920e54fa50065b15",
"testharness"
],
"server-timing/navigation_timing_idl.https.html": [
"191f42a92f0ac135de816275920e54fa50065b15",
"testharness"
],
"server-timing/resource_timing_idl.html": [
"eba493537f5d156b4fb074e24787e522ea3c7971",
"testharness"
],
"server-timing/resource_timing_idl.https.html": [
"eba493537f5d156b4fb074e24787e522ea3c7971",
"testharness"
],
"server-timing/resources/blue.png": [
"7de5cdb5ad04ac365430b3b5f5ba01d2ba57ea23",
"support"
@ -601127,6 +601164,10 @@
"10f756bbf749b7ad8f7c6eb4efe752ee79c44b4a",
"testharness"
],
"server-timing/server_timing_header-parsing.https.html": [
"10f756bbf749b7ad8f7c6eb4efe752ee79c44b4a",
"testharness"
],
"server-timing/test_server_timing.html": [
"7c778ca856e5cff0bbc785f59c9ccf1ec86456fb",
"testharness"
@ -601135,6 +601176,14 @@
"77000d65537ef522a3471002118a120d2faf296a",
"support"
],
"server-timing/test_server_timing.https.html": [
"7c778ca856e5cff0bbc785f59c9ccf1ec86456fb",
"testharness"
],
"server-timing/test_server_timing.https.html.sub.headers": [
"77000d65537ef522a3471002118a120d2faf296a",
"support"
],
"service-workers/OWNERS": [
"153554b1fb793acd7a005c50b12678305e02014a",
"support"

View File

@ -0,0 +1,27 @@
[server_timing_header-parsing.https.html]
[Untitled]
expected: FAIL
[59.js - count (0 ?== 1)]
expected: FAIL
[59.js - duration (0 ?== 123.4)]
expected: FAIL
[60.js - count (0 ?== 1)]
expected: FAIL
[60.js - duration (0 ?== 123.4)]
expected: FAIL
[61.js - description ( ?== description)]
expected: FAIL
[62.js - description ( ?== description)]
expected: FAIL
[67.js - duration (0 ?== 123.4)]
expected: FAIL
[68.js - count (2 ?== 1)]
expected: FAIL

View File

@ -0,0 +1,9 @@
[test_server_timing.https.html]
[Untitled]
expected: FAIL
[Entry {"duration":1.1,"name":"metric1","description":"document"} could not be found.]
expected: FAIL
[Entry {"duration":1.2,"name":"metric1","description":"document"} could not be found.]
expected: FAIL

View File

@ -30,7 +30,6 @@
float: right;
width: 200px;
height: 200px;
background-color: green;
shape-margin: 10px;
shape-outside: inset(60px 10px 60px 110px round 70px 0px 0px 10px / 10px 0px 0px 20px);
}

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<head>
<meta charset='utf-8' />
<script src="/resources/testharness.js"></script>
<script src='/resources/testharnessreport.js'></script>
<script>
setup({explicit_done: true})
window.addEventListener('load', function(){
assert_not_equals(typeof performance.getEntriesByType('navigation')[0].serverTiming, 'undefined',
'An instance of `PerformanceNavigationTiming` should have a `serverTiming` attribute.')
done()
})
</script>
</head>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<head>
<meta charset='utf-8' />
<script src="/resources/testharness.js"></script>
<script src='/resources/testharnessreport.js'></script>
<script>
setup({explicit_done: true})
window.addEventListener('load', function(){
assert_not_equals(typeof performance.getEntriesByType('resource')[0].serverTiming, 'undefined',
'An instance of `PerformanceResourceTiming` should have a `serverTiming` attribute.')
done()
})
</script>
</head>

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<!--
tests generated by:
https://github.com/cvazac/generate-server-timing-tests
-->
<head>
<meta charset='utf-8' />
<script src="/resources/testharness.js"></script>
<script src='/resources/testharnessreport.js'></script>
<script src="/common/performance-timeline-utils.js"></script>
<script>
setup({explicit_done: true})
const tests = []
const urlToIndex = {}
function testServerTiming(script, expectedResults) {
const url = script.src
tests[urlToIndex[url]] = {url, expectedResults}
}
function runTests() {
tests.forEach(function({url, expectedResults}) {
const {serverTiming} = performance.getEntriesByName(url)[0]
const fileName = url.substring(url.lastIndexOf('/') + 1)
test_equals(serverTiming.length, expectedResults.length, `${fileName} - count (${serverTiming.length} ?== ${expectedResults.length})`)
expectedResults.forEach(function(expectedResult, i) {
const dur = expectedResult.dur || 0
const desc = expectedResult.desc || ''
const index = expectedResults.length === 1 ? '' : `[${i}].`
test_equals(expectedResult.name, serverTiming[i].name,
`${fileName} - ${index}name (${expectedResult.name} ?== ${serverTiming[i].name})`)
test_equals(dur, serverTiming[i].duration,
`${fileName} - ${index}duration (${dur} ?== ${serverTiming[i].duration})`)
test_equals(desc, serverTiming[i].description,
`${fileName} - ${index}description (${desc} ?== ${serverTiming[i].description})`)
})
})
done()
}
for (let i = 0; i <= 83; i++) {
const script = document.createElement('script')
script.src = `./resources/parsing/${i}.js`
document.getElementsByTagName('head')[0].appendChild(script)
urlToIndex[script.src] = i
}
window.addEventListener('load', runTests)
</script>
</head>

View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<head>
<meta charset='utf-8' />
<script src="/resources/testharness.js"></script>
<script src='/resources/testharnessreport.js'></script>
<script src="/common/performance-timeline-utils.js"></script>
<script>
setup({explicit_done: true})
window.addEventListener('load', function() {
// there should be exactly three server-timing entries, 2 for document, 1 for img#one
test_entries(performance.getEntriesByType('navigation')[0].serverTiming, [{
duration: 1.1,
name: 'metric1',
description: 'document',
}, {
duration: 1.2,
name: 'metric1',
description: 'document',
}])
test_entries(performance.getEntriesByName(document.querySelector('img#one').src)[0].serverTiming, [{
duration: 2.1,
name: 'metric2',
description: 'blue.png',
}])
new PerformanceObserver(function(entryList, observer) {
// there should be exactly one server-timing entry, 1 for img#two
test_entries(entryList.getEntriesByName(document.querySelector('img#two').src)[0].serverTiming, [{
duration: 3.1,
name: 'metric3',
description: 'green.png',
}])
observer.disconnect()
done()
}).observe({entryTypes: ['resource']})
var img = document.createElement('img')
img.id = 'two'
img.src = './resources/green.png'
document.getElementsByTagName('script')[0].parentNode.appendChild(img)
})
</script>
</head>
<img id='one' src='resources/blue.png'>

View File

@ -0,0 +1 @@
Server-Timing: metric1; dur=1.1; desc=document, metric1; dur=1.2; desc=document

View File

@ -324,7 +324,6 @@ var PlacesUtils = {
LMANNO_FEEDURI: "livemark/feedURI",
LMANNO_SITEURI: "livemark/siteURI",
READ_ONLY_ANNO: "placesInternal/READ_ONLY",
CHARSET_ANNO: "URIProperties/characterSet",
// Deprecated: This is only used for supporting import from older datasets.
MOBILE_ROOT_ANNO: "mobile/bookmarksRoot",
@ -1872,14 +1871,6 @@ XPCOMUtils.defineLazyGetter(PlacesUtils, "history", function() {
}));
});
if (AppConstants.MOZ_APP_NAME != "firefox") {
// TODO (bug 1458865): This is deprecated and should not be used. We'll
// remove it once comm-central stops using it.
XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "asyncHistory",
"@mozilla.org/browser/history;1",
"mozIAsyncHistory");
}
XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "favicons",
"@mozilla.org/browser/favicon-service;1",
"mozIAsyncFavicons");

View File

@ -1272,14 +1272,6 @@ interface nsINavHistoryService : nsISupports
*/
readonly attribute unsigned short databaseStatus;
/**
* True if there is any history. This can be used in UI to determine whether
* the "clear history" button should be enabled or not. This is much better
* than using BrowserHistory.count since that can be very slow if there is
* a lot of history (it must enumerate each item). This is pretty fast.
*/
readonly attribute boolean hasHistoryEntries;
/**
* This is just like markPageAsTyped (in nsIBrowserHistory, also implemented
* by the history service), but for bookmarks. It declares that the given URI

View File

@ -787,12 +787,10 @@ nsNavHistory::DomainNameFromURI(nsIURI *aURI,
}
NS_IMETHODIMP
nsNavHistory::GetHasHistoryEntries(bool* aHasEntries)
bool
nsNavHistory::hasHistoryEntries()
{
NS_ENSURE_ARG_POINTER(aHasEntries);
*aHasEntries = GetDaysOfHistory() > 0;
return NS_OK;
return GetDaysOfHistory() > 0;
}

View File

@ -331,6 +331,12 @@ public:
*/
uint32_t GetRecentFlags(nsIURI *aURI);
/**
* Whether there are visits.
* Note: This may cause synchronous I/O.
*/
bool hasHistoryEntries();
/**
* Registers a TRANSITION_EMBED visit for the session.
*

View File

@ -2059,7 +2059,8 @@ nsNavHistoryQueryResultNode::GetHasChildren(bool* aHasChildren)
resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY) {
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
return history->GetHasHistoryEntries(aHasChildren);
*aHasChildren = history->hasHistoryEntries();
return NS_OK;
}
//XXX: For other containers queries we must:

View File

@ -201,14 +201,6 @@
"lastModified": 1361551979457086,
"livemark": 1,
"annos": [
{
"name": "placesInternal/READ_ONLY",
"flags": 0,
"expires": 4,
"mimeType": null,
"type": 1,
"value": 1
},
{
"name": "livemark/feedURI",
"flags": 0,

View File

@ -7,14 +7,20 @@
const TEST_URI = NetUtil.newURI("http://mozilla.com/");
const TEST_SUBDOMAIN_URI = NetUtil.newURI("http://foobar.mozilla.com/");
async function checkEmptyHistory() {
let db = await PlacesUtils.promiseDBConnection();
let rows = await db.executeCached("SELECT count(*) FROM moz_historyvisits");
return !rows[0].getResultByIndex(0);
}
add_task(async function test_addPage() {
await PlacesTestUtils.addVisits(TEST_URI);
Assert.equal(1, PlacesUtils.history.hasHistoryEntries);
Assert.ok(!await checkEmptyHistory(), "History has entries");
});
add_task(async function test_removePage() {
await PlacesUtils.history.remove(TEST_URI);
Assert.equal(0, PlacesUtils.history.hasHistoryEntries);
Assert.ok(await checkEmptyHistory(), "History is empty");
});
add_task(async function test_removePages() {
@ -42,7 +48,7 @@ add_task(async function test_removePages() {
Ci.nsIAnnotationService.EXPIRE_NEVER);
await PlacesUtils.history.remove(pages);
Assert.equal(0, PlacesUtils.history.hasHistoryEntries);
Assert.ok(await checkEmptyHistory(), "History is empty");
// Check that the bookmark and its annotation still exist.
let folder = await PlacesUtils.getFolderContents(PlacesUtils.unfiledBookmarksFolderId);
@ -90,44 +96,22 @@ add_task(async function test_removePagesByTimeframe() {
beginDate: PlacesUtils.toDate(startDate),
endDate: PlacesUtils.toDate(startDate + 9000)
});
Assert.equal(0, PlacesUtils.history.hasHistoryEntries);
Assert.ok(await checkEmptyHistory(), "History is empty");
});
add_task(async function test_removePagesFromHost() {
await PlacesTestUtils.addVisits(TEST_URI);
await PlacesUtils.history.removeByFilter({ host: ".mozilla.com" });
Assert.equal(0, PlacesUtils.history.hasHistoryEntries);
Assert.ok(await checkEmptyHistory(), "History is empty");
});
add_task(async function test_removePagesFromHost_keepSubdomains() {
await PlacesTestUtils.addVisits([{ uri: TEST_URI }, { uri: TEST_SUBDOMAIN_URI }]);
await PlacesUtils.history.removeByFilter({ host: "mozilla.com" });
Assert.equal(1, PlacesUtils.history.hasHistoryEntries);
Assert.ok(!await checkEmptyHistory(), "History has entries");
});
add_task(async function test_history_clear() {
await PlacesUtils.history.clear();
Assert.equal(0, PlacesUtils.history.hasHistoryEntries);
});
add_task(async function test_getObservers() {
// Ensure that getObservers() invalidates the hasHistoryEntries cache.
await PlacesTestUtils.addVisits(TEST_URI);
Assert.equal(1, PlacesUtils.history.hasHistoryEntries);
// This is just for testing purposes, never do it.
return new Promise((resolve, reject) => {
DBConn().executeSimpleSQLAsync("DELETE FROM moz_historyvisits", {
handleError(error) {
reject(error);
},
handleResult(result) {
},
handleCompletion(result) {
// Just invoking getObservers should be enough to invalidate the cache.
PlacesUtils.history.getObservers();
Assert.equal(0, PlacesUtils.history.hasHistoryEntries);
resolve();
}
});
});
Assert.ok(await checkEmptyHistory(), "History is empty");
});

View File

@ -55,12 +55,8 @@ add_task(async function test_history_clear() {
// Clear history and wait for the onClearHistory notification.
let promiseClearHistory =
PlacesTestUtils.waitForNotification("onClearHistory", () => true, "history");
PlacesUtils.history.clear();
await PlacesUtils.history.clear();
await promiseClearHistory;
// check browserHistory returns no entries
Assert.equal(0, PlacesUtils.history.hasHistoryEntries);
await PlacesTestUtils.promiseAsyncUpdates();
// Check that frecency for not cleared items (bookmarks) has been converted