mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-14 05:45:37 +00:00
Merge mozilla-central to mozilla-inbound
This commit is contained in:
commit
956b982878
@ -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);
|
||||
|
@ -36,6 +36,10 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tab-label[multiselected] {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tab-label-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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,11 +288,16 @@ 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) {
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
});
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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) {
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
@ -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()});
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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!
|
||||
|
13
layout/generic/crashtests/1459697.html
Normal file
13
layout/generic/crashtests/1459697.html
Normal 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">
|
@ -682,3 +682,4 @@ load 1427824.html
|
||||
load 1431781.html
|
||||
load 1431781-2.html
|
||||
asserts(6) load 1458028.html # bug 847368
|
||||
load 1459697.html
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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"));
|
||||
|
@ -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"
|
||||
|
@ -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
|
@ -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
|
@ -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);
|
||||
}
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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'>
|
@ -0,0 +1 @@
|
||||
Server-Timing: metric1; dur=1.1; desc=document, metric1; dur=1.2; desc=document
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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");
|
||||
});
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user