mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 22:01:30 +00:00
Merge mozilla-central to inbound r=merge a=merge on a CLOSED TREE
This commit is contained in:
commit
042d2e8d74
@ -37,7 +37,6 @@ module.exports = {
|
||||
"dom/media/**",
|
||||
"extensions/pref/**",
|
||||
"mobile/android/**",
|
||||
"security/**",
|
||||
"testing/**",
|
||||
"tools/profiler/**",
|
||||
],
|
||||
|
@ -1346,6 +1346,8 @@ var gBrowserInit = {
|
||||
this._uriToLoadPromise.then(uriToLoad => {
|
||||
if (uriToLoad == "about:home") {
|
||||
gBrowser.setIcon(gBrowser.selectedTab, "chrome://branding/content/icon32.png");
|
||||
} else if (uriToLoad == "about:privatebrowsing") {
|
||||
gBrowser.setIcon(gBrowser.selectedTab, "chrome://browser/skin/privatebrowsing/favicon.svg");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -801,7 +801,16 @@
|
||||
|
||||
// Ignore initial about:blank to prevent flickering.
|
||||
if (!this.mBrowser.mIconURL && !ignoreBlank) {
|
||||
this.mTabBrowser.useDefaultIcon(this.mTab);
|
||||
// Don't switch to the default icon on about:home or about:newtab,
|
||||
// since these pages get their favicon set in browser code to
|
||||
// improve perceived performance.
|
||||
let isNewTab = originalLocation &&
|
||||
(originalLocation.spec == "about:newtab" ||
|
||||
originalLocation.spec == "about:privatebrowsing" ||
|
||||
originalLocation.spec == "about:home");
|
||||
if (!isNewTab) {
|
||||
this.mTabBrowser.useDefaultIcon(this.mTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2813,8 +2822,10 @@
|
||||
|
||||
// Hack to ensure that the about:newtab favicon is loaded
|
||||
// instantaneously, to avoid flickering and improve perceived performance.
|
||||
if (aURI == BROWSER_NEW_TAB_URL) {
|
||||
if (aURI == "about:newtab") {
|
||||
this.setIcon(t, "chrome://branding/content/icon32.png");
|
||||
} else if (aURI == "about:privatebrowsing") {
|
||||
this.setIcon(t, "chrome://browser/skin/privatebrowsing/favicon.svg");
|
||||
}
|
||||
|
||||
// Dispatch a new tab notification. We do this once we're
|
||||
|
@ -216,8 +216,6 @@ skip-if = toolkit != "cocoa" # Because of tests for supporting pasting from Serv
|
||||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
[browser_bug579872.js]
|
||||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
[browser_bug580956.js]
|
||||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
[browser_bug581242.js]
|
||||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
[browser_bug581253.js]
|
||||
@ -540,10 +538,6 @@ skip-if = true # Bug 1409184 disabled because interactive find next is not autom
|
||||
[browser_visibleTabs_bookmarkAllPages.js]
|
||||
skip-if = true # Bug 1005420 - fails intermittently. also with e10s enabled: bizarre problem with hidden tab having _mouseenter called, via _setPositionalAttributes, and tab not being found resulting in 'candidate is undefined'
|
||||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
[browser_visibleTabs_bookmarkAllTabs.js]
|
||||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
[browser_visibleTabs_contextMenu.js]
|
||||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
[browser_visibleTabs_tabPreview.js]
|
||||
skip-if = (os == "win" && !debug)
|
||||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
|
@ -57,25 +57,6 @@ function whenDelayedStartupFinished(aWindow, aCallback) {
|
||||
}, "browser-delayed-startup-finished");
|
||||
}
|
||||
|
||||
function updateTabContextMenu(tab, onOpened) {
|
||||
let menu = document.getElementById("tabContextMenu");
|
||||
if (!tab)
|
||||
tab = gBrowser.selectedTab;
|
||||
var evt = new Event("");
|
||||
tab.dispatchEvent(evt);
|
||||
menu.openPopup(tab, "end_after", 0, 0, true, false, evt);
|
||||
is(TabContextMenu.contextTab, tab, "TabContextMenu context is the expected tab");
|
||||
const onFinished = () => menu.hidePopup();
|
||||
if (onOpened) {
|
||||
return (async function() {
|
||||
await onOpened();
|
||||
onFinished();
|
||||
})();
|
||||
}
|
||||
onFinished();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function openToolbarCustomizationUI(aCallback, aBrowserWin) {
|
||||
if (!aBrowserWin)
|
||||
aBrowserWin = window;
|
||||
|
@ -11,6 +11,17 @@ const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
|
||||
|
||||
let [testTab] = gBrowser.visibleTabs;
|
||||
|
||||
function updateTabContextMenu(tab) {
|
||||
let menu = document.getElementById("tabContextMenu");
|
||||
if (!tab)
|
||||
tab = gBrowser.selectedTab;
|
||||
var evt = new Event("");
|
||||
tab.dispatchEvent(evt);
|
||||
menu.openPopup(tab, "end_after", 0, 0, true, false, evt);
|
||||
is(TabContextMenu.contextTab, tab, "TabContextMenu context is the expected tab");
|
||||
menu.hidePopup();
|
||||
}
|
||||
|
||||
add_task(async function setup() {
|
||||
await promiseSyncReady();
|
||||
is(gBrowser.visibleTabs.length, 1, "there is one visible tab");
|
||||
@ -23,7 +34,7 @@ add_task(async function test_tab_contextmenu() {
|
||||
const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: remoteClientsFixture,
|
||||
state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
|
||||
|
||||
await updateTabContextMenu(testTab);
|
||||
updateTabContextMenu(testTab);
|
||||
is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
|
||||
is(document.getElementById("context_sendTabToDevice").disabled, false, "Send tab to device is enabled");
|
||||
|
||||
@ -34,7 +45,7 @@ add_task(async function test_tab_contextmenu_unconfigured() {
|
||||
const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: remoteClientsFixture,
|
||||
state: UIState.STATUS_NOT_CONFIGURED, isSendableURI: true });
|
||||
|
||||
await updateTabContextMenu(testTab);
|
||||
updateTabContextMenu(testTab);
|
||||
is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
|
||||
is(document.getElementById("context_sendTabToDevice").disabled, false, "Send tab to device is enabled");
|
||||
|
||||
@ -45,7 +56,7 @@ add_task(async function test_tab_contextmenu_not_sendable() {
|
||||
const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: [{ id: 1, name: "Foo"}],
|
||||
state: UIState.STATUS_SIGNED_IN, isSendableURI: false });
|
||||
|
||||
await updateTabContextMenu(testTab);
|
||||
updateTabContextMenu(testTab);
|
||||
is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
|
||||
is(document.getElementById("context_sendTabToDevice").disabled, true, "Send tab to device is disabled");
|
||||
|
||||
@ -56,7 +67,7 @@ add_task(async function test_tab_contextmenu_not_synced_yet() {
|
||||
const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: false, remoteClients: [],
|
||||
state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
|
||||
|
||||
await updateTabContextMenu(testTab);
|
||||
updateTabContextMenu(testTab);
|
||||
is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
|
||||
is(document.getElementById("context_sendTabToDevice").disabled, true, "Send tab to device is disabled");
|
||||
|
||||
@ -67,7 +78,7 @@ add_task(async function test_tab_contextmenu_sync_not_ready_configured() {
|
||||
const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, remoteClients: null,
|
||||
state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
|
||||
|
||||
await updateTabContextMenu(testTab);
|
||||
updateTabContextMenu(testTab);
|
||||
is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
|
||||
is(document.getElementById("context_sendTabToDevice").disabled, true, "Send tab to device is disabled");
|
||||
|
||||
@ -78,7 +89,7 @@ add_task(async function test_tab_contextmenu_sync_not_ready_other_state() {
|
||||
const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, remoteClients: null,
|
||||
state: UIState.STATUS_NOT_VERIFIED, isSendableURI: true });
|
||||
|
||||
await updateTabContextMenu(testTab);
|
||||
updateTabContextMenu(testTab);
|
||||
is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
|
||||
is(document.getElementById("context_sendTabToDevice").disabled, false, "Send tab to device is enabled");
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
[DEFAULT]
|
||||
support-files =
|
||||
head.js
|
||||
dummy_page.html
|
||||
test_bug1358314.html
|
||||
|
||||
[browser_abandonment_telemetry.js]
|
||||
[browser_accessibility_indicator.js]
|
||||
[browser_allow_process_switches_despite_related_browser.js]
|
||||
[browser_bug580956.js]
|
||||
[browser_contextmenu_openlink_after_tabnavigated.js]
|
||||
support-files =
|
||||
test_bug1358314.html
|
||||
[browser_isLocalAboutURI.js]
|
||||
[browser_tabCloseProbes.js]
|
||||
[browser_tabSpinnerProbe.js]
|
||||
@ -27,4 +30,6 @@ skip-if = !e10s # Pref and test only relevant for e10s.
|
||||
[browser_reload_deleted_file.js]
|
||||
[browser_tabswitch_updatecommands.js]
|
||||
[browser_viewsource_of_data_URI_in_file_process.js]
|
||||
[browser_visibleTabs_bookmarkAllTabs.js]
|
||||
[browser_visibleTabs_contextMenu.js]
|
||||
[browser_open_newtab_start_observer_notification.js]
|
||||
|
11
browser/base/content/test/tabs/head.js
Normal file
11
browser/base/content/test/tabs/head.js
Normal file
@ -0,0 +1,11 @@
|
||||
function updateTabContextMenu(tab) {
|
||||
let menu = document.getElementById("tabContextMenu");
|
||||
if (!tab)
|
||||
tab = gBrowser.selectedTab;
|
||||
var evt = new Event("");
|
||||
tab.dispatchEvent(evt);
|
||||
menu.openPopup(tab, "end_after", 0, 0, true, false, evt);
|
||||
is(TabContextMenu.contextTab, tab, "TabContextMenu context is the expected tab");
|
||||
menu.hidePopup();
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
const FAVICON_QUESTION = "chrome://global/skin/icons/question-32.png";
|
||||
const FAVICON_PRIVACY = "chrome://browser/skin/privatebrowsing/favicon.svg";
|
||||
|
||||
var stringBundle = Services.strings.createBundle(
|
||||
"chrome://browser/locale/aboutPrivateBrowsing.properties");
|
||||
@ -38,10 +37,6 @@ var prefObserver = {
|
||||
prefBranch.addObserver("pbmode.enabled", prefObserver, true);
|
||||
prefBranch.addObserver("enabled", prefObserver, true);
|
||||
|
||||
function setFavIcon(url) {
|
||||
document.getElementById("favicon").setAttribute("href", url);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
if (!PrivateBrowsingUtils.isContentWindowPrivate(window)) {
|
||||
document.documentElement.classList.remove("private");
|
||||
@ -60,8 +55,6 @@ document.addEventListener("DOMContentLoaded", function() {
|
||||
});
|
||||
|
||||
document.title = stringBundle.GetStringFromName("title.head");
|
||||
document.getElementById("favicon")
|
||||
.setAttribute("href", FAVICON_PRIVACY);
|
||||
tpToggle.addEventListener("change", toggleTrackingProtection);
|
||||
document.getElementById("startTour")
|
||||
.addEventListener("click", dontShowIntroPanelAgain);
|
||||
|
@ -120,11 +120,12 @@ this.AddonStudies = {
|
||||
for (const study of studies) {
|
||||
await getStore(db).add(study);
|
||||
}
|
||||
await AddonStudies.close();
|
||||
|
||||
try {
|
||||
await testFunction(...args, studies);
|
||||
} finally {
|
||||
db = await getDatabase(); // Re-acquire in case the test closed the connection.
|
||||
db = await getDatabase();
|
||||
await AddonStudies.clear();
|
||||
for (const study of oldStudies) {
|
||||
await getStore(db).add(study);
|
||||
|
@ -9,6 +9,16 @@ Cu.import("resource://shield-recipe-client/lib/AddonStudies.jsm", this);
|
||||
// Initialize test utils
|
||||
AddonTestUtils.initMochitest(this);
|
||||
|
||||
let _startArgsFactoryId = 1;
|
||||
function startArgsFactory(args) {
|
||||
return Object.assign({
|
||||
recipeId: _startArgsFactoryId++,
|
||||
name: "Test",
|
||||
description: "Test",
|
||||
addonUrl: "http://test/addon.xpi",
|
||||
}, args);
|
||||
}
|
||||
|
||||
decorate_task(
|
||||
AddonStudies.withStudies(),
|
||||
async function testGetMissing() {
|
||||
@ -103,16 +113,6 @@ decorate_task(
|
||||
}
|
||||
);
|
||||
|
||||
let _startArgsFactoryId = 0;
|
||||
function startArgsFactory(args) {
|
||||
return Object.assign({
|
||||
recipeId: _startArgsFactoryId++,
|
||||
name: "Test",
|
||||
description: "Test",
|
||||
addonUrl: "http://test/addon.xpi",
|
||||
}, args);
|
||||
}
|
||||
|
||||
add_task(async function testStartRequiredArguments() {
|
||||
const requiredArguments = startArgsFactory();
|
||||
for (const key in requiredArguments) {
|
||||
@ -175,6 +175,7 @@ decorate_task(
|
||||
|
||||
decorate_task(
|
||||
withWebExtension({version: "2.0"}),
|
||||
AddonStudies.withStudies(),
|
||||
async function testStart([addonId, addonFile]) {
|
||||
const startupPromise = AddonTestUtils.promiseWebExtensionStartup(addonId);
|
||||
const addonUrl = Services.io.newFileURI(addonFile).spec;
|
||||
@ -210,7 +211,7 @@ decorate_task(
|
||||
);
|
||||
ok(study.studyStartDate, "start assigns a value to the study start date.");
|
||||
|
||||
await Addons.uninstall(addonId);
|
||||
await AddonStudies.stop(args.recipeId);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -4,6 +4,7 @@ Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
Cu.import("resource://testing-common/AddonTestUtils.jsm", this);
|
||||
Cu.import("resource://shield-recipe-client/lib/AddonStudies.jsm", this);
|
||||
Cu.import("resource://shield-recipe-client/lib/NormandyDriver.jsm", this);
|
||||
Cu.import("resource://shield-recipe-client/lib/PreferenceExperiments.jsm", this);
|
||||
|
||||
add_task(withDriver(Assert, async function uuids(driver) {
|
||||
// Test that it is a UUID
|
||||
@ -192,6 +193,7 @@ decorate_task(
|
||||
decorate_task(
|
||||
withSandboxManager(Assert),
|
||||
withWebExtension({id: "driver-addon-studies@example.com"}),
|
||||
AddonStudies.withStudies(),
|
||||
async function testAddonStudies(sandboxManager, [addonId, addonFile]) {
|
||||
const addonUrl = Services.io.newFileURI(addonFile).spec;
|
||||
const driver = new NormandyDriver(sandboxManager);
|
||||
@ -319,7 +321,8 @@ decorate_task(
|
||||
decorate_task(
|
||||
withSandboxManager(Assert),
|
||||
withMockPreferences,
|
||||
async function testAddonStudies(sandboxManager) {
|
||||
PreferenceExperiments.withMockExperiments,
|
||||
async function testPreferenceStudies(sandboxManager) {
|
||||
const driver = new NormandyDriver(sandboxManager);
|
||||
sandboxManager.cloneIntoGlobal("driver", driver, {cloneFunctions: true});
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
Cu.import("resource://gre/modules/Preferences.jsm", this);
|
||||
Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", this);
|
||||
Cu.import("resource://shield-recipe-client/lib/PreferenceExperiments.jsm", this);
|
||||
Cu.import("resource://shield-recipe-client/lib/CleanupManager.jsm", this);
|
||||
|
||||
// Save ourselves some typing
|
||||
const {withMockExperiments} = PreferenceExperiments;
|
||||
@ -25,62 +26,74 @@ function experimentFactory(attrs) {
|
||||
}
|
||||
|
||||
// clearAllExperimentStorage
|
||||
add_task(withMockExperiments(async function(experiments) {
|
||||
experiments.test = experimentFactory({name: "test"});
|
||||
ok(await PreferenceExperiments.has("test"), "Mock experiment is detected.");
|
||||
await PreferenceExperiments.clearAllExperimentStorage();
|
||||
ok(
|
||||
!(await PreferenceExperiments.has("test")),
|
||||
"clearAllExperimentStorage removed all stored experiments",
|
||||
);
|
||||
}));
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function(experiments) {
|
||||
experiments.test = experimentFactory({name: "test"});
|
||||
ok(await PreferenceExperiments.has("test"), "Mock experiment is detected.");
|
||||
await PreferenceExperiments.clearAllExperimentStorage();
|
||||
ok(
|
||||
!(await PreferenceExperiments.has("test")),
|
||||
"clearAllExperimentStorage removed all stored experiments",
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// start should throw if an experiment with the given name already exists
|
||||
add_task(withMockExperiments(async function(experiments) {
|
||||
experiments.test = experimentFactory({name: "test"});
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.start({
|
||||
name: "test",
|
||||
branch: "branch",
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "value",
|
||||
preferenceType: "string",
|
||||
preferenceBranchType: "default",
|
||||
}),
|
||||
"start threw an error due to a conflicting experiment name",
|
||||
);
|
||||
}));
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function(experiments) {
|
||||
experiments.test = experimentFactory({name: "test"});
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.start({
|
||||
name: "test",
|
||||
branch: "branch",
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "value",
|
||||
preferenceType: "string",
|
||||
preferenceBranchType: "default",
|
||||
}),
|
||||
"start threw an error due to a conflicting experiment name",
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// start should throw if an experiment for the given preference is active
|
||||
add_task(withMockExperiments(async function(experiments) {
|
||||
experiments.test = experimentFactory({name: "test", preferenceName: "fake.preference"});
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.start({
|
||||
name: "different",
|
||||
branch: "branch",
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "value",
|
||||
preferenceType: "string",
|
||||
preferenceBranchType: "default",
|
||||
}),
|
||||
"start threw an error due to an active experiment for the given preference",
|
||||
);
|
||||
}));
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function(experiments) {
|
||||
experiments.test = experimentFactory({name: "test", preferenceName: "fake.preference"});
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.start({
|
||||
name: "different",
|
||||
branch: "branch",
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "value",
|
||||
preferenceType: "string",
|
||||
preferenceBranchType: "default",
|
||||
}),
|
||||
"start threw an error due to an active experiment for the given preference",
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// start should throw if an invalid preferenceBranchType is given
|
||||
add_task(withMockExperiments(async function() {
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.start({
|
||||
name: "test",
|
||||
branch: "branch",
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "value",
|
||||
preferenceType: "string",
|
||||
preferenceBranchType: "invalid",
|
||||
}),
|
||||
"start threw an error due to an invalid preference branch type",
|
||||
);
|
||||
}));
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function() {
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.start({
|
||||
name: "test",
|
||||
branch: "branch",
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "value",
|
||||
preferenceType: "string",
|
||||
preferenceBranchType: "invalid",
|
||||
}),
|
||||
"start threw an error due to an invalid preference branch type",
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// start should save experiment data, modify the preference, and register a
|
||||
// watcher.
|
||||
@ -139,217 +152,254 @@ decorate_task(
|
||||
);
|
||||
|
||||
// start should modify the user preference for the user branch type
|
||||
add_task(withMockExperiments(withMockPreferences(async function(experiments, mockPreferences) {
|
||||
const startObserver = sinon.stub(PreferenceExperiments, "startObserver");
|
||||
mockPreferences.set("fake.preference", "oldvalue", "user");
|
||||
mockPreferences.set("fake.preference", "olddefaultvalue", "default");
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
withMockPreferences,
|
||||
withStub(PreferenceExperiments, "startObserver"),
|
||||
async function(experiments, mockPreferences, startObserver) {
|
||||
mockPreferences.set("fake.preference", "olddefaultvalue", "default");
|
||||
mockPreferences.set("fake.preference", "oldvalue", "user");
|
||||
|
||||
await PreferenceExperiments.start({
|
||||
name: "test",
|
||||
branch: "branch",
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "newvalue",
|
||||
preferenceType: "string",
|
||||
preferenceBranchType: "user",
|
||||
});
|
||||
ok(
|
||||
startObserver.calledWith("test", "fake.preference", "string", "newvalue"),
|
||||
"start registered an observer",
|
||||
);
|
||||
|
||||
const expectedExperiment = {
|
||||
name: "test",
|
||||
branch: "branch",
|
||||
expired: false,
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "newvalue",
|
||||
preferenceType: "string",
|
||||
previousPreferenceValue: "oldvalue",
|
||||
preferenceBranchType: "user",
|
||||
};
|
||||
|
||||
const experiment = {};
|
||||
Object.keys(expectedExperiment).forEach(key => experiment[key] = experiments.test[key]);
|
||||
Assert.deepEqual(experiment, expectedExperiment, "start saved the experiment");
|
||||
|
||||
Assert.notEqual(
|
||||
DefaultPreferences.get("fake.preference"),
|
||||
"newvalue",
|
||||
"start did not modify the default preference",
|
||||
);
|
||||
is(Preferences.get("fake.preference"), "newvalue", "start modified the user preference");
|
||||
|
||||
startObserver.restore();
|
||||
})));
|
||||
|
||||
// start should detect if a new preference value type matches the previous value type
|
||||
add_task(withMockPreferences(async function(mockPreferences) {
|
||||
mockPreferences.set("fake.type_preference", "oldvalue");
|
||||
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.start({
|
||||
await PreferenceExperiments.start({
|
||||
name: "test",
|
||||
branch: "branch",
|
||||
preferenceName: "fake.type_preference",
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "newvalue",
|
||||
preferenceType: "string",
|
||||
preferenceBranchType: "user",
|
||||
preferenceValue: 12345,
|
||||
preferenceType: "integer",
|
||||
}),
|
||||
"start threw error for incompatible preference type"
|
||||
);
|
||||
}));
|
||||
});
|
||||
ok(
|
||||
startObserver.calledWith("test", "fake.preference", "string", "newvalue"),
|
||||
"start registered an observer",
|
||||
);
|
||||
|
||||
const expectedExperiment = {
|
||||
name: "test",
|
||||
branch: "branch",
|
||||
expired: false,
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "newvalue",
|
||||
preferenceType: "string",
|
||||
previousPreferenceValue: "oldvalue",
|
||||
preferenceBranchType: "user",
|
||||
};
|
||||
|
||||
const experiment = {};
|
||||
Object.keys(expectedExperiment).forEach(key => experiment[key] = experiments.test[key]);
|
||||
Assert.deepEqual(experiment, expectedExperiment, "start saved the experiment");
|
||||
|
||||
Assert.notEqual(
|
||||
DefaultPreferences.get("fake.preference"),
|
||||
"newvalue",
|
||||
"start did not modify the default preference",
|
||||
);
|
||||
is(Preferences.get("fake.preference"), "newvalue", "start modified the user preference");
|
||||
}
|
||||
);
|
||||
|
||||
// start should detect if a new preference value type matches the previous value type
|
||||
decorate_task(
|
||||
withMockPreferences,
|
||||
async function(mockPreferences) {
|
||||
mockPreferences.set("fake.type_preference", "oldvalue");
|
||||
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.start({
|
||||
name: "test",
|
||||
branch: "branch",
|
||||
preferenceName: "fake.type_preference",
|
||||
preferenceBranchType: "user",
|
||||
preferenceValue: 12345,
|
||||
preferenceType: "integer",
|
||||
}),
|
||||
"start threw error for incompatible preference type"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// startObserver should throw if an observer for the experiment is already
|
||||
// active.
|
||||
add_task(withMockExperiments(async function() {
|
||||
PreferenceExperiments.startObserver("test", "fake.preference", "string", "newvalue");
|
||||
Assert.throws(
|
||||
() => PreferenceExperiments.startObserver("test", "another.fake", "string", "othervalue"),
|
||||
"startObserver threw due to a conflicting active observer",
|
||||
);
|
||||
PreferenceExperiments.stopAllObservers();
|
||||
}));
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function() {
|
||||
PreferenceExperiments.startObserver("test", "fake.preference", "string", "newvalue");
|
||||
Assert.throws(
|
||||
() => PreferenceExperiments.startObserver("test", "another.fake", "string", "othervalue"),
|
||||
"startObserver threw due to a conflicting active observer",
|
||||
);
|
||||
PreferenceExperiments.stopAllObservers();
|
||||
}
|
||||
);
|
||||
|
||||
// startObserver should register an observer that calls stop when a preference
|
||||
// changes from its experimental value.
|
||||
add_task(withMockExperiments(withMockPreferences(async function(mockExperiments, mockPreferences) {
|
||||
const tests = [
|
||||
["string", "startvalue", "experimentvalue", "newvalue"],
|
||||
["boolean", false, true, false],
|
||||
["integer", 1, 2, 42],
|
||||
];
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
withMockPreferences,
|
||||
async function(mockExperiments, mockPreferences) {
|
||||
const tests = [
|
||||
["string", "startvalue", "experimentvalue", "newvalue"],
|
||||
["boolean", false, true, false],
|
||||
["integer", 1, 2, 42],
|
||||
];
|
||||
|
||||
for (const [type, startvalue, experimentvalue, newvalue] of tests) {
|
||||
for (const [type, startvalue, experimentvalue, newvalue] of tests) {
|
||||
const stop = sinon.stub(PreferenceExperiments, "stop");
|
||||
mockPreferences.set("fake.preference" + type, startvalue);
|
||||
|
||||
// NOTE: startObserver does not modify the pref
|
||||
PreferenceExperiments.startObserver("test" + type, "fake.preference" + type, type, experimentvalue);
|
||||
|
||||
// Setting it to the experimental value should not trigger the call.
|
||||
mockPreferences.set("fake.preference" + type, experimentvalue);
|
||||
ok(!stop.called, "Changing to the experimental pref value did not trigger the observer");
|
||||
|
||||
// Setting it to something different should trigger the call.
|
||||
mockPreferences.set("fake.preference" + type, newvalue);
|
||||
ok(stop.called, "Changing to a different value triggered the observer");
|
||||
|
||||
PreferenceExperiments.stopAllObservers();
|
||||
stop.restore();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function testHasObserver() {
|
||||
PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentValue");
|
||||
|
||||
ok(await PreferenceExperiments.hasObserver("test"), "hasObserver should detect active observers");
|
||||
ok(
|
||||
!(await PreferenceExperiments.hasObserver("missing")),
|
||||
"hasObserver shouldn't detect inactive observers",
|
||||
);
|
||||
|
||||
PreferenceExperiments.stopAllObservers();
|
||||
}
|
||||
);
|
||||
|
||||
// stopObserver should throw if there is no observer active for it to stop.
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function() {
|
||||
Assert.throws(
|
||||
() => PreferenceExperiments.stopObserver("neveractive", "another.fake", "othervalue"),
|
||||
"stopObserver threw because there was not matching active observer",
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// stopObserver should cancel an active observer.
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
withMockPreferences,
|
||||
async function(mockExperiments, mockPreferences) {
|
||||
const stop = sinon.stub(PreferenceExperiments, "stop");
|
||||
mockPreferences.set("fake.preference" + type, startvalue);
|
||||
mockPreferences.set("fake.preference", "startvalue");
|
||||
|
||||
// NOTE: startObserver does not modify the pref
|
||||
PreferenceExperiments.startObserver("test" + type, "fake.preference" + type, type, experimentvalue);
|
||||
PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentvalue");
|
||||
PreferenceExperiments.stopObserver("test");
|
||||
|
||||
// Setting it to the experimental value should not trigger the call.
|
||||
Preferences.set("fake.preference" + type, experimentvalue);
|
||||
ok(!stop.called, "Changing to the experimental pref value did not trigger the observer");
|
||||
// Setting the preference now that the observer is stopped should not call
|
||||
// stop.
|
||||
mockPreferences.set("fake.preference", "newvalue");
|
||||
ok(!stop.called, "stopObserver successfully removed the observer");
|
||||
|
||||
// Setting it to something different should trigger the call.
|
||||
Preferences.set("fake.preference" + type, newvalue);
|
||||
ok(stop.called, "Changing to a different value triggered the observer");
|
||||
// Now that the observer is stopped, start should be able to start a new one
|
||||
// without throwing.
|
||||
try {
|
||||
PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentvalue");
|
||||
} catch (err) {
|
||||
ok(false, "startObserver did not throw an error for an observer that was already stopped");
|
||||
}
|
||||
|
||||
PreferenceExperiments.stopAllObservers();
|
||||
stop.restore();
|
||||
}
|
||||
})));
|
||||
|
||||
add_task(withMockExperiments(async function testHasObserver() {
|
||||
PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentValue");
|
||||
|
||||
ok(await PreferenceExperiments.hasObserver("test"), "hasObserver detects active observers");
|
||||
ok(
|
||||
!(await PreferenceExperiments.hasObserver("missing")),
|
||||
"hasObserver doesn't detect inactive observers",
|
||||
);
|
||||
|
||||
PreferenceExperiments.stopAllObservers();
|
||||
}));
|
||||
|
||||
// stopObserver should throw if there is no observer active for it to stop.
|
||||
add_task(withMockExperiments(async function() {
|
||||
Assert.throws(
|
||||
() => PreferenceExperiments.stopObserver("neveractive", "another.fake", "othervalue"),
|
||||
"stopObserver threw because there was not matching active observer",
|
||||
);
|
||||
}));
|
||||
|
||||
// stopObserver should cancel an active observer.
|
||||
add_task(withMockExperiments(withMockPreferences(async function(mockExperiments, mockPreferences) {
|
||||
const stop = sinon.stub(PreferenceExperiments, "stop");
|
||||
mockPreferences.set("fake.preference", "startvalue");
|
||||
|
||||
PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentvalue");
|
||||
PreferenceExperiments.stopObserver("test");
|
||||
|
||||
// Setting the preference now that the observer is stopped should not call
|
||||
// stop.
|
||||
Preferences.set("fake.preference", "newvalue");
|
||||
ok(!stop.called, "stopObserver successfully removed the observer");
|
||||
|
||||
// Now that the observer is stopped, start should be able to start a new one
|
||||
// without throwing.
|
||||
try {
|
||||
PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentvalue");
|
||||
} catch (err) {
|
||||
ok(false, "startObserver did not throw an error for an observer that was already stopped");
|
||||
}
|
||||
|
||||
PreferenceExperiments.stopAllObservers();
|
||||
stop.restore();
|
||||
})));
|
||||
);
|
||||
|
||||
// stopAllObservers
|
||||
add_task(withMockExperiments(withMockPreferences(async function(mockExperiments, mockPreferences) {
|
||||
const stop = sinon.stub(PreferenceExperiments, "stop");
|
||||
mockPreferences.set("fake.preference", "startvalue");
|
||||
mockPreferences.set("other.fake.preference", "startvalue");
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
withMockPreferences,
|
||||
async function(mockExperiments, mockPreferences) {
|
||||
const stop = sinon.stub(PreferenceExperiments, "stop");
|
||||
mockPreferences.set("fake.preference", "startvalue");
|
||||
mockPreferences.set("other.fake.preference", "startvalue");
|
||||
|
||||
PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentvalue");
|
||||
PreferenceExperiments.startObserver("test2", "other.fake.preference", "string", "experimentvalue");
|
||||
PreferenceExperiments.stopAllObservers();
|
||||
|
||||
// Setting the preference now that the observers are stopped should not call
|
||||
// stop.
|
||||
Preferences.set("fake.preference", "newvalue");
|
||||
Preferences.set("other.fake.preference", "newvalue");
|
||||
ok(!stop.called, "stopAllObservers successfully removed all observers");
|
||||
|
||||
// Now that the observers are stopped, start should be able to start new
|
||||
// observers without throwing.
|
||||
try {
|
||||
PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentvalue");
|
||||
PreferenceExperiments.startObserver("test2", "other.fake.preference", "string", "experimentvalue");
|
||||
} catch (err) {
|
||||
ok(false, "startObserver did not throw an error for an observer that was already stopped");
|
||||
}
|
||||
PreferenceExperiments.stopAllObservers();
|
||||
|
||||
PreferenceExperiments.stopAllObservers();
|
||||
stop.restore();
|
||||
})));
|
||||
// Setting the preference now that the observers are stopped should not call
|
||||
// stop.
|
||||
mockPreferences.set("fake.preference", "newvalue");
|
||||
mockPreferences.set("other.fake.preference", "newvalue");
|
||||
ok(!stop.called, "stopAllObservers successfully removed all observers");
|
||||
|
||||
// Now that the observers are stopped, start should be able to start new
|
||||
// observers without throwing.
|
||||
try {
|
||||
PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentvalue");
|
||||
PreferenceExperiments.startObserver("test2", "other.fake.preference", "string", "experimentvalue");
|
||||
} catch (err) {
|
||||
ok(false, "startObserver did not throw an error for an observer that was already stopped");
|
||||
}
|
||||
|
||||
PreferenceExperiments.stopAllObservers();
|
||||
stop.restore();
|
||||
}
|
||||
);
|
||||
|
||||
// markLastSeen should throw if it can't find a matching experiment
|
||||
add_task(withMockExperiments(async function() {
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.markLastSeen("neveractive"),
|
||||
"markLastSeen threw because there was not a matching experiment",
|
||||
);
|
||||
}));
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function() {
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.markLastSeen("neveractive"),
|
||||
"markLastSeen threw because there was not a matching experiment",
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// markLastSeen should update the lastSeen date
|
||||
add_task(withMockExperiments(async function(experiments) {
|
||||
const oldDate = new Date(1988, 10, 1).toJSON();
|
||||
experiments.test = experimentFactory({name: "test", lastSeen: oldDate});
|
||||
await PreferenceExperiments.markLastSeen("test");
|
||||
Assert.notEqual(
|
||||
experiments.test.lastSeen,
|
||||
oldDate,
|
||||
"markLastSeen updated the experiment lastSeen date",
|
||||
);
|
||||
}));
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function(experiments) {
|
||||
const oldDate = new Date(1988, 10, 1).toJSON();
|
||||
experiments.test = experimentFactory({name: "test", lastSeen: oldDate});
|
||||
await PreferenceExperiments.markLastSeen("test");
|
||||
Assert.notEqual(
|
||||
experiments.test.lastSeen,
|
||||
oldDate,
|
||||
"markLastSeen updated the experiment lastSeen date",
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// stop should throw if an experiment with the given name doesn't exist
|
||||
add_task(withMockExperiments(async function() {
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.stop("test"),
|
||||
"stop threw an error because there are no experiments with the given name",
|
||||
);
|
||||
}));
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function() {
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.stop("test"),
|
||||
"stop threw an error because there are no experiments with the given name",
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// stop should throw if the experiment is already expired
|
||||
add_task(withMockExperiments(async function(experiments) {
|
||||
experiments.test = experimentFactory({name: "test", expired: true});
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.stop("test"),
|
||||
"stop threw an error because the experiment was already expired",
|
||||
);
|
||||
}));
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function(experiments) {
|
||||
experiments.test = experimentFactory({name: "test", expired: true});
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.stop("test"),
|
||||
"stop threw an error because the experiment was already expired",
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// stop should mark the experiment as expired, stop its observer, and revert the
|
||||
// preference value.
|
||||
@ -358,6 +408,7 @@ decorate_task(
|
||||
withMockPreferences,
|
||||
withSpy(PreferenceExperiments, "stopObserver"),
|
||||
async function testStop(experiments, mockPreferences, stopObserverSpy) {
|
||||
is(Preferences.get("fake.preference"), null, "preference should start unset");
|
||||
mockPreferences.set(`${startupPrefs}.fake.preference`, "experimentvalue", "user");
|
||||
mockPreferences.set("fake.preference", "experimentvalue", "default");
|
||||
experiments.test = experimentFactory({
|
||||
@ -389,158 +440,187 @@ decorate_task(
|
||||
);
|
||||
|
||||
// stop should also support user pref experiments
|
||||
add_task(withMockExperiments(withMockPreferences(async function(experiments, mockPreferences) {
|
||||
const stopObserver = sinon.stub(PreferenceExperiments, "stopObserver");
|
||||
const hasObserver = sinon.stub(PreferenceExperiments, "hasObserver");
|
||||
hasObserver.returns(true);
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
withMockPreferences,
|
||||
withStub(PreferenceExperiments, "stopObserver"),
|
||||
withStub(PreferenceExperiments, "hasObserver"),
|
||||
async function testStopUserPrefs(experiments, mockPreferences, stopObserver, hasObserver) {
|
||||
hasObserver.returns(true);
|
||||
|
||||
mockPreferences.set("fake.preference", "experimentvalue", "user");
|
||||
experiments.test = experimentFactory({
|
||||
name: "test",
|
||||
expired: false,
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "experimentvalue",
|
||||
preferenceType: "string",
|
||||
previousPreferenceValue: "oldvalue",
|
||||
preferenceBranchType: "user",
|
||||
});
|
||||
PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentvalue");
|
||||
mockPreferences.set("fake.preference", "experimentvalue", "user");
|
||||
experiments.test = experimentFactory({
|
||||
name: "test",
|
||||
expired: false,
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "experimentvalue",
|
||||
preferenceType: "string",
|
||||
previousPreferenceValue: "oldvalue",
|
||||
preferenceBranchType: "user",
|
||||
});
|
||||
PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentvalue");
|
||||
|
||||
await PreferenceExperiments.stop("test");
|
||||
ok(stopObserver.calledWith("test"), "stop removed an observer");
|
||||
is(experiments.test.expired, true, "stop marked the experiment as expired");
|
||||
is(
|
||||
Preferences.get("fake.preference"),
|
||||
"oldvalue",
|
||||
"stop reverted the preference to its previous value",
|
||||
);
|
||||
stopObserver.restore();
|
||||
PreferenceExperiments.stopAllObservers();
|
||||
})));
|
||||
await PreferenceExperiments.stop("test");
|
||||
ok(stopObserver.calledWith("test"), "stop removed an observer");
|
||||
is(experiments.test.expired, true, "stop marked the experiment as expired");
|
||||
is(
|
||||
Preferences.get("fake.preference"),
|
||||
"oldvalue",
|
||||
"stop reverted the preference to its previous value",
|
||||
);
|
||||
stopObserver.restore();
|
||||
PreferenceExperiments.stopAllObservers();
|
||||
}
|
||||
);
|
||||
|
||||
// stop should remove a preference that had no value prior to an experiment for user prefs
|
||||
add_task(withMockExperiments(withMockPreferences(async function(experiments, mockPreferences) {
|
||||
const stopObserver = sinon.stub(PreferenceExperiments, "stopObserver");
|
||||
mockPreferences.set("fake.preference", "experimentvalue", "user");
|
||||
experiments.test = experimentFactory({
|
||||
name: "test",
|
||||
expired: false,
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "experimentvalue",
|
||||
preferenceType: "string",
|
||||
previousPreferenceValue: null,
|
||||
preferenceBranchType: "user",
|
||||
});
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
withMockPreferences,
|
||||
async function(experiments, mockPreferences) {
|
||||
const stopObserver = sinon.stub(PreferenceExperiments, "stopObserver");
|
||||
mockPreferences.set("fake.preference", "experimentvalue", "user");
|
||||
experiments.test = experimentFactory({
|
||||
name: "test",
|
||||
expired: false,
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "experimentvalue",
|
||||
preferenceType: "string",
|
||||
previousPreferenceValue: null,
|
||||
preferenceBranchType: "user",
|
||||
});
|
||||
|
||||
await PreferenceExperiments.stop("test");
|
||||
ok(
|
||||
!Preferences.isSet("fake.preference"),
|
||||
"stop removed the preference that had no value prior to the experiment",
|
||||
);
|
||||
await PreferenceExperiments.stop("test");
|
||||
ok(
|
||||
!Preferences.isSet("fake.preference"),
|
||||
"stop removed the preference that had no value prior to the experiment",
|
||||
);
|
||||
|
||||
stopObserver.restore();
|
||||
})));
|
||||
stopObserver.restore();
|
||||
}
|
||||
);
|
||||
|
||||
// stop should not modify a preference if resetValue is false
|
||||
add_task(withMockExperiments(withMockPreferences(async function(experiments, mockPreferences) {
|
||||
const stopObserver = sinon.stub(PreferenceExperiments, "stopObserver");
|
||||
mockPreferences.set("fake.preference", "customvalue", "default");
|
||||
experiments.test = experimentFactory({
|
||||
name: "test",
|
||||
expired: false,
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "experimentvalue",
|
||||
preferenceType: "string",
|
||||
previousPreferenceValue: "oldvalue",
|
||||
peferenceBranchType: "default",
|
||||
});
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
withMockPreferences,
|
||||
withStub(PreferenceExperiments, "stopObserver"),
|
||||
|
||||
await PreferenceExperiments.stop("test", false);
|
||||
is(
|
||||
DefaultPreferences.get("fake.preference"),
|
||||
"customvalue",
|
||||
"stop did not modify the preference",
|
||||
);
|
||||
async function(experiments, mockPreferences, stopObserver) {
|
||||
mockPreferences.set("fake.preference", "customvalue", "default");
|
||||
experiments.test = experimentFactory({
|
||||
name: "test",
|
||||
expired: false,
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "experimentvalue",
|
||||
preferenceType: "string",
|
||||
previousPreferenceValue: "oldvalue",
|
||||
peferenceBranchType: "default",
|
||||
});
|
||||
|
||||
stopObserver.restore();
|
||||
})));
|
||||
await PreferenceExperiments.stop("test", false);
|
||||
is(
|
||||
DefaultPreferences.get("fake.preference"),
|
||||
"customvalue",
|
||||
"stop did not modify the preference",
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// get should throw if no experiment exists with the given name
|
||||
add_task(withMockExperiments(async function() {
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.get("neverexisted"),
|
||||
"get rejects if no experiment with the given name is found",
|
||||
);
|
||||
}));
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function() {
|
||||
await Assert.rejects(
|
||||
PreferenceExperiments.get("neverexisted"),
|
||||
"get rejects if no experiment with the given name is found",
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// get
|
||||
add_task(withMockExperiments(async function(experiments) {
|
||||
const experiment = experimentFactory({name: "test"});
|
||||
experiments.test = experiment;
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function(experiments) {
|
||||
const experiment = experimentFactory({name: "test"});
|
||||
experiments.test = experiment;
|
||||
|
||||
const fetchedExperiment = await PreferenceExperiments.get("test");
|
||||
Assert.deepEqual(fetchedExperiment, experiment, "get fetches the correct experiment");
|
||||
const fetchedExperiment = await PreferenceExperiments.get("test");
|
||||
Assert.deepEqual(fetchedExperiment, experiment, "get fetches the correct experiment");
|
||||
|
||||
// Modifying the fetched experiment must not edit the data source.
|
||||
fetchedExperiment.name = "othername";
|
||||
is(experiments.test.name, "test", "get returns a copy of the experiment");
|
||||
}));
|
||||
// Modifying the fetched experiment must not edit the data source.
|
||||
fetchedExperiment.name = "othername";
|
||||
is(experiments.test.name, "test", "get returns a copy of the experiment");
|
||||
}
|
||||
);
|
||||
|
||||
add_task(withMockExperiments(async function testGetAll(experiments) {
|
||||
const experiment1 = experimentFactory({name: "experiment1"});
|
||||
const experiment2 = experimentFactory({name: "experiment2", disabled: true});
|
||||
experiments.experiment1 = experiment1;
|
||||
experiments.experiment2 = experiment2;
|
||||
// get all
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function testGetAll(experiments) {
|
||||
const experiment1 = experimentFactory({name: "experiment1"});
|
||||
const experiment2 = experimentFactory({name: "experiment2", disabled: true});
|
||||
experiments.experiment1 = experiment1;
|
||||
experiments.experiment2 = experiment2;
|
||||
|
||||
const fetchedExperiments = await PreferenceExperiments.getAll();
|
||||
is(fetchedExperiments.length, 2, "getAll returns a list of all stored experiments");
|
||||
Assert.deepEqual(
|
||||
fetchedExperiments.find(e => e.name === "experiment1"),
|
||||
experiment1,
|
||||
"getAll returns a list with the correct experiments",
|
||||
);
|
||||
const fetchedExperiment2 = fetchedExperiments.find(e => e.name === "experiment2");
|
||||
Assert.deepEqual(
|
||||
fetchedExperiment2,
|
||||
experiment2,
|
||||
"getAll returns a list with the correct experiments, including disabled ones",
|
||||
);
|
||||
const fetchedExperiments = await PreferenceExperiments.getAll();
|
||||
is(fetchedExperiments.length, 2, "getAll returns a list of all stored experiments");
|
||||
Assert.deepEqual(
|
||||
fetchedExperiments.find(e => e.name === "experiment1"),
|
||||
experiment1,
|
||||
"getAll returns a list with the correct experiments",
|
||||
);
|
||||
const fetchedExperiment2 = fetchedExperiments.find(e => e.name === "experiment2");
|
||||
Assert.deepEqual(
|
||||
fetchedExperiment2,
|
||||
experiment2,
|
||||
"getAll returns a list with the correct experiments, including disabled ones",
|
||||
);
|
||||
|
||||
fetchedExperiment2.name = "othername";
|
||||
is(experiment2.name, "experiment2", "getAll returns copies of the experiments");
|
||||
}));
|
||||
fetchedExperiment2.name = "othername";
|
||||
is(experiment2.name, "experiment2", "getAll returns copies of the experiments");
|
||||
}
|
||||
);
|
||||
|
||||
add_task(withMockExperiments(withMockPreferences(async function testGetAllActive(experiments) {
|
||||
experiments.active = experimentFactory({
|
||||
name: "active",
|
||||
expired: false,
|
||||
});
|
||||
experiments.inactive = experimentFactory({
|
||||
name: "inactive",
|
||||
expired: true,
|
||||
});
|
||||
// get all active
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
withMockPreferences,
|
||||
async function testGetAllActive(experiments) {
|
||||
experiments.active = experimentFactory({
|
||||
name: "active",
|
||||
expired: false,
|
||||
});
|
||||
experiments.inactive = experimentFactory({
|
||||
name: "inactive",
|
||||
expired: true,
|
||||
});
|
||||
|
||||
const activeExperiments = await PreferenceExperiments.getAllActive();
|
||||
Assert.deepEqual(
|
||||
activeExperiments,
|
||||
[experiments.active],
|
||||
"getAllActive only returns active experiments",
|
||||
);
|
||||
const activeExperiments = await PreferenceExperiments.getAllActive();
|
||||
Assert.deepEqual(
|
||||
activeExperiments,
|
||||
[experiments.active],
|
||||
"getAllActive only returns active experiments",
|
||||
);
|
||||
|
||||
activeExperiments[0].name = "newfakename";
|
||||
Assert.notEqual(
|
||||
experiments.active.name,
|
||||
"newfakename",
|
||||
"getAllActive returns copies of stored experiments",
|
||||
);
|
||||
})));
|
||||
activeExperiments[0].name = "newfakename";
|
||||
Assert.notEqual(
|
||||
experiments.active.name,
|
||||
"newfakename",
|
||||
"getAllActive returns copies of stored experiments",
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// has
|
||||
add_task(withMockExperiments(async function(experiments) {
|
||||
experiments.test = experimentFactory({name: "test"});
|
||||
ok(await PreferenceExperiments.has("test"), "has returned true for a stored experiment");
|
||||
ok(!(await PreferenceExperiments.has("missing")), "has returned false for a missing experiment");
|
||||
}));
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function(experiments) {
|
||||
experiments.test = experimentFactory({name: "test"});
|
||||
ok(await PreferenceExperiments.has("test"), "has returned true for a stored experiment");
|
||||
ok(!(await PreferenceExperiments.has("missing")), "has returned false for a missing experiment");
|
||||
}
|
||||
);
|
||||
|
||||
// init should register telemetry experiments
|
||||
decorate_task(
|
||||
@ -639,10 +719,13 @@ decorate_task(
|
||||
["test", "branch", {type: "normandy-pref-test"}],
|
||||
"start() should register the experiment with the provided type",
|
||||
);
|
||||
|
||||
// start sets the passed preference in a way that is hard to mock.
|
||||
// Reset the preference so it doesn't interfere with other tests.
|
||||
Services.prefs.getDefaultBranch("fake.preference").deleteBranch("");
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
// Experiments shouldn't be recorded by init() in telemetry if they are expired
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
@ -655,41 +738,52 @@ decorate_task(
|
||||
);
|
||||
|
||||
// Experiments should end if the preference has been changed when init() is called
|
||||
add_task(withMockExperiments(withMockPreferences(async function testInitChanges(experiments, mockPreferences) {
|
||||
const stopStub = sinon.stub(PreferenceExperiments, "stop");
|
||||
mockPreferences.set("fake.preference", "experiment value", "default");
|
||||
experiments.test = experimentFactory({
|
||||
name: "test",
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "experiment value",
|
||||
});
|
||||
mockPreferences.set("fake.preference", "changed value");
|
||||
await PreferenceExperiments.init();
|
||||
ok(stopStub.calledWith("test"), "Experiment is stopped because value changed");
|
||||
ok(Preferences.get("fake.preference"), "changed value", "Preference value was not changed");
|
||||
stopStub.restore();
|
||||
})));
|
||||
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
withMockPreferences,
|
||||
withStub(PreferenceExperiments, "stop"),
|
||||
async function testInitChanges(experiments, mockPreferences, stopStub) {
|
||||
mockPreferences.set("fake.preference", "experiment value", "default");
|
||||
experiments.test = experimentFactory({
|
||||
name: "test",
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "experiment value",
|
||||
});
|
||||
mockPreferences.set("fake.preference", "changed value");
|
||||
await PreferenceExperiments.init();
|
||||
ok(stopStub.calledWith("test"), "Experiment is stopped because value changed");
|
||||
is(Preferences.get("fake.preference"), "changed value", "Preference value was not changed");
|
||||
}
|
||||
);
|
||||
|
||||
// init should register an observer for experiments
|
||||
add_task(withMockExperiments(withMockPreferences(async function testInitRegistersObserver(experiments, mockPreferences) {
|
||||
const startObserver = sinon.stub(PreferenceExperiments, "startObserver");
|
||||
mockPreferences.set("fake.preference", "experiment value", "default");
|
||||
experiments.test = experimentFactory({
|
||||
name: "test",
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "experiment value",
|
||||
});
|
||||
await PreferenceExperiments.init();
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
withMockPreferences,
|
||||
withStub(PreferenceExperiments, "startObserver"),
|
||||
withStub(PreferenceExperiments, "stop"),
|
||||
withStub(CleanupManager, "addCleanupHandler"),
|
||||
async function testInitRegistersObserver(experiments, mockPreferences, startObserver, stop) {
|
||||
stop.throws("Stop should not be called");
|
||||
mockPreferences.set("fake.preference", "experiment value", "default");
|
||||
is(Preferences.get("fake.preference"), "experiment value", "pref shouldn't have a user value");
|
||||
experiments.test = experimentFactory({
|
||||
name: "test",
|
||||
preferenceName: "fake.preference",
|
||||
preferenceValue: "experiment value",
|
||||
});
|
||||
await PreferenceExperiments.init();
|
||||
|
||||
ok(
|
||||
startObserver.calledWith("test", "fake.preference", "string", "experiment value"),
|
||||
"init registered an observer",
|
||||
);
|
||||
|
||||
startObserver.restore();
|
||||
})));
|
||||
ok(startObserver.calledOnce, "init should register an observer");
|
||||
Assert.deepEqual(
|
||||
startObserver.getCall(0).args,
|
||||
["test", "fake.preference", "string", "experiment value"],
|
||||
"init should register an observer with the right args",
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// saveStartupPrefs
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function testSaveStartupPrefs(experiments) {
|
||||
@ -731,6 +825,7 @@ decorate_task(
|
||||
},
|
||||
);
|
||||
|
||||
// saveStartupPrefs errors for invalid pref type
|
||||
decorate_task(
|
||||
withMockExperiments,
|
||||
async function testSaveStartupPrefsError(experiments) {
|
||||
|
@ -66,34 +66,35 @@ add_task(async function checkFilter() {
|
||||
ok(!(await RecipeRunner.checkFilter(recipe)), "The recipe is available in the filter context");
|
||||
});
|
||||
|
||||
add_task(withMockNormandyApi(async function testClientClassificationCache() {
|
||||
const getStub = sinon.stub(ClientEnvironment, "getClientClassification")
|
||||
.returns(Promise.resolve(false));
|
||||
decorate_task(
|
||||
withMockNormandyApi,
|
||||
withStub(ClientEnvironment, "getClientClassification"),
|
||||
async function testClientClassificationCache(api, getStub) {
|
||||
getStub.returns(Promise.resolve(false));
|
||||
|
||||
await SpecialPowers.pushPrefEnv({set: [
|
||||
["extensions.shield-recipe-client.api_url",
|
||||
"https://example.com/selfsupport-dummy"],
|
||||
]});
|
||||
await SpecialPowers.pushPrefEnv({set: [
|
||||
["extensions.shield-recipe-client.api_url",
|
||||
"https://example.com/selfsupport-dummy"],
|
||||
]});
|
||||
|
||||
// When the experiment pref is false, eagerly call getClientClassification.
|
||||
await SpecialPowers.pushPrefEnv({set: [
|
||||
["extensions.shield-recipe-client.experiments.lazy_classify", false],
|
||||
]});
|
||||
ok(!getStub.called, "getClientClassification hasn't been called");
|
||||
await RecipeRunner.run();
|
||||
ok(getStub.called, "getClientClassification was called eagerly");
|
||||
// When the experiment pref is false, eagerly call getClientClassification.
|
||||
await SpecialPowers.pushPrefEnv({set: [
|
||||
["extensions.shield-recipe-client.experiments.lazy_classify", false],
|
||||
]});
|
||||
ok(!getStub.called, "getClientClassification hasn't been called");
|
||||
await RecipeRunner.run();
|
||||
ok(getStub.called, "getClientClassification was called eagerly");
|
||||
|
||||
// When the experiment pref is true, do not eagerly call getClientClassification.
|
||||
await SpecialPowers.pushPrefEnv({set: [
|
||||
["extensions.shield-recipe-client.experiments.lazy_classify", true],
|
||||
]});
|
||||
getStub.reset();
|
||||
ok(!getStub.called, "getClientClassification hasn't been called");
|
||||
await RecipeRunner.run();
|
||||
ok(!getStub.called, "getClientClassification was not called eagerly");
|
||||
|
||||
getStub.restore();
|
||||
}));
|
||||
// When the experiment pref is true, do not eagerly call getClientClassification.
|
||||
await SpecialPowers.pushPrefEnv({set: [
|
||||
["extensions.shield-recipe-client.experiments.lazy_classify", true],
|
||||
]});
|
||||
getStub.reset();
|
||||
ok(!getStub.called, "getClientClassification hasn't been called");
|
||||
await RecipeRunner.run();
|
||||
ok(!getStub.called, "getClientClassification was not called eagerly");
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Mocks RecipeRunner.loadActionSandboxManagers for testing run.
|
||||
@ -118,211 +119,223 @@ async function withMockActionSandboxManagers(actions, testFunction) {
|
||||
}
|
||||
}
|
||||
|
||||
add_task(withMockNormandyApi(async function testRun(mockApi) {
|
||||
const closeSpy = sinon.spy(AddonStudies, "close");
|
||||
const reportRunner = sinon.stub(Uptake, "reportRunner");
|
||||
const reportAction = sinon.stub(Uptake, "reportAction");
|
||||
const reportRecipe = sinon.stub(Uptake, "reportRecipe");
|
||||
decorate_task(
|
||||
withMockNormandyApi,
|
||||
withSpy(AddonStudies, "close"),
|
||||
withStub(Uptake, "reportRunner"),
|
||||
withStub(Uptake, "reportAction"),
|
||||
withStub(Uptake, "reportRecipe"),
|
||||
async function testRun(mockApi, closeSpy, reportRunner, reportAction, reportRecipe) {
|
||||
const matchAction = {name: "matchAction"};
|
||||
const noMatchAction = {name: "noMatchAction"};
|
||||
mockApi.actions = [matchAction, noMatchAction];
|
||||
|
||||
const matchAction = {name: "matchAction"};
|
||||
const noMatchAction = {name: "noMatchAction"};
|
||||
mockApi.actions = [matchAction, noMatchAction];
|
||||
const matchRecipe = {id: "match", action: "matchAction", filter_expression: "true"};
|
||||
const noMatchRecipe = {id: "noMatch", action: "noMatchAction", filter_expression: "false"};
|
||||
const missingRecipe = {id: "missing", action: "missingAction", filter_expression: "true"};
|
||||
mockApi.recipes = [matchRecipe, noMatchRecipe, missingRecipe];
|
||||
|
||||
const matchRecipe = {id: "match", action: "matchAction", filter_expression: "true"};
|
||||
const noMatchRecipe = {id: "noMatch", action: "noMatchAction", filter_expression: "false"};
|
||||
const missingRecipe = {id: "missing", action: "missingAction", filter_expression: "true"};
|
||||
mockApi.recipes = [matchRecipe, noMatchRecipe, missingRecipe];
|
||||
await withMockActionSandboxManagers(mockApi.actions, async managers => {
|
||||
const matchManager = managers.matchAction;
|
||||
const noMatchManager = managers.noMatchAction;
|
||||
|
||||
await withMockActionSandboxManagers(mockApi.actions, async managers => {
|
||||
const matchManager = managers.matchAction;
|
||||
const noMatchManager = managers.noMatchAction;
|
||||
await RecipeRunner.run();
|
||||
|
||||
await RecipeRunner.run();
|
||||
// match should be called for preExecution, action, and postExecution
|
||||
sinon.assert.calledWith(matchManager.runAsyncCallback, "preExecution");
|
||||
sinon.assert.calledWith(matchManager.runAsyncCallback, "action", matchRecipe);
|
||||
sinon.assert.calledWith(matchManager.runAsyncCallback, "postExecution");
|
||||
|
||||
// match should be called for preExecution, action, and postExecution
|
||||
sinon.assert.calledWith(matchManager.runAsyncCallback, "preExecution");
|
||||
sinon.assert.calledWith(matchManager.runAsyncCallback, "action", matchRecipe);
|
||||
sinon.assert.calledWith(matchManager.runAsyncCallback, "postExecution");
|
||||
// noMatch should be called for preExecution and postExecution, and skipped
|
||||
// for action since the filter expression does not match.
|
||||
sinon.assert.calledWith(noMatchManager.runAsyncCallback, "preExecution");
|
||||
sinon.assert.neverCalledWith(noMatchManager.runAsyncCallback, "action", noMatchRecipe);
|
||||
sinon.assert.calledWith(noMatchManager.runAsyncCallback, "postExecution");
|
||||
|
||||
// noMatch should be called for preExecution and postExecution, and skipped
|
||||
// for action since the filter expression does not match.
|
||||
sinon.assert.calledWith(noMatchManager.runAsyncCallback, "preExecution");
|
||||
sinon.assert.neverCalledWith(noMatchManager.runAsyncCallback, "action", noMatchRecipe);
|
||||
sinon.assert.calledWith(noMatchManager.runAsyncCallback, "postExecution");
|
||||
// missing is never called at all due to no matching action/manager.
|
||||
|
||||
// missing is never called at all due to no matching action/manager.
|
||||
|
||||
// Test uptake reporting
|
||||
sinon.assert.calledWith(reportRunner, Uptake.RUNNER_SUCCESS);
|
||||
sinon.assert.calledWith(reportAction, "matchAction", Uptake.ACTION_SUCCESS);
|
||||
sinon.assert.calledWith(reportAction, "noMatchAction", Uptake.ACTION_SUCCESS);
|
||||
sinon.assert.calledWith(reportRecipe, "match", Uptake.RECIPE_SUCCESS);
|
||||
sinon.assert.neverCalledWith(reportRecipe, "noMatch", Uptake.RECIPE_SUCCESS);
|
||||
sinon.assert.calledWith(reportRecipe, "missing", Uptake.RECIPE_INVALID_ACTION);
|
||||
});
|
||||
|
||||
// Ensure storage is closed after the run.
|
||||
sinon.assert.calledOnce(closeSpy);
|
||||
|
||||
closeSpy.restore();
|
||||
reportRunner.restore();
|
||||
reportAction.restore();
|
||||
reportRecipe.restore();
|
||||
}));
|
||||
|
||||
add_task(withMockNormandyApi(async function testRunRecipeError(mockApi) {
|
||||
const reportRecipe = sinon.stub(Uptake, "reportRecipe");
|
||||
|
||||
const action = {name: "action"};
|
||||
mockApi.actions = [action];
|
||||
|
||||
const recipe = {id: "recipe", action: "action", filter_expression: "true"};
|
||||
mockApi.recipes = [recipe];
|
||||
|
||||
await withMockActionSandboxManagers(mockApi.actions, async managers => {
|
||||
const manager = managers.action;
|
||||
manager.runAsyncCallback.callsFake(async callbackName => {
|
||||
if (callbackName === "action") {
|
||||
throw new Error("Action execution failure");
|
||||
}
|
||||
// Test uptake reporting
|
||||
sinon.assert.calledWith(reportRunner, Uptake.RUNNER_SUCCESS);
|
||||
sinon.assert.calledWith(reportAction, "matchAction", Uptake.ACTION_SUCCESS);
|
||||
sinon.assert.calledWith(reportAction, "noMatchAction", Uptake.ACTION_SUCCESS);
|
||||
sinon.assert.calledWith(reportRecipe, "match", Uptake.RECIPE_SUCCESS);
|
||||
sinon.assert.neverCalledWith(reportRecipe, "noMatch", Uptake.RECIPE_SUCCESS);
|
||||
sinon.assert.calledWith(reportRecipe, "missing", Uptake.RECIPE_INVALID_ACTION);
|
||||
});
|
||||
|
||||
await RecipeRunner.run();
|
||||
// Ensure storage is closed after the run.
|
||||
sinon.assert.calledOnce(closeSpy);
|
||||
}
|
||||
);
|
||||
|
||||
// Uptake should report that the recipe threw an exception
|
||||
sinon.assert.calledWith(reportRecipe, "recipe", Uptake.RECIPE_EXECUTION_ERROR);
|
||||
});
|
||||
decorate_task(
|
||||
withMockNormandyApi,
|
||||
async function testRunRecipeError(mockApi) {
|
||||
const reportRecipe = sinon.stub(Uptake, "reportRecipe");
|
||||
|
||||
reportRecipe.restore();
|
||||
}));
|
||||
const action = {name: "action"};
|
||||
mockApi.actions = [action];
|
||||
|
||||
add_task(withMockNormandyApi(async function testRunFetchFail(mockApi) {
|
||||
const closeSpy = sinon.spy(AddonStudies, "close");
|
||||
const reportRunner = sinon.stub(Uptake, "reportRunner");
|
||||
const recipe = {id: "recipe", action: "action", filter_expression: "true"};
|
||||
mockApi.recipes = [recipe];
|
||||
|
||||
const action = {name: "action"};
|
||||
mockApi.actions = [action];
|
||||
mockApi.fetchRecipes.rejects(new Error("Signature not valid"));
|
||||
await withMockActionSandboxManagers(mockApi.actions, async managers => {
|
||||
const manager = managers.action;
|
||||
manager.runAsyncCallback.callsFake(async callbackName => {
|
||||
if (callbackName === "action") {
|
||||
throw new Error("Action execution failure");
|
||||
}
|
||||
});
|
||||
|
||||
await withMockActionSandboxManagers(mockApi.actions, async managers => {
|
||||
const manager = managers.action;
|
||||
await RecipeRunner.run();
|
||||
await RecipeRunner.run();
|
||||
|
||||
// If the recipe fetch failed, do not run anything.
|
||||
sinon.assert.notCalled(manager.runAsyncCallback);
|
||||
sinon.assert.calledWith(reportRunner, Uptake.RUNNER_SERVER_ERROR);
|
||||
|
||||
// Test that network errors report a specific uptake error
|
||||
reportRunner.reset();
|
||||
mockApi.fetchRecipes.rejects(new Error("NetworkError: The system was down"));
|
||||
await RecipeRunner.run();
|
||||
sinon.assert.calledWith(reportRunner, Uptake.RUNNER_NETWORK_ERROR);
|
||||
|
||||
// Test that signature issues report a specific uptake error
|
||||
reportRunner.reset();
|
||||
mockApi.fetchRecipes.rejects(new NormandyApi.InvalidSignatureError("Signature fail"));
|
||||
await RecipeRunner.run();
|
||||
sinon.assert.calledWith(reportRunner, Uptake.RUNNER_INVALID_SIGNATURE);
|
||||
});
|
||||
|
||||
// If the recipe fetch failed, we don't need to call close since nothing
|
||||
// opened a connection in the first place.
|
||||
sinon.assert.notCalled(closeSpy);
|
||||
|
||||
closeSpy.restore();
|
||||
reportRunner.restore();
|
||||
}));
|
||||
|
||||
add_task(withMockNormandyApi(async function testRunPreExecutionFailure(mockApi) {
|
||||
const closeSpy = sinon.spy(AddonStudies, "close");
|
||||
const reportAction = sinon.stub(Uptake, "reportAction");
|
||||
const reportRecipe = sinon.stub(Uptake, "reportRecipe");
|
||||
|
||||
const passAction = {name: "passAction"};
|
||||
const failAction = {name: "failAction"};
|
||||
mockApi.actions = [passAction, failAction];
|
||||
|
||||
const passRecipe = {id: "pass", action: "passAction", filter_expression: "true"};
|
||||
const failRecipe = {id: "fail", action: "failAction", filter_expression: "true"};
|
||||
mockApi.recipes = [passRecipe, failRecipe];
|
||||
|
||||
await withMockActionSandboxManagers(mockApi.actions, async managers => {
|
||||
const passManager = managers.passAction;
|
||||
const failManager = managers.failAction;
|
||||
failManager.runAsyncCallback.returns(Promise.reject(new Error("oh no")));
|
||||
|
||||
await RecipeRunner.run();
|
||||
|
||||
// pass should be called for preExecution, action, and postExecution
|
||||
sinon.assert.calledWith(passManager.runAsyncCallback, "preExecution");
|
||||
sinon.assert.calledWith(passManager.runAsyncCallback, "action", passRecipe);
|
||||
sinon.assert.calledWith(passManager.runAsyncCallback, "postExecution");
|
||||
|
||||
// fail should only be called for preExecution, since it fails during that
|
||||
sinon.assert.calledWith(failManager.runAsyncCallback, "preExecution");
|
||||
sinon.assert.neverCalledWith(failManager.runAsyncCallback, "action", failRecipe);
|
||||
sinon.assert.neverCalledWith(failManager.runAsyncCallback, "postExecution");
|
||||
|
||||
sinon.assert.calledWith(reportAction, "passAction", Uptake.ACTION_SUCCESS);
|
||||
sinon.assert.calledWith(reportAction, "failAction", Uptake.ACTION_PRE_EXECUTION_ERROR);
|
||||
sinon.assert.calledWith(reportRecipe, "fail", Uptake.RECIPE_ACTION_DISABLED);
|
||||
});
|
||||
|
||||
// Ensure storage is closed after the run, despite the failures.
|
||||
sinon.assert.calledOnce(closeSpy);
|
||||
closeSpy.restore();
|
||||
reportAction.restore();
|
||||
reportRecipe.restore();
|
||||
}));
|
||||
|
||||
add_task(withMockNormandyApi(async function testRunPostExecutionFailure(mockApi) {
|
||||
const reportAction = sinon.stub(Uptake, "reportAction");
|
||||
|
||||
const failAction = {name: "failAction"};
|
||||
mockApi.actions = [failAction];
|
||||
|
||||
const failRecipe = {action: "failAction", filter_expression: "true"};
|
||||
mockApi.recipes = [failRecipe];
|
||||
|
||||
await withMockActionSandboxManagers(mockApi.actions, async managers => {
|
||||
const failManager = managers.failAction;
|
||||
failManager.runAsyncCallback.callsFake(async callbackName => {
|
||||
if (callbackName === "postExecution") {
|
||||
throw new Error("postExecution failure");
|
||||
}
|
||||
// Uptake should report that the recipe threw an exception
|
||||
sinon.assert.calledWith(reportRecipe, "recipe", Uptake.RECIPE_EXECUTION_ERROR);
|
||||
});
|
||||
|
||||
await RecipeRunner.run();
|
||||
reportRecipe.restore();
|
||||
}
|
||||
);
|
||||
|
||||
// fail should be called for every stage
|
||||
sinon.assert.calledWith(failManager.runAsyncCallback, "preExecution");
|
||||
sinon.assert.calledWith(failManager.runAsyncCallback, "action", failRecipe);
|
||||
sinon.assert.calledWith(failManager.runAsyncCallback, "postExecution");
|
||||
decorate_task(
|
||||
withMockNormandyApi,
|
||||
async function testRunFetchFail(mockApi) {
|
||||
const closeSpy = sinon.spy(AddonStudies, "close");
|
||||
const reportRunner = sinon.stub(Uptake, "reportRunner");
|
||||
|
||||
// Uptake should report a post-execution error
|
||||
sinon.assert.calledWith(reportAction, "failAction", Uptake.ACTION_POST_EXECUTION_ERROR);
|
||||
});
|
||||
const action = {name: "action"};
|
||||
mockApi.actions = [action];
|
||||
mockApi.fetchRecipes.rejects(new Error("Signature not valid"));
|
||||
|
||||
reportAction.restore();
|
||||
}));
|
||||
await withMockActionSandboxManagers(mockApi.actions, async managers => {
|
||||
const manager = managers.action;
|
||||
await RecipeRunner.run();
|
||||
|
||||
add_task(withMockNormandyApi(async function testLoadActionSandboxManagers(mockApi) {
|
||||
mockApi.actions = [
|
||||
{name: "normalAction"},
|
||||
{name: "missingImpl"},
|
||||
];
|
||||
mockApi.implementations.normalAction = "window.scriptRan = true";
|
||||
// If the recipe fetch failed, do not run anything.
|
||||
sinon.assert.notCalled(manager.runAsyncCallback);
|
||||
sinon.assert.calledWith(reportRunner, Uptake.RUNNER_SERVER_ERROR);
|
||||
|
||||
const managers = await RecipeRunner.loadActionSandboxManagers();
|
||||
ok("normalAction" in managers, "Actions with implementations have managers");
|
||||
ok(!("missingImpl" in managers), "Actions without implementations are skipped");
|
||||
// Test that network errors report a specific uptake error
|
||||
reportRunner.reset();
|
||||
mockApi.fetchRecipes.rejects(new Error("NetworkError: The system was down"));
|
||||
await RecipeRunner.run();
|
||||
sinon.assert.calledWith(reportRunner, Uptake.RUNNER_NETWORK_ERROR);
|
||||
|
||||
const normalManager = managers.normalAction;
|
||||
ok(
|
||||
await normalManager.evalInSandbox("window.scriptRan"),
|
||||
"Implementations are run in the sandbox",
|
||||
);
|
||||
}));
|
||||
// Test that signature issues report a specific uptake error
|
||||
reportRunner.reset();
|
||||
mockApi.fetchRecipes.rejects(new NormandyApi.InvalidSignatureError("Signature fail"));
|
||||
await RecipeRunner.run();
|
||||
sinon.assert.calledWith(reportRunner, Uptake.RUNNER_INVALID_SIGNATURE);
|
||||
});
|
||||
|
||||
// If the recipe fetch failed, we don't need to call close since nothing
|
||||
// opened a connection in the first place.
|
||||
sinon.assert.notCalled(closeSpy);
|
||||
|
||||
closeSpy.restore();
|
||||
reportRunner.restore();
|
||||
}
|
||||
);
|
||||
|
||||
decorate_task(
|
||||
withMockNormandyApi,
|
||||
async function testRunPreExecutionFailure(mockApi) {
|
||||
const closeSpy = sinon.spy(AddonStudies, "close");
|
||||
const reportAction = sinon.stub(Uptake, "reportAction");
|
||||
const reportRecipe = sinon.stub(Uptake, "reportRecipe");
|
||||
|
||||
const passAction = {name: "passAction"};
|
||||
const failAction = {name: "failAction"};
|
||||
mockApi.actions = [passAction, failAction];
|
||||
|
||||
const passRecipe = {id: "pass", action: "passAction", filter_expression: "true"};
|
||||
const failRecipe = {id: "fail", action: "failAction", filter_expression: "true"};
|
||||
mockApi.recipes = [passRecipe, failRecipe];
|
||||
|
||||
await withMockActionSandboxManagers(mockApi.actions, async managers => {
|
||||
const passManager = managers.passAction;
|
||||
const failManager = managers.failAction;
|
||||
failManager.runAsyncCallback.returns(Promise.reject(new Error("oh no")));
|
||||
|
||||
await RecipeRunner.run();
|
||||
|
||||
// pass should be called for preExecution, action, and postExecution
|
||||
sinon.assert.calledWith(passManager.runAsyncCallback, "preExecution");
|
||||
sinon.assert.calledWith(passManager.runAsyncCallback, "action", passRecipe);
|
||||
sinon.assert.calledWith(passManager.runAsyncCallback, "postExecution");
|
||||
|
||||
// fail should only be called for preExecution, since it fails during that
|
||||
sinon.assert.calledWith(failManager.runAsyncCallback, "preExecution");
|
||||
sinon.assert.neverCalledWith(failManager.runAsyncCallback, "action", failRecipe);
|
||||
sinon.assert.neverCalledWith(failManager.runAsyncCallback, "postExecution");
|
||||
|
||||
sinon.assert.calledWith(reportAction, "passAction", Uptake.ACTION_SUCCESS);
|
||||
sinon.assert.calledWith(reportAction, "failAction", Uptake.ACTION_PRE_EXECUTION_ERROR);
|
||||
sinon.assert.calledWith(reportRecipe, "fail", Uptake.RECIPE_ACTION_DISABLED);
|
||||
});
|
||||
|
||||
// Ensure storage is closed after the run, despite the failures.
|
||||
sinon.assert.calledOnce(closeSpy);
|
||||
closeSpy.restore();
|
||||
reportAction.restore();
|
||||
reportRecipe.restore();
|
||||
}
|
||||
);
|
||||
|
||||
decorate_task(
|
||||
withMockNormandyApi,
|
||||
async function testRunPostExecutionFailure(mockApi) {
|
||||
const reportAction = sinon.stub(Uptake, "reportAction");
|
||||
|
||||
const failAction = {name: "failAction"};
|
||||
mockApi.actions = [failAction];
|
||||
|
||||
const failRecipe = {action: "failAction", filter_expression: "true"};
|
||||
mockApi.recipes = [failRecipe];
|
||||
|
||||
await withMockActionSandboxManagers(mockApi.actions, async managers => {
|
||||
const failManager = managers.failAction;
|
||||
failManager.runAsyncCallback.callsFake(async callbackName => {
|
||||
if (callbackName === "postExecution") {
|
||||
throw new Error("postExecution failure");
|
||||
}
|
||||
});
|
||||
|
||||
await RecipeRunner.run();
|
||||
|
||||
// fail should be called for every stage
|
||||
sinon.assert.calledWith(failManager.runAsyncCallback, "preExecution");
|
||||
sinon.assert.calledWith(failManager.runAsyncCallback, "action", failRecipe);
|
||||
sinon.assert.calledWith(failManager.runAsyncCallback, "postExecution");
|
||||
|
||||
// Uptake should report a post-execution error
|
||||
sinon.assert.calledWith(reportAction, "failAction", Uptake.ACTION_POST_EXECUTION_ERROR);
|
||||
});
|
||||
|
||||
reportAction.restore();
|
||||
}
|
||||
);
|
||||
|
||||
decorate_task(
|
||||
withMockNormandyApi,
|
||||
async function testLoadActionSandboxManagers(mockApi) {
|
||||
mockApi.actions = [
|
||||
{name: "normalAction"},
|
||||
{name: "missingImpl"},
|
||||
];
|
||||
mockApi.implementations.normalAction = "window.scriptRan = true";
|
||||
|
||||
const managers = await RecipeRunner.loadActionSandboxManagers();
|
||||
ok("normalAction" in managers, "Actions with implementations have managers");
|
||||
ok(!("missingImpl" in managers), "Actions without implementations are skipped");
|
||||
|
||||
const normalManager = managers.normalAction;
|
||||
ok(
|
||||
await normalManager.evalInSandbox("window.scriptRan"),
|
||||
"Implementations are run in the sandbox",
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// test init() in dev mode
|
||||
decorate_task(
|
||||
@ -402,8 +415,9 @@ decorate_task(
|
||||
withStub(RecipeRunner, "enable"),
|
||||
withStub(RecipeRunner, "disable"),
|
||||
withStub(CleanupManager, "addCleanupHandler"),
|
||||
withStub(AddonStudies, "stop"),
|
||||
|
||||
async function testPrefWatching(runStub, enableStub, disableStub, addCleanupHandlerStub) {
|
||||
async function testPrefWatching(runStub, enableStub, disableStub, addCleanupHandlerStub, stopStub) {
|
||||
await RecipeRunner.init();
|
||||
is(enableStub.callCount, 1, "Enable should be called initially");
|
||||
is(disableStub.callCount, 0, "Disable should not be called initially");
|
||||
|
@ -6,14 +6,13 @@ Cu.import("resource://shield-recipe-client/lib/AddonStudies.jsm", this);
|
||||
const OPT_OUT_PREF = "app.shield.optoutstudies.enabled";
|
||||
|
||||
decorate_task(
|
||||
withPrefEnv({
|
||||
set: [[OPT_OUT_PREF, true]],
|
||||
}),
|
||||
withMockPreferences,
|
||||
AddonStudies.withStudies([
|
||||
studyFactory({active: true}),
|
||||
studyFactory({active: true}),
|
||||
]),
|
||||
async function testDisableStudiesWhenOptOutDisabled([study1, study2]) {
|
||||
async function testDisableStudiesWhenOptOutDisabled(mockPreferences, [study1, study2]) {
|
||||
mockPreferences.set(OPT_OUT_PREF, true);
|
||||
const observers = [
|
||||
studyEndObserved(study1.recipeId),
|
||||
studyEndObserved(study2.recipeId),
|
||||
|
@ -31,12 +31,10 @@ const experimentPref3 = "test.initExperimentPrefs3";
|
||||
const experimentPref4 = "test.initExperimentPrefs4";
|
||||
|
||||
decorate_task(
|
||||
withPrefEnv({
|
||||
clear: [[initPref1], [initPref2], [initPref3]],
|
||||
}),
|
||||
withBootstrap,
|
||||
async function testInitShieldPrefs(Bootstrap) {
|
||||
const defaultBranch = Services.prefs.getDefaultBranch("");
|
||||
|
||||
const prefDefaults = {
|
||||
[initPref1]: true,
|
||||
[initPref2]: 2,
|
||||
@ -74,6 +72,8 @@ decorate_task(
|
||||
`Pref ${pref} doesn't have a user value after being initialized.`,
|
||||
);
|
||||
}
|
||||
|
||||
defaultBranch.deleteBranch("test.");
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -184,11 +184,27 @@ class MockPreferences {
|
||||
for (const [branchName, values] of Object.entries(this.oldValues)) {
|
||||
const preferenceBranch = preferenceBranches[branchName];
|
||||
for (const [name, {oldValue, existed}] of Object.entries(values)) {
|
||||
const before = preferenceBranch.get(name);
|
||||
|
||||
if (before === oldValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (existed) {
|
||||
preferenceBranch.set(name, oldValue);
|
||||
} else if (branchName === "default") {
|
||||
Services.prefs.getDefaultBranch(name).deleteBranch("");
|
||||
} else {
|
||||
preferenceBranch.reset(name);
|
||||
}
|
||||
|
||||
const after = preferenceBranch.get(name);
|
||||
if (before === after && before !== undefined) {
|
||||
throw new Error(
|
||||
`Couldn't reset pref "${name}" to "${oldValue}" on "${branchName}" branch ` +
|
||||
`(value stayed "${before}", did ${existed ? "" : "not "}exist)`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironmen
|
||||
const APPLY_CONFIG_TIMEOUT_MS = 60 * 1000;
|
||||
const HOME_PAGE = "chrome://mozscreenshots/content/lib/mozscreenshots.html";
|
||||
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
@ -34,6 +35,7 @@ this.TestRunner = {
|
||||
|
||||
init(extensionPath) {
|
||||
this._extensionPath = extensionPath;
|
||||
this.setupOS();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -46,6 +48,28 @@ this.TestRunner = {
|
||||
this.mochitestScope = mochitestScope;
|
||||
},
|
||||
|
||||
setupOS() {
|
||||
switch (AppConstants.platform) {
|
||||
case "macosx": {
|
||||
this.disableNotificationCenter();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
disableNotificationCenter() {
|
||||
let killall = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
killall.initWithPath("/bin/bash");
|
||||
|
||||
let killallP = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
|
||||
killallP.init(killall);
|
||||
let ncPlist = "/System/Library/LaunchAgents/com.apple.notificationcenterui.plist";
|
||||
let killallArgs = ["-c",
|
||||
`/bin/launchctl unload -w ${ncPlist} && ` +
|
||||
"/usr/bin/killall -v NotificationCenter"];
|
||||
killallP.run(true, killallArgs, killallArgs.length);
|
||||
},
|
||||
|
||||
/**
|
||||
* Load specified sets, execute all combinations of them, and capture screenshots.
|
||||
*/
|
||||
|
@ -401,19 +401,22 @@ function* waitForServiceWorkerActivation(swUrl, document) {
|
||||
/**
|
||||
* Set all preferences needed to enable service worker debugging and testing.
|
||||
*/
|
||||
function enableServiceWorkerDebugging() {
|
||||
return new Promise(done => {
|
||||
let options = { "set": [
|
||||
// Enable service workers.
|
||||
["dom.serviceWorkers.enabled", true],
|
||||
// Accept workers from mochitest's http.
|
||||
["dom.serviceWorkers.testing.enabled", true],
|
||||
// Force single content process.
|
||||
["dom.ipc.processCount", 1],
|
||||
]};
|
||||
function* enableServiceWorkerDebugging() {
|
||||
let options = { "set": [
|
||||
// Enable service workers.
|
||||
["dom.serviceWorkers.enabled", true],
|
||||
// Accept workers from mochitest's http.
|
||||
["dom.serviceWorkers.testing.enabled", true],
|
||||
// Force single content process.
|
||||
["dom.ipc.processCount", 1],
|
||||
]};
|
||||
|
||||
// Wait for dom.ipc.processCount to be updated before releasing processes.
|
||||
yield new Promise(done => {
|
||||
SpecialPowers.pushPrefEnv(options, done);
|
||||
Services.ppmm.releaseCachedProcesses();
|
||||
});
|
||||
|
||||
Services.ppmm.releaseCachedProcesses();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -693,3 +693,47 @@ function createTestHTTPServer() {
|
||||
server.start(-1);
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject `EventUtils` helpers into ContentTask scope.
|
||||
*
|
||||
* This helper is automatically exposed to mochitest browser tests,
|
||||
* but is missing from content task scope.
|
||||
* You should call this method only once per <browser> tag
|
||||
*
|
||||
* @param {xul:browser} browser
|
||||
* Reference to the browser in which we load content task
|
||||
*/
|
||||
async function injectEventUtilsInContentTask(browser) {
|
||||
await ContentTask.spawn(browser, {}, function* () {
|
||||
if ("EventUtils" in this) {
|
||||
return;
|
||||
}
|
||||
|
||||
let EventUtils = this.EventUtils = {};
|
||||
|
||||
EventUtils.window = {};
|
||||
EventUtils.parent = EventUtils.window;
|
||||
/* eslint-disable camelcase */
|
||||
EventUtils._EU_Ci = Components.interfaces;
|
||||
EventUtils._EU_Cc = Components.classes;
|
||||
/* eslint-enable camelcase */
|
||||
// EventUtils' `sendChar` function relies on the navigator to synthetize events.
|
||||
EventUtils.navigator = content.navigator;
|
||||
EventUtils.KeyboardEvent = content.KeyboardEvent;
|
||||
|
||||
EventUtils.synthesizeClick = element => new Promise(resolve => {
|
||||
element.addEventListener("click", function () {
|
||||
resolve();
|
||||
}, {once: true});
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(element,
|
||||
{ type: "mousedown", isSynthesized: false }, content);
|
||||
EventUtils.synthesizeMouseAtCenter(element,
|
||||
{ type: "mouseup", isSynthesized: false }, content);
|
||||
});
|
||||
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
|
||||
});
|
||||
}
|
||||
|
@ -10,8 +10,12 @@ const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
// Components
|
||||
const MonitorPanel = createFactory(require("./MonitorPanel"));
|
||||
const StatisticsPanel = createFactory(require("./StatisticsPanel"));
|
||||
loader.lazyGetter(this, "MonitorPanel", function () {
|
||||
return createFactory(require("./MonitorPanel"));
|
||||
});
|
||||
loader.lazyGetter(this, "StatisticsPanel", function () {
|
||||
return createFactory(require("./StatisticsPanel"));
|
||||
});
|
||||
|
||||
const { div } = dom;
|
||||
|
||||
|
@ -23,11 +23,19 @@ const {
|
||||
const { sortObjectKeys } = require("../utils/sort-utils");
|
||||
|
||||
// Components
|
||||
const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
|
||||
const MDNLink = createFactory(require("./MdnLink"));
|
||||
const PropertiesView = createFactory(require("./PropertiesView"));
|
||||
|
||||
const { Rep } = REPS;
|
||||
loader.lazyGetter(this, "MDNLink", function () {
|
||||
return createFactory(require("./MdnLink"));
|
||||
});
|
||||
|
||||
loader.lazyGetter(this, "Rep", function () {
|
||||
return require("devtools/client/shared/components/reps/reps").REPS.Rep;
|
||||
});
|
||||
loader.lazyGetter(this, "MODE", function () {
|
||||
return require("devtools/client/shared/components/reps/reps").MODE;
|
||||
});
|
||||
|
||||
const { button, div, input, textarea, span } = dom;
|
||||
|
||||
const EDIT_AND_RESEND = L10N.getStr("netmonitor.summary.editAndResend");
|
||||
|
@ -7,6 +7,7 @@
|
||||
const Services = require("Services");
|
||||
const { Component, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const { div } = dom;
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
|
||||
@ -16,10 +17,13 @@ const { getSelectedRequest } = require("../selectors/index");
|
||||
|
||||
// Components
|
||||
const SplitBox = createFactory(require("devtools/client/shared/components/splitter/SplitBox"));
|
||||
const NetworkDetailsPanel = createFactory(require("./NetworkDetailsPanel"));
|
||||
const RequestList = createFactory(require("./RequestList"));
|
||||
const Toolbar = createFactory(require("./Toolbar"));
|
||||
const { div } = dom;
|
||||
|
||||
loader.lazyGetter(this, "NetworkDetailsPanel", function () {
|
||||
return createFactory(require("./NetworkDetailsPanel"));
|
||||
});
|
||||
|
||||
const MediaQueryList = window.matchMedia("(min-width: 700px)");
|
||||
|
||||
/**
|
||||
|
@ -12,8 +12,12 @@ const Actions = require("../actions/index");
|
||||
const { getSelectedRequest } = require("../selectors/index");
|
||||
|
||||
// Components
|
||||
const CustomRequestPanel = createFactory(require("./CustomRequestPanel"));
|
||||
const TabboxPanel = createFactory(require("./TabboxPanel"));
|
||||
loader.lazyGetter(this, "CustomRequestPanel", function () {
|
||||
return createFactory(require("./CustomRequestPanel"));
|
||||
});
|
||||
loader.lazyGetter(this, "TabboxPanel", function () {
|
||||
return createFactory(require("./TabboxPanel"));
|
||||
});
|
||||
|
||||
const { div } = dom;
|
||||
|
||||
|
@ -10,18 +10,32 @@ const { Component, createFactory } = require("devtools/client/shared/vendor/reac
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
|
||||
const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
|
||||
const { Rep } = REPS;
|
||||
|
||||
const { FILTER_SEARCH_DELAY } = require("../constants");
|
||||
|
||||
// Components
|
||||
const SearchBox = createFactory(require("devtools/client/shared/components/SearchBox"));
|
||||
const TreeViewClass = require("devtools/client/shared/components/tree/TreeView");
|
||||
const TreeView = createFactory(TreeViewClass);
|
||||
const TreeRow = createFactory(require("devtools/client/shared/components/tree/TreeRow"));
|
||||
const SourceEditor = createFactory(require("./SourceEditor"));
|
||||
const HTMLPreview = createFactory(require("./HtmlPreview"));
|
||||
const SearchBox = createFactory(require("devtools/client/shared/components/SearchBox"));
|
||||
|
||||
loader.lazyGetter(this, "SearchBox", function () {
|
||||
return createFactory(require("devtools/client/shared/components/SearchBox"));
|
||||
});
|
||||
loader.lazyGetter(this, "TreeRow", function () {
|
||||
return createFactory(require("devtools/client/shared/components/tree/TreeRow"));
|
||||
});
|
||||
loader.lazyGetter(this, "SourceEditor", function () {
|
||||
return createFactory(require("./SourceEditor"));
|
||||
});
|
||||
loader.lazyGetter(this, "HTMLPreview", function () {
|
||||
return createFactory(require("./HtmlPreview"));
|
||||
});
|
||||
|
||||
loader.lazyGetter(this, "Rep", function () {
|
||||
return require("devtools/client/shared/components/reps/reps").REPS.Rep;
|
||||
});
|
||||
loader.lazyGetter(this, "MODE", function () {
|
||||
return require("devtools/client/shared/components/reps/reps").MODE;
|
||||
});
|
||||
|
||||
const { div, tr, td } = dom;
|
||||
const AUTO_EXPAND_MAX_LEVEL = 7;
|
||||
|
@ -6,14 +6,18 @@
|
||||
|
||||
const { createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const { div } = dom;
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
|
||||
// Components
|
||||
const RequestListContent = createFactory(require("./RequestListContent"));
|
||||
const RequestListEmptyNotice = createFactory(require("./RequestListEmptyNotice"));
|
||||
const StatusBar = createFactory(require("./StatusBar"));
|
||||
|
||||
const { div } = dom;
|
||||
loader.lazyGetter(this, "RequestListContent", function () {
|
||||
return createFactory(require("./RequestListContent"));
|
||||
});
|
||||
loader.lazyGetter(this, "RequestListEmptyNotice", function () {
|
||||
return createFactory(require("./RequestListEmptyNotice"));
|
||||
});
|
||||
|
||||
/**
|
||||
* Request panel component
|
||||
|
@ -9,10 +9,7 @@ const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
const { HTMLTooltip } = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
const {
|
||||
setImageTooltip,
|
||||
getImageDimensions,
|
||||
} = require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
|
||||
|
||||
const Actions = require("../actions/index");
|
||||
const { formDataURI } = require("../utils/request-utils");
|
||||
const {
|
||||
@ -22,6 +19,15 @@ const {
|
||||
getWaterfallScale,
|
||||
} = require("../selectors/index");
|
||||
|
||||
loader.lazyGetter(this, "setImageTooltip", function () {
|
||||
return require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper")
|
||||
.setImageTooltip;
|
||||
});
|
||||
loader.lazyGetter(this, "getImageDimensions", function () {
|
||||
return require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper")
|
||||
.getImageDimensions;
|
||||
});
|
||||
|
||||
// Components
|
||||
const RequestListHeader = createFactory(require("./RequestListHeader"));
|
||||
const RequestListItem = createFactory(require("./RequestListItem"));
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
const { Component, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const { div } = dom;
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const {
|
||||
fetchNetworkUpdatePacket,
|
||||
@ -14,28 +15,56 @@ const {
|
||||
const { RESPONSE_HEADERS } = require("../constants");
|
||||
|
||||
// Components
|
||||
const RequestListColumnCause = createFactory(require("./RequestListColumnCause"));
|
||||
const RequestListColumnContentSize = createFactory(require("./RequestListColumnContentSize"));
|
||||
const RequestListColumnCookies = createFactory(require("./RequestListColumnCookies"));
|
||||
const RequestListColumnDomain = createFactory(require("./RequestListColumnDomain"));
|
||||
const RequestListColumnDuration = createFactory(require("./RequestListColumnDuration"));
|
||||
const RequestListColumnEndTime = createFactory(require("./RequestListColumnEndTime"));
|
||||
const RequestListColumnFile = createFactory(require("./RequestListColumnFile"));
|
||||
const RequestListColumnLatency = createFactory(require("./RequestListColumnLatency"));
|
||||
const RequestListColumnMethod = createFactory(require("./RequestListColumnMethod"));
|
||||
const RequestListColumnProtocol = createFactory(require("./RequestListColumnProtocol"));
|
||||
const RequestListColumnRemoteIP = createFactory(require("./RequestListColumnRemoteIp"));
|
||||
const RequestListColumnResponseHeader = createFactory(require("./RequestListColumnResponseHeader"));
|
||||
const RequestListColumnResponseTime = createFactory(require("./RequestListColumnResponseTime"));
|
||||
const RequestListColumnScheme = createFactory(require("./RequestListColumnScheme"));
|
||||
const RequestListColumnSetCookies = createFactory(require("./RequestListColumnSetCookies"));
|
||||
const RequestListColumnStartTime = createFactory(require("./RequestListColumnStartTime"));
|
||||
const RequestListColumnStatus = createFactory(require("./RequestListColumnStatus"));
|
||||
const RequestListColumnTransferredSize = createFactory(require("./RequestListColumnTransferredSize"));
|
||||
const RequestListColumnType = createFactory(require("./RequestListColumnType"));
|
||||
const RequestListColumnWaterfall = createFactory(require("./RequestListColumnWaterfall"));
|
||||
/* global
|
||||
RequestListColumnCause,
|
||||
RequestListColumnContentSize,
|
||||
RequestListColumnCookies,
|
||||
RequestListColumnDomain,
|
||||
RequestListColumnDuration,
|
||||
RequestListColumnEndTime,
|
||||
RequestListColumnFile,
|
||||
RequestListColumnLatency,
|
||||
RequestListColumnMethod,
|
||||
RequestListColumnProtocol,
|
||||
RequestListColumnRemoteIP,
|
||||
RequestListColumnResponseHeader,
|
||||
RequestListColumnResponseTime,
|
||||
RequestListColumnScheme,
|
||||
RequestListColumnSetCookies,
|
||||
RequestListColumnStartTime,
|
||||
RequestListColumnStatus,
|
||||
RequestListColumnTransferredSize,
|
||||
RequestListColumnType,
|
||||
RequestListColumnWaterfall
|
||||
*/
|
||||
|
||||
const { div } = dom;
|
||||
const COLUMNS = [
|
||||
"Cause",
|
||||
"ContentSize",
|
||||
"Cookies",
|
||||
"Domain",
|
||||
"Duration",
|
||||
"EndTime",
|
||||
"File",
|
||||
"Latency",
|
||||
"Method",
|
||||
"Protocol",
|
||||
"RemoteIP",
|
||||
"ResponseHeader",
|
||||
"ResponseTime",
|
||||
"Scheme",
|
||||
"SetCookies",
|
||||
"StartTime",
|
||||
"Status",
|
||||
"TransferredSize",
|
||||
"Type",
|
||||
"Waterfall"
|
||||
];
|
||||
for (let name of COLUMNS) {
|
||||
loader.lazyGetter(this, "RequestListColumn" + name, function () {
|
||||
return createFactory(require("./RequestListColumn" + name));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by shouldComponentUpdate: compare two items, and compare only properties
|
||||
|
@ -24,7 +24,7 @@ DevToolsModules(
|
||||
'RequestListColumnLatency.js',
|
||||
'RequestListColumnMethod.js',
|
||||
'RequestListColumnProtocol.js',
|
||||
'RequestListColumnRemoteIp.js',
|
||||
'RequestListColumnRemoteIP.js',
|
||||
'RequestListColumnResponseHeader.js',
|
||||
'RequestListColumnResponseTime.js',
|
||||
'RequestListColumnScheme.js',
|
||||
|
@ -5,13 +5,7 @@
|
||||
"use strict";
|
||||
|
||||
const Services = require("Services");
|
||||
const { Curl } = require("devtools/client/shared/curl");
|
||||
const { gDevTools } = require("devtools/client/framework/devtools");
|
||||
const { saveAs } = require("devtools/client/shared/file-saver");
|
||||
const { copyString } = require("devtools/shared/platform/clipboard");
|
||||
const { showMenu } = require("devtools/client/netmonitor/src/utils/menu");
|
||||
const { openRequestInTab } = require("devtools/client/netmonitor/src/utils/firefox/open-request-in-tab");
|
||||
const { HarExporter } = require("../har/har-exporter");
|
||||
const { L10N } = require("../utils/l10n");
|
||||
const {
|
||||
formDataURI,
|
||||
@ -20,6 +14,13 @@ const {
|
||||
parseQueryString,
|
||||
} = require("../utils/request-utils");
|
||||
|
||||
loader.lazyRequireGetter(this, "Curl", "devtools/client/shared/curl", true);
|
||||
loader.lazyRequireGetter(this, "saveAs", "devtools/client/shared/file-saver", true);
|
||||
loader.lazyRequireGetter(this, "copyString", "devtools/shared/platform/clipboard", true);
|
||||
loader.lazyRequireGetter(this, "showMenu", "devtools/client/netmonitor/src/utils/menu", true);
|
||||
loader.lazyRequireGetter(this, "openRequestInTab", "devtools/client/netmonitor/src/utils/firefox/open-request-in-tab", true);
|
||||
loader.lazyRequireGetter(this, "HarExporter", "devtools/client/netmonitor/src/har/har-exporter", true);
|
||||
|
||||
class RequestListContextMenu {
|
||||
constructor(props) {
|
||||
this.props = props;
|
||||
|
@ -9,6 +9,8 @@ const TEST_URL = `${URL_ROOT}touch.html`;
|
||||
const PREF_DOM_META_VIEWPORT_ENABLED = "dom.meta-viewport.enabled";
|
||||
|
||||
addRDMTask(TEST_URL, function* ({ ui }) {
|
||||
yield injectEventUtilsInContentTask(ui.getViewportBrowser());
|
||||
|
||||
yield waitBootstrap(ui);
|
||||
yield testWithNoTouch(ui);
|
||||
yield toggleTouchSimulation(ui);
|
||||
@ -19,10 +21,7 @@ addRDMTask(TEST_URL, function* ({ ui }) {
|
||||
});
|
||||
|
||||
function* testWithNoTouch(ui) {
|
||||
yield injectEventUtils(ui);
|
||||
yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
|
||||
let { EventUtils } = content;
|
||||
|
||||
let div = content.document.querySelector("div");
|
||||
let x = 0, y = 0;
|
||||
|
||||
@ -62,11 +61,7 @@ function* testWithNoTouch(ui) {
|
||||
}
|
||||
|
||||
function* testWithTouch(ui) {
|
||||
yield injectEventUtils(ui);
|
||||
|
||||
yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
|
||||
let { EventUtils } = content;
|
||||
|
||||
let div = content.document.querySelector("div");
|
||||
let x = 0, y = 0;
|
||||
|
||||
@ -105,10 +100,8 @@ function* testWithTouch(ui) {
|
||||
function* testWithMetaViewportEnabled(ui) {
|
||||
yield SpecialPowers.pushPrefEnv({set: [[PREF_DOM_META_VIEWPORT_ENABLED, true]]});
|
||||
|
||||
yield injectEventUtils(ui);
|
||||
|
||||
yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
|
||||
let { synthesizeClick } = content.EventUtils;
|
||||
let { synthesizeClick } = EventUtils;
|
||||
|
||||
let meta = content.document.querySelector("meta[name=viewport]");
|
||||
let div = content.document.querySelector("div");
|
||||
@ -150,10 +143,8 @@ function* testWithMetaViewportEnabled(ui) {
|
||||
function* testWithMetaViewportDisabled(ui) {
|
||||
yield SpecialPowers.pushPrefEnv({set: [[PREF_DOM_META_VIEWPORT_ENABLED, false]]});
|
||||
|
||||
yield injectEventUtils(ui);
|
||||
|
||||
yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
|
||||
let { synthesizeClick } = content.EventUtils;
|
||||
let { synthesizeClick } = EventUtils;
|
||||
|
||||
let meta = content.document.querySelector("meta[name=viewport]");
|
||||
let div = content.document.querySelector("div");
|
||||
@ -191,37 +182,3 @@ function* waitBootstrap(ui) {
|
||||
yield waitUntilState(store, state => state.viewports.length == 1);
|
||||
yield waitForFrameLoad(ui, TEST_URL);
|
||||
}
|
||||
|
||||
function* injectEventUtils(ui) {
|
||||
yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
|
||||
if ("EventUtils" in content) {
|
||||
return;
|
||||
}
|
||||
|
||||
let EventUtils = content.EventUtils = {};
|
||||
|
||||
EventUtils.window = {};
|
||||
EventUtils.parent = EventUtils.window;
|
||||
/* eslint-disable camelcase */
|
||||
EventUtils._EU_Ci = Components.interfaces;
|
||||
EventUtils._EU_Cc = Components.classes;
|
||||
/* eslint-enable camelcase */
|
||||
// EventUtils' `sendChar` function relies on the navigator to synthetize events.
|
||||
EventUtils.navigator = content.navigator;
|
||||
EventUtils.KeyboardEvent = content.KeyboardEvent;
|
||||
|
||||
EventUtils.synthesizeClick = element => new Promise(resolve => {
|
||||
element.addEventListener("click", function () {
|
||||
resolve();
|
||||
}, {once: true});
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(element,
|
||||
{ type: "mousedown", isSynthesized: false }, content);
|
||||
EventUtils.synthesizeMouseAtCenter(element,
|
||||
{ type: "mouseup", isSynthesized: false }, content);
|
||||
});
|
||||
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
|
||||
});
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
var a = typeof exports === 'object' ? factory(require("devtools/client/shared/vendor/react-dom-factories"), require("devtools/client/shared/vendor/lodash"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react")) : factory(root["devtools/client/shared/vendor/react-dom-factories"], root["devtools/client/shared/vendor/lodash"], root["devtools/client/shared/vendor/react-prop-types"], root["devtools/client/shared/vendor/react"]);
|
||||
for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
|
||||
}
|
||||
})(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_53__, __WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_6__) {
|
||||
})(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_53__, __WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_7__) {
|
||||
return /******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {};
|
||||
@ -533,7 +533,7 @@ __webpack_require__(18);
|
||||
// Load all existing rep templates
|
||||
const Undefined = __webpack_require__(19);
|
||||
const Null = __webpack_require__(20);
|
||||
const StringRep = __webpack_require__(7);
|
||||
const StringRep = __webpack_require__(6);
|
||||
const LongStringRep = __webpack_require__(21);
|
||||
const Number = __webpack_require__(22);
|
||||
const ArrayRep = __webpack_require__(10);
|
||||
@ -744,12 +744,6 @@ module.exports = wrapRender(PropRep);
|
||||
|
||||
/***/ }),
|
||||
/* 6 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = __WEBPACK_EXTERNAL_MODULE_6__;
|
||||
|
||||
/***/ }),
|
||||
/* 7 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
@ -843,8 +837,8 @@ function StringRep(props) {
|
||||
*/
|
||||
function getLinkifiedElements(text, cropLimit, omitLinkHref, openLink) {
|
||||
const halfLimit = Math.ceil((cropLimit - ELLIPSIS.length) / 2);
|
||||
const startCropIndex = halfLimit;
|
||||
const endCropIndex = text.length - halfLimit;
|
||||
const startCropIndex = cropLimit ? halfLimit : null;
|
||||
const endCropIndex = cropLimit ? text.length - halfLimit : null;
|
||||
|
||||
// As we walk through the tokens of the source string, we make sure to preserve
|
||||
// the original whitespace that separated the tokens.
|
||||
@ -902,13 +896,17 @@ function getLinkifiedElements(text, cropLimit, omitLinkHref, openLink) {
|
||||
* @param {String} text: The substring to crop.
|
||||
* @param {Integer} offset: The offset corresponding to the index at which the substring
|
||||
* is in the parent string.
|
||||
* @param {Integer} startCropIndex: the index where the start of the crop should happen
|
||||
* in the parent string
|
||||
* @param {Integer} endCropIndex: the index where the end of the crop should happen
|
||||
* in the parent string
|
||||
* @param {Integer|null} startCropIndex: the index where the start of the crop should
|
||||
* happen in the parent string.
|
||||
* @param {Integer|null} endCropIndex: the index where the end of the crop should happen
|
||||
* in the parent string
|
||||
* @returns {String|null} The cropped substring, or null if the text is completly cropped.
|
||||
*/
|
||||
function getCroppedString(text, offset = 0, startCropIndex, endCropIndex) {
|
||||
if (!startCropIndex) {
|
||||
return text;
|
||||
}
|
||||
|
||||
const start = offset;
|
||||
const end = offset + text.length;
|
||||
|
||||
@ -944,6 +942,12 @@ module.exports = {
|
||||
supportsObject
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
/* 7 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = __WEBPACK_EXTERNAL_MODULE_7__;
|
||||
|
||||
/***/ }),
|
||||
/* 8 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
@ -1279,7 +1283,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const React = __webpack_require__(6);
|
||||
const React = __webpack_require__(7);
|
||||
const PropTypes = __webpack_require__(2);
|
||||
|
||||
|
||||
@ -1470,7 +1474,7 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument
|
||||
|
||||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||||
|
||||
var _react = __webpack_require__(6);
|
||||
var _react = __webpack_require__(7);
|
||||
|
||||
var _react2 = _interopRequireDefault(_react);
|
||||
|
||||
@ -1639,20 +1643,30 @@ function GripArray(props) {
|
||||
if (mode === MODE.TINY) {
|
||||
let objectLength = getLength(object);
|
||||
let isEmpty = objectLength === 0;
|
||||
if (isEmpty) {
|
||||
items = [];
|
||||
} else {
|
||||
items = [span({
|
||||
let ellipsis;
|
||||
if (!isEmpty) {
|
||||
ellipsis = span({
|
||||
className: "more-ellipsis",
|
||||
title: "more…"
|
||||
}, "…")];
|
||||
}, "…");
|
||||
}
|
||||
|
||||
let title;
|
||||
if (object.class != "Array") {
|
||||
title = object.class + " ";
|
||||
}
|
||||
brackets = needSpace(false);
|
||||
} else {
|
||||
let max = maxLengthMap.get(mode);
|
||||
items = arrayIterator(props, object, max);
|
||||
brackets = needSpace(items.length > 0);
|
||||
return span({
|
||||
"data-link-actor-id": object.actor,
|
||||
className: "objectBox objectBox-array" }, title, span({
|
||||
className: "arrayLeftBracket"
|
||||
}, brackets.left), ellipsis, span({
|
||||
className: "arrayRightBracket"
|
||||
}, brackets.right));
|
||||
}
|
||||
let max = maxLengthMap.get(mode);
|
||||
items = arrayIterator(props, object, max);
|
||||
brackets = needSpace(items.length > 0);
|
||||
|
||||
let title = getTitle(props, object);
|
||||
|
||||
@ -3416,7 +3430,7 @@ const {
|
||||
isGrip,
|
||||
wrapRender
|
||||
} = __webpack_require__(0);
|
||||
const { rep: StringRep } = __webpack_require__(7);
|
||||
const { rep: StringRep } = __webpack_require__(6);
|
||||
|
||||
/**
|
||||
* Renders DOM attribute
|
||||
@ -3723,6 +3737,8 @@ const Svg = __webpack_require__(9);
|
||||
const dom = __webpack_require__(1);
|
||||
const { span } = dom;
|
||||
|
||||
const IGNORED_SOURCE_URLS = ["debugger eval code"];
|
||||
|
||||
/**
|
||||
* This component represents a template for Function objects.
|
||||
*/
|
||||
@ -3739,7 +3755,7 @@ function FunctionRep(props) {
|
||||
} = props;
|
||||
|
||||
let jumpToDefinitionButton;
|
||||
if (onViewSourceInDebugger && grip.location && grip.location.url) {
|
||||
if (onViewSourceInDebugger && grip.location && grip.location.url && !IGNORED_SOURCE_URLS.includes(grip.location.url)) {
|
||||
jumpToDefinitionButton = Svg("jump-definition", {
|
||||
element: "a",
|
||||
draggable: false,
|
||||
@ -4232,7 +4248,7 @@ const {
|
||||
isGrip,
|
||||
wrapRender
|
||||
} = __webpack_require__(0);
|
||||
const { rep: StringRep } = __webpack_require__(7);
|
||||
const { rep: StringRep } = __webpack_require__(6);
|
||||
const { MODE } = __webpack_require__(3);
|
||||
const nodeConstants = __webpack_require__(12);
|
||||
const Svg = __webpack_require__(9);
|
||||
@ -4636,6 +4652,8 @@ const {
|
||||
wrapRender
|
||||
} = __webpack_require__(0);
|
||||
|
||||
const String = __webpack_require__(6).rep;
|
||||
|
||||
const dom = __webpack_require__(1);
|
||||
const { span } = dom;
|
||||
|
||||
@ -4650,8 +4668,8 @@ function ObjectWithText(props) {
|
||||
let grip = props.object;
|
||||
return span({
|
||||
"data-link-actor-id": grip.actor,
|
||||
className: "objectBox objectBox-" + getType(grip)
|
||||
}, span({ className: "objectPropValue" }, getDescription(grip)));
|
||||
className: "objectTitle objectBox objectBox-" + getType(grip)
|
||||
}, getType(grip), " ", getDescription(grip));
|
||||
}
|
||||
|
||||
function getType(grip) {
|
||||
@ -4659,7 +4677,9 @@ function getType(grip) {
|
||||
}
|
||||
|
||||
function getDescription(grip) {
|
||||
return "\"" + grip.preview.text + "\"";
|
||||
return String({
|
||||
object: grip.preview.text
|
||||
});
|
||||
}
|
||||
|
||||
// Registration
|
||||
@ -4760,7 +4780,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const { Component, createFactory } = __webpack_require__(6);
|
||||
const { Component, createFactory } = __webpack_require__(7);
|
||||
const PropTypes = __webpack_require__(2);
|
||||
const dom = __webpack_require__(1);
|
||||
|
||||
@ -5213,7 +5233,7 @@ Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
|
||||
var _react = __webpack_require__(6);
|
||||
var _react = __webpack_require__(7);
|
||||
|
||||
var _react2 = _interopRequireDefault(_react);
|
||||
|
||||
|
@ -25,6 +25,7 @@ support-files =
|
||||
timeline-iframe-parent.html
|
||||
storage-helpers.js
|
||||
!/devtools/server/tests/mochitest/hello-actor.js
|
||||
!/devtools/client/framework/test/shared-head.js
|
||||
|
||||
[browser_accessibility_node_events.js]
|
||||
[browser_accessibility_node.js]
|
||||
|
@ -6,86 +6,84 @@
|
||||
|
||||
// Simple CanvasFrameAnonymousContentHelper tests.
|
||||
|
||||
// This makes sure the 'domnode' protocol actor type is known when importing
|
||||
// highlighter.
|
||||
require("devtools/server/actors/inspector");
|
||||
const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
|
||||
|
||||
const {
|
||||
CanvasFrameAnonymousContentHelper
|
||||
} = require("devtools/server/actors/highlighters/utils/markup");
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test";
|
||||
|
||||
add_task(function* () {
|
||||
let browser = yield addTab(TEST_URL);
|
||||
// eslint-disable-next-line mozilla/no-cpows-in-tests
|
||||
let doc = browser.contentDocument;
|
||||
add_task(async function () {
|
||||
let browser = await addTab(TEST_URL);
|
||||
|
||||
let nodeBuilder = () => {
|
||||
let root = doc.createElement("div");
|
||||
let child = doc.createElement("div");
|
||||
child.style = "width:200px;height:200px;background:red;";
|
||||
child.id = "child-element";
|
||||
child.className = "child-element";
|
||||
child.textContent = "test element";
|
||||
root.appendChild(child);
|
||||
return root;
|
||||
};
|
||||
await ContentTask.spawn(browser, null, async function () {
|
||||
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
|
||||
const {
|
||||
CanvasFrameAnonymousContentHelper
|
||||
} = require("devtools/server/actors/highlighters/utils/markup");
|
||||
let doc = content.document;
|
||||
|
||||
info("Building the helper");
|
||||
let env = new HighlighterEnvironment();
|
||||
env.initFromWindow(doc.defaultView);
|
||||
let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
|
||||
let nodeBuilder = () => {
|
||||
let root = doc.createElement("div");
|
||||
let child = doc.createElement("div");
|
||||
child.style = "width:200px;height:200px;background:red;";
|
||||
child.id = "child-element";
|
||||
child.className = "child-element";
|
||||
child.textContent = "test element";
|
||||
root.appendChild(child);
|
||||
return root;
|
||||
};
|
||||
|
||||
ok(helper.content instanceof AnonymousContent,
|
||||
"The helper owns the AnonymousContent object");
|
||||
ok(helper.getTextContentForElement,
|
||||
"The helper has the getTextContentForElement method");
|
||||
ok(helper.setTextContentForElement,
|
||||
"The helper has the setTextContentForElement method");
|
||||
ok(helper.setAttributeForElement,
|
||||
"The helper has the setAttributeForElement method");
|
||||
ok(helper.getAttributeForElement,
|
||||
"The helper has the getAttributeForElement method");
|
||||
ok(helper.removeAttributeForElement,
|
||||
"The helper has the removeAttributeForElement method");
|
||||
ok(helper.addEventListenerForElement,
|
||||
"The helper has the addEventListenerForElement method");
|
||||
ok(helper.removeEventListenerForElement,
|
||||
"The helper has the removeEventListenerForElement method");
|
||||
ok(helper.getElement,
|
||||
"The helper has the getElement method");
|
||||
ok(helper.scaleRootElement,
|
||||
"The helper has the scaleRootElement method");
|
||||
info("Building the helper");
|
||||
let env = new HighlighterEnvironment();
|
||||
env.initFromWindow(doc.defaultView);
|
||||
let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
|
||||
|
||||
is(helper.getTextContentForElement("child-element"), "test element",
|
||||
"The text content was retrieve correctly");
|
||||
is(helper.getAttributeForElement("child-element", "id"), "child-element",
|
||||
"The ID attribute was retrieve correctly");
|
||||
is(helper.getAttributeForElement("child-element", "class"), "child-element",
|
||||
"The class attribute was retrieve correctly");
|
||||
ok(helper.content instanceof content.AnonymousContent,
|
||||
"The helper owns the AnonymousContent object");
|
||||
ok(helper.getTextContentForElement,
|
||||
"The helper has the getTextContentForElement method");
|
||||
ok(helper.setTextContentForElement,
|
||||
"The helper has the setTextContentForElement method");
|
||||
ok(helper.setAttributeForElement,
|
||||
"The helper has the setAttributeForElement method");
|
||||
ok(helper.getAttributeForElement,
|
||||
"The helper has the getAttributeForElement method");
|
||||
ok(helper.removeAttributeForElement,
|
||||
"The helper has the removeAttributeForElement method");
|
||||
ok(helper.addEventListenerForElement,
|
||||
"The helper has the addEventListenerForElement method");
|
||||
ok(helper.removeEventListenerForElement,
|
||||
"The helper has the removeEventListenerForElement method");
|
||||
ok(helper.getElement,
|
||||
"The helper has the getElement method");
|
||||
ok(helper.scaleRootElement,
|
||||
"The helper has the scaleRootElement method");
|
||||
|
||||
let el = helper.getElement("child-element");
|
||||
ok(el, "The DOMNode-like element was created");
|
||||
is(helper.getTextContentForElement("child-element"), "test element",
|
||||
"The text content was retrieve correctly");
|
||||
is(helper.getAttributeForElement("child-element", "id"), "child-element",
|
||||
"The ID attribute was retrieve correctly");
|
||||
is(helper.getAttributeForElement("child-element", "class"), "child-element",
|
||||
"The class attribute was retrieve correctly");
|
||||
|
||||
is(el.getTextContent(), "test element",
|
||||
"The text content was retrieve correctly");
|
||||
is(el.getAttribute("id"), "child-element",
|
||||
"The ID attribute was retrieve correctly");
|
||||
is(el.getAttribute("class"), "child-element",
|
||||
"The class attribute was retrieve correctly");
|
||||
let el = helper.getElement("child-element");
|
||||
ok(el, "The DOMNode-like element was created");
|
||||
|
||||
info("Destroying the helper");
|
||||
helper.destroy();
|
||||
env.destroy();
|
||||
is(el.getTextContent(), "test element",
|
||||
"The text content was retrieve correctly");
|
||||
is(el.getAttribute("id"), "child-element",
|
||||
"The ID attribute was retrieve correctly");
|
||||
is(el.getAttribute("class"), "child-element",
|
||||
"The class attribute was retrieve correctly");
|
||||
|
||||
ok(!helper.getTextContentForElement("child-element"),
|
||||
"No text content was retrieved after the helper was destroyed");
|
||||
ok(!helper.getAttributeForElement("child-element", "id"),
|
||||
"No ID attribute was retrieved after the helper was destroyed");
|
||||
ok(!helper.getAttributeForElement("child-element", "class"),
|
||||
"No class attribute was retrieved after the helper was destroyed");
|
||||
info("Destroying the helper");
|
||||
helper.destroy();
|
||||
env.destroy();
|
||||
|
||||
ok(!helper.getTextContentForElement("child-element"),
|
||||
"No text content was retrieved after the helper was destroyed");
|
||||
ok(!helper.getAttributeForElement("child-element", "id"),
|
||||
"No ID attribute was retrieved after the helper was destroyed");
|
||||
ok(!helper.getAttributeForElement("child-element", "class"),
|
||||
"No class attribute was retrieved after the helper was destroyed");
|
||||
});
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
@ -7,43 +7,40 @@
|
||||
// Test that the CanvasFrameAnonymousContentHelper does not insert content in
|
||||
// XUL windows.
|
||||
|
||||
// This makes sure the 'domnode' protocol actor type is known when importing
|
||||
// highlighter.
|
||||
require("devtools/server/actors/inspector");
|
||||
add_task(async function () {
|
||||
let browser = await addTab("about:preferences");
|
||||
|
||||
const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
|
||||
await ContentTask.spawn(browser, null, async function () {
|
||||
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
|
||||
const {
|
||||
CanvasFrameAnonymousContentHelper
|
||||
} = require("devtools/server/actors/highlighters/utils/markup");
|
||||
let doc = content.document;
|
||||
|
||||
const {
|
||||
CanvasFrameAnonymousContentHelper
|
||||
} = require("devtools/server/actors/highlighters/utils/markup");
|
||||
let nodeBuilder = () => {
|
||||
let root = doc.createElement("div");
|
||||
let child = doc.createElement("div");
|
||||
child.style = "width:200px;height:200px;background:red;";
|
||||
child.id = "child-element";
|
||||
child.className = "child-element";
|
||||
child.textContent = "test element";
|
||||
root.appendChild(child);
|
||||
return root;
|
||||
};
|
||||
|
||||
add_task(function* () {
|
||||
let browser = yield addTab("about:preferences");
|
||||
// eslint-disable-next-line mozilla/no-cpows-in-tests
|
||||
let doc = browser.contentDocument;
|
||||
info("Building the helper");
|
||||
let env = new HighlighterEnvironment();
|
||||
env.initFromWindow(doc.defaultView);
|
||||
let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
|
||||
|
||||
let nodeBuilder = () => {
|
||||
let root = doc.createElement("div");
|
||||
let child = doc.createElement("div");
|
||||
child.style = "width:200px;height:200px;background:red;";
|
||||
child.id = "child-element";
|
||||
child.className = "child-element";
|
||||
child.textContent = "test element";
|
||||
root.appendChild(child);
|
||||
return root;
|
||||
};
|
||||
ok(!helper.content, "The AnonymousContent was not inserted in the window");
|
||||
ok(!helper.getTextContentForElement("child-element"),
|
||||
"No text content is returned");
|
||||
|
||||
info("Building the helper");
|
||||
let env = new HighlighterEnvironment();
|
||||
env.initFromWindow(doc.defaultView);
|
||||
let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
|
||||
|
||||
ok(!helper.content, "The AnonymousContent was not inserted in the window");
|
||||
ok(!helper.getTextContentForElement("child-element"),
|
||||
"No text content is returned");
|
||||
|
||||
env.destroy();
|
||||
helper.destroy();
|
||||
env.destroy();
|
||||
helper.destroy();
|
||||
});
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
@ -6,98 +6,108 @@
|
||||
|
||||
// Test the CanvasFrameAnonymousContentHelper event handling mechanism.
|
||||
|
||||
// This makes sure the 'domnode' protocol actor type is known when importing
|
||||
// highlighter.
|
||||
require("devtools/server/actors/inspector");
|
||||
|
||||
const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
|
||||
|
||||
const {
|
||||
CanvasFrameAnonymousContentHelper
|
||||
} = require("devtools/server/actors/highlighters/utils/markup");
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test";
|
||||
|
||||
add_task(function* () {
|
||||
let browser = yield addTab(TEST_URL);
|
||||
// eslint-disable-next-line mozilla/no-cpows-in-tests
|
||||
let doc = browser.contentDocument;
|
||||
add_task(async function () {
|
||||
let browser = await addTab(TEST_URL);
|
||||
await ContentTask.spawn(browser, null, async function () {
|
||||
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
|
||||
const {
|
||||
CanvasFrameAnonymousContentHelper
|
||||
} = require("devtools/server/actors/highlighters/utils/markup");
|
||||
let doc = content.document;
|
||||
|
||||
let nodeBuilder = () => {
|
||||
let root = doc.createElement("div");
|
||||
let child = doc.createElement("div");
|
||||
child.style = "pointer-events:auto;width:200px;height:200px;background:red;";
|
||||
child.id = "child-element";
|
||||
child.className = "child-element";
|
||||
root.appendChild(child);
|
||||
return root;
|
||||
};
|
||||
let nodeBuilder = () => {
|
||||
let root = doc.createElement("div");
|
||||
let child = doc.createElement("div");
|
||||
child.style = "pointer-events:auto;width:200px;height:200px;background:red;";
|
||||
child.id = "child-element";
|
||||
child.className = "child-element";
|
||||
root.appendChild(child);
|
||||
return root;
|
||||
};
|
||||
|
||||
info("Building the helper");
|
||||
let env = new HighlighterEnvironment();
|
||||
env.initFromWindow(doc.defaultView);
|
||||
let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
|
||||
info("Building the helper");
|
||||
let env = new HighlighterEnvironment();
|
||||
env.initFromWindow(doc.defaultView);
|
||||
let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
|
||||
|
||||
let el = helper.getElement("child-element");
|
||||
let el = helper.getElement("child-element");
|
||||
|
||||
info("Adding an event listener on the inserted element");
|
||||
let mouseDownHandled = 0;
|
||||
function onMouseDown(e, id) {
|
||||
is(id, "child-element", "The mousedown event was triggered on the element");
|
||||
ok(!e.originalTarget, "The originalTarget property isn't available");
|
||||
mouseDownHandled++;
|
||||
}
|
||||
el.addEventListener("mousedown", onMouseDown);
|
||||
info("Adding an event listener on the inserted element");
|
||||
let mouseDownHandled = 0;
|
||||
function onMouseDown(e, id) {
|
||||
is(id, "child-element", "The mousedown event was triggered on the element");
|
||||
ok(!e.originalTarget, "The originalTarget property isn't available");
|
||||
mouseDownHandled++;
|
||||
}
|
||||
el.addEventListener("mousedown", onMouseDown);
|
||||
|
||||
info("Synthesizing an event on the inserted element");
|
||||
let onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
yield onDocMouseDown;
|
||||
function once(target, event) {
|
||||
return new Promise(done => {
|
||||
target.addEventListener(event, done, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
is(mouseDownHandled, 1, "The mousedown event was handled once on the element");
|
||||
info("Synthesizing an event on the inserted element");
|
||||
let onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
await onDocMouseDown;
|
||||
|
||||
info("Synthesizing an event somewhere else");
|
||||
onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(400, 400, doc.defaultView);
|
||||
yield onDocMouseDown;
|
||||
is(mouseDownHandled, 1, "The mousedown event was handled once on the element");
|
||||
|
||||
is(mouseDownHandled, 1, "The mousedown event was not handled on the element");
|
||||
info("Synthesizing an event somewhere else");
|
||||
onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(400, 400, doc.defaultView);
|
||||
await onDocMouseDown;
|
||||
|
||||
info("Removing the event listener");
|
||||
el.removeEventListener("mousedown", onMouseDown);
|
||||
is(mouseDownHandled, 1, "The mousedown event was not handled on the element");
|
||||
|
||||
info("Synthesizing another event after the listener has been removed");
|
||||
// Using a document event listener to know when the event has been synthesized.
|
||||
onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
yield onDocMouseDown;
|
||||
info("Removing the event listener");
|
||||
el.removeEventListener("mousedown", onMouseDown);
|
||||
|
||||
is(mouseDownHandled, 1,
|
||||
"The mousedown event hasn't been handled after the listener was removed");
|
||||
info("Synthesizing another event after the listener has been removed");
|
||||
// Using a document event listener to know when the event has been synthesized.
|
||||
onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
await onDocMouseDown;
|
||||
|
||||
info("Adding again the event listener");
|
||||
el.addEventListener("mousedown", onMouseDown);
|
||||
is(mouseDownHandled, 1,
|
||||
"The mousedown event hasn't been handled after the listener was removed");
|
||||
|
||||
info("Destroying the helper");
|
||||
env.destroy();
|
||||
helper.destroy();
|
||||
info("Adding again the event listener");
|
||||
el.addEventListener("mousedown", onMouseDown);
|
||||
|
||||
info("Synthesizing another event after the helper has been destroyed");
|
||||
// Using a document event listener to know when the event has been synthesized.
|
||||
onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
yield onDocMouseDown;
|
||||
info("Destroying the helper");
|
||||
env.destroy();
|
||||
helper.destroy();
|
||||
|
||||
is(mouseDownHandled, 1,
|
||||
"The mousedown event hasn't been handled after the helper was destroyed");
|
||||
info("Synthesizing another event after the helper has been destroyed");
|
||||
// Using a document event listener to know when the event has been synthesized.
|
||||
onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
await onDocMouseDown;
|
||||
|
||||
is(mouseDownHandled, 1,
|
||||
"The mousedown event hasn't been handled after the helper was destroyed");
|
||||
|
||||
function synthesizeMouseDown(x, y, win) {
|
||||
// We need to make sure the inserted anonymous content can be targeted by the
|
||||
// event right after having been inserted, and so we need to force a sync
|
||||
// reflow.
|
||||
win.document.documentElement.offsetWidth;
|
||||
// Minimal environment for EventUtils to work.
|
||||
let EventUtils = {
|
||||
window: content,
|
||||
parent: content,
|
||||
_EU_Ci: Components.interfaces,
|
||||
_EU_Cc: Components.classes,
|
||||
};
|
||||
Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
|
||||
EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win);
|
||||
}
|
||||
});
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function synthesizeMouseDown(x, y, win) {
|
||||
// We need to make sure the inserted anonymous content can be targeted by the
|
||||
// event right after having been inserted, and so we need to force a sync
|
||||
// reflow.
|
||||
win.document.documentElement.offsetWidth;
|
||||
EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win);
|
||||
}
|
||||
|
@ -7,95 +7,99 @@
|
||||
// Test the CanvasFrameAnonymousContentHelper re-inserts the content when the
|
||||
// page reloads.
|
||||
|
||||
// This makes sure the 'domnode' protocol actor type is known when importing
|
||||
// highlighter.
|
||||
require("devtools/server/actors/inspector");
|
||||
|
||||
const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
|
||||
|
||||
const {
|
||||
CanvasFrameAnonymousContentHelper
|
||||
} = require("devtools/server/actors/highlighters/utils/markup");
|
||||
|
||||
const TEST_URL_1 =
|
||||
"data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test 1";
|
||||
const TEST_URL_2 =
|
||||
"data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test 2";
|
||||
|
||||
add_task(function* () {
|
||||
let browser = yield addTab(TEST_URL_1);
|
||||
// eslint-disable-next-line mozilla/no-cpows-in-tests
|
||||
let doc = browser.contentDocument;
|
||||
add_task(async function () {
|
||||
let browser = await addTab(TEST_URL_1);
|
||||
await injectEventUtilsInContentTask(browser);
|
||||
await ContentTask.spawn(browser, TEST_URL_2, async function (url2) {
|
||||
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
|
||||
const {
|
||||
CanvasFrameAnonymousContentHelper
|
||||
} = require("devtools/server/actors/highlighters/utils/markup");
|
||||
let doc = content.document;
|
||||
|
||||
let nodeBuilder = () => {
|
||||
let root = doc.createElement("div");
|
||||
let child = doc.createElement("div");
|
||||
child.style = "pointer-events:auto;width:200px;height:200px;background:red;";
|
||||
child.id = "child-element";
|
||||
child.className = "child-element";
|
||||
child.textContent = "test content";
|
||||
root.appendChild(child);
|
||||
return root;
|
||||
};
|
||||
let nodeBuilder = () => {
|
||||
let root = doc.createElement("div");
|
||||
let child = doc.createElement("div");
|
||||
child.style = "pointer-events:auto;width:200px;height:200px;background:red;";
|
||||
child.id = "child-element";
|
||||
child.className = "child-element";
|
||||
child.textContent = "test content";
|
||||
root.appendChild(child);
|
||||
return root;
|
||||
};
|
||||
|
||||
info("Building the helper");
|
||||
let env = new HighlighterEnvironment();
|
||||
env.initFromWindow(doc.defaultView);
|
||||
let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
|
||||
info("Building the helper");
|
||||
let env = new HighlighterEnvironment();
|
||||
env.initFromWindow(doc.defaultView);
|
||||
let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
|
||||
|
||||
info("Get an element from the helper");
|
||||
let el = helper.getElement("child-element");
|
||||
info("Get an element from the helper");
|
||||
let el = helper.getElement("child-element");
|
||||
|
||||
info("Try to access the element");
|
||||
is(el.getAttribute("class"), "child-element",
|
||||
"The attribute is correct before navigation");
|
||||
is(el.getTextContent(), "test content",
|
||||
"The text content is correct before navigation");
|
||||
info("Try to access the element");
|
||||
is(el.getAttribute("class"), "child-element",
|
||||
"The attribute is correct before navigation");
|
||||
is(el.getTextContent(), "test content",
|
||||
"The text content is correct before navigation");
|
||||
|
||||
info("Add an event listener on the element");
|
||||
let mouseDownHandled = 0;
|
||||
function onMouseDown(e, id) {
|
||||
is(id, "child-element", "The mousedown event was triggered on the element");
|
||||
mouseDownHandled++;
|
||||
}
|
||||
el.addEventListener("mousedown", onMouseDown);
|
||||
info("Add an event listener on the element");
|
||||
let mouseDownHandled = 0;
|
||||
let onMouseDown = (e, id) => {
|
||||
is(id, "child-element", "The mousedown event was triggered on the element");
|
||||
mouseDownHandled++;
|
||||
};
|
||||
el.addEventListener("mousedown", onMouseDown);
|
||||
|
||||
info("Synthesizing an event on the element");
|
||||
let onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
yield onDocMouseDown;
|
||||
is(mouseDownHandled, 1, "The mousedown event was handled once before navigation");
|
||||
let once = function once(target, event) {
|
||||
return new Promise(done => {
|
||||
target.addEventListener(event, done, { once: true });
|
||||
});
|
||||
};
|
||||
|
||||
info("Navigating to a new page");
|
||||
let loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
|
||||
BrowserTestUtils.loadURI(browser, TEST_URL_2);
|
||||
yield loaded;
|
||||
// eslint-disable-next-line mozilla/no-cpows-in-tests
|
||||
doc = gBrowser.selectedBrowser.contentWindow.document;
|
||||
let synthesizeMouseDown = function synthesizeMouseDown(x, y, win) {
|
||||
// We need to make sure the inserted anonymous content can be targeted by the
|
||||
// event right after having been inserted, and so we need to force a sync
|
||||
// reflow.
|
||||
win.document.documentElement.offsetWidth;
|
||||
EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win);
|
||||
};
|
||||
|
||||
info("Try to access the element again");
|
||||
is(el.getAttribute("class"), "child-element",
|
||||
"The attribute is correct after navigation");
|
||||
is(el.getTextContent(), "test content",
|
||||
"The text content is correct after navigation");
|
||||
info("Synthesizing an event on the element");
|
||||
let onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
await onDocMouseDown;
|
||||
is(mouseDownHandled, 1, "The mousedown event was handled once before navigation");
|
||||
|
||||
info("Synthesizing an event on the element again");
|
||||
onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
yield onDocMouseDown;
|
||||
is(mouseDownHandled, 1, "The mousedown event was not handled after navigation");
|
||||
info("Navigating to a new page");
|
||||
let loaded = once(this, "load");
|
||||
content.location = url2;
|
||||
await loaded;
|
||||
|
||||
info("Destroying the helper");
|
||||
env.destroy();
|
||||
helper.destroy();
|
||||
// Update to the new document we just loaded
|
||||
doc = content.document;
|
||||
|
||||
info("Try to access the element again");
|
||||
is(el.getAttribute("class"), "child-element",
|
||||
"The attribute is correct after navigation");
|
||||
is(el.getTextContent(), "test content",
|
||||
"The text content is correct after navigation");
|
||||
|
||||
info("Synthesizing an event on the element again");
|
||||
onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
await onDocMouseDown;
|
||||
is(mouseDownHandled, 1, "The mousedown event was not handled after navigation");
|
||||
|
||||
info("Destroying the helper");
|
||||
env.destroy();
|
||||
helper.destroy();
|
||||
});
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function synthesizeMouseDown(x, y, win) {
|
||||
// We need to make sure the inserted anonymous content can be targeted by the
|
||||
// event right after having been inserted, and so we need to force a sync
|
||||
// reflow.
|
||||
win.document.documentElement.offsetWidth;
|
||||
EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win);
|
||||
}
|
||||
|
@ -7,107 +7,117 @@
|
||||
// Test some edge cases of the CanvasFrameAnonymousContentHelper event handling
|
||||
// mechanism.
|
||||
|
||||
// This makes sure the 'domnode' protocol actor type is known when importing
|
||||
// highlighter.
|
||||
require("devtools/server/actors/inspector");
|
||||
|
||||
const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
|
||||
|
||||
const {
|
||||
CanvasFrameAnonymousContentHelper
|
||||
} = require("devtools/server/actors/highlighters/utils/markup");
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test";
|
||||
|
||||
add_task(function* () {
|
||||
let browser = yield addTab(TEST_URL);
|
||||
// eslint-disable-next-line mozilla/no-cpows-in-tests
|
||||
let doc = browser.contentDocument;
|
||||
add_task(async function () {
|
||||
let browser = await addTab(TEST_URL);
|
||||
await ContentTask.spawn(browser, null, async function () {
|
||||
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
|
||||
const {
|
||||
CanvasFrameAnonymousContentHelper
|
||||
} = require("devtools/server/actors/highlighters/utils/markup");
|
||||
let doc = content.document;
|
||||
|
||||
let nodeBuilder = () => {
|
||||
let root = doc.createElement("div");
|
||||
let nodeBuilder = () => {
|
||||
let root = doc.createElement("div");
|
||||
|
||||
let parent = doc.createElement("div");
|
||||
parent.style = "pointer-events:auto;width:300px;height:300px;background:yellow;";
|
||||
parent.id = "parent-element";
|
||||
root.appendChild(parent);
|
||||
let parent = doc.createElement("div");
|
||||
parent.style = "pointer-events:auto;width:300px;height:300px;background:yellow;";
|
||||
parent.id = "parent-element";
|
||||
root.appendChild(parent);
|
||||
|
||||
let child = doc.createElement("div");
|
||||
child.style = "pointer-events:auto;width:200px;height:200px;background:red;";
|
||||
child.id = "child-element";
|
||||
parent.appendChild(child);
|
||||
let child = doc.createElement("div");
|
||||
child.style = "pointer-events:auto;width:200px;height:200px;background:red;";
|
||||
child.id = "child-element";
|
||||
parent.appendChild(child);
|
||||
|
||||
return root;
|
||||
};
|
||||
return root;
|
||||
};
|
||||
|
||||
info("Building the helper");
|
||||
let env = new HighlighterEnvironment();
|
||||
env.initFromWindow(doc.defaultView);
|
||||
let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
|
||||
info("Building the helper");
|
||||
let env = new HighlighterEnvironment();
|
||||
env.initFromWindow(doc.defaultView);
|
||||
let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
|
||||
|
||||
info("Getting the parent and child elements");
|
||||
let parentEl = helper.getElement("parent-element");
|
||||
let childEl = helper.getElement("child-element");
|
||||
info("Getting the parent and child elements");
|
||||
let parentEl = helper.getElement("parent-element");
|
||||
let childEl = helper.getElement("child-element");
|
||||
|
||||
info("Adding an event listener on both elements");
|
||||
let mouseDownHandled = [];
|
||||
function onMouseDown(e, id) {
|
||||
mouseDownHandled.push(id);
|
||||
}
|
||||
parentEl.addEventListener("mousedown", onMouseDown);
|
||||
childEl.addEventListener("mousedown", onMouseDown);
|
||||
info("Adding an event listener on both elements");
|
||||
let mouseDownHandled = [];
|
||||
function onMouseDown(e, id) {
|
||||
mouseDownHandled.push(id);
|
||||
}
|
||||
parentEl.addEventListener("mousedown", onMouseDown);
|
||||
childEl.addEventListener("mousedown", onMouseDown);
|
||||
|
||||
info("Synthesizing an event on the child element");
|
||||
let onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
yield onDocMouseDown;
|
||||
function once(target, event) {
|
||||
return new Promise(done => {
|
||||
target.addEventListener(event, done, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
is(mouseDownHandled.length, 2, "The mousedown event was handled twice");
|
||||
is(mouseDownHandled[0], "child-element",
|
||||
"The mousedown event was handled on the child element");
|
||||
is(mouseDownHandled[1], "parent-element",
|
||||
"The mousedown event was handled on the parent element");
|
||||
info("Synthesizing an event on the child element");
|
||||
let onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
await onDocMouseDown;
|
||||
|
||||
info("Synthesizing an event on the parent, outside of the child element");
|
||||
mouseDownHandled = [];
|
||||
onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(250, 250, doc.defaultView);
|
||||
yield onDocMouseDown;
|
||||
is(mouseDownHandled.length, 2, "The mousedown event was handled twice");
|
||||
is(mouseDownHandled[0], "child-element",
|
||||
"The mousedown event was handled on the child element");
|
||||
is(mouseDownHandled[1], "parent-element",
|
||||
"The mousedown event was handled on the parent element");
|
||||
|
||||
is(mouseDownHandled.length, 1, "The mousedown event was handled only once");
|
||||
is(mouseDownHandled[0], "parent-element",
|
||||
"The mousedown event was handled on the parent element");
|
||||
info("Synthesizing an event on the parent, outside of the child element");
|
||||
mouseDownHandled = [];
|
||||
onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(250, 250, doc.defaultView);
|
||||
await onDocMouseDown;
|
||||
|
||||
info("Removing the event listener");
|
||||
parentEl.removeEventListener("mousedown", onMouseDown);
|
||||
childEl.removeEventListener("mousedown", onMouseDown);
|
||||
is(mouseDownHandled.length, 1, "The mousedown event was handled only once");
|
||||
is(mouseDownHandled[0], "parent-element",
|
||||
"The mousedown event was handled on the parent element");
|
||||
|
||||
info("Adding an event listener on the parent element only");
|
||||
mouseDownHandled = [];
|
||||
parentEl.addEventListener("mousedown", onMouseDown);
|
||||
info("Removing the event listener");
|
||||
parentEl.removeEventListener("mousedown", onMouseDown);
|
||||
childEl.removeEventListener("mousedown", onMouseDown);
|
||||
|
||||
info("Synthesizing an event on the child element");
|
||||
onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
yield onDocMouseDown;
|
||||
info("Adding an event listener on the parent element only");
|
||||
mouseDownHandled = [];
|
||||
parentEl.addEventListener("mousedown", onMouseDown);
|
||||
|
||||
is(mouseDownHandled.length, 1, "The mousedown event was handled once");
|
||||
is(mouseDownHandled[0], "parent-element",
|
||||
"The mousedown event did bubble to the parent element");
|
||||
info("Synthesizing an event on the child element");
|
||||
onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
await onDocMouseDown;
|
||||
|
||||
info("Removing the parent listener");
|
||||
parentEl.removeEventListener("mousedown", onMouseDown);
|
||||
is(mouseDownHandled.length, 1, "The mousedown event was handled once");
|
||||
is(mouseDownHandled[0], "parent-element",
|
||||
"The mousedown event did bubble to the parent element");
|
||||
|
||||
env.destroy();
|
||||
helper.destroy();
|
||||
info("Removing the parent listener");
|
||||
parentEl.removeEventListener("mousedown", onMouseDown);
|
||||
|
||||
env.destroy();
|
||||
helper.destroy();
|
||||
|
||||
function synthesizeMouseDown(x, y, win) {
|
||||
// We need to make sure the inserted anonymous content can be targeted by the
|
||||
// event right after having been inserted, and so we need to force a sync
|
||||
// reflow.
|
||||
win.document.documentElement.offsetWidth;
|
||||
// Minimal environment for EventUtils to work.
|
||||
let EventUtils = {
|
||||
window: content,
|
||||
parent: content,
|
||||
_EU_Ci: Components.interfaces,
|
||||
_EU_Cc: Components.classes,
|
||||
};
|
||||
Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
|
||||
EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win);
|
||||
}
|
||||
});
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function synthesizeMouseDown(x, y, win) {
|
||||
// We need to make sure the inserted anonymous content can be targeted by the
|
||||
// event right after having been inserted, and so we need to force a sync
|
||||
// reflow.
|
||||
win.document.documentElement.offsetWidth;
|
||||
EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win);
|
||||
}
|
||||
|
@ -7,95 +7,105 @@
|
||||
// Test support for event propagation stop in the
|
||||
// CanvasFrameAnonymousContentHelper event handling mechanism.
|
||||
|
||||
// This makes sure the 'domnode' protocol actor type is known when importing
|
||||
// highlighter.
|
||||
require("devtools/server/actors/inspector");
|
||||
|
||||
const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
|
||||
|
||||
const {
|
||||
CanvasFrameAnonymousContentHelper
|
||||
} = require("devtools/server/actors/highlighters/utils/markup");
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test";
|
||||
|
||||
add_task(function* () {
|
||||
let browser = yield addTab(TEST_URL);
|
||||
// eslint-disable-next-line mozilla/no-cpows-in-tests
|
||||
let doc = browser.contentDocument;
|
||||
add_task(async function () {
|
||||
let browser = await addTab(TEST_URL);
|
||||
await ContentTask.spawn(browser, null, async function () {
|
||||
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
|
||||
const {
|
||||
CanvasFrameAnonymousContentHelper
|
||||
} = require("devtools/server/actors/highlighters/utils/markup");
|
||||
let doc = content.document;
|
||||
|
||||
let nodeBuilder = () => {
|
||||
let root = doc.createElement("div");
|
||||
let nodeBuilder = () => {
|
||||
let root = doc.createElement("div");
|
||||
|
||||
let parent = doc.createElement("div");
|
||||
parent.style = "pointer-events:auto;width:300px;height:300px;background:yellow;";
|
||||
parent.id = "parent-element";
|
||||
root.appendChild(parent);
|
||||
let parent = doc.createElement("div");
|
||||
parent.style = "pointer-events:auto;width:300px;height:300px;background:yellow;";
|
||||
parent.id = "parent-element";
|
||||
root.appendChild(parent);
|
||||
|
||||
let child = doc.createElement("div");
|
||||
child.style = "pointer-events:auto;width:200px;height:200px;background:red;";
|
||||
child.id = "child-element";
|
||||
parent.appendChild(child);
|
||||
let child = doc.createElement("div");
|
||||
child.style = "pointer-events:auto;width:200px;height:200px;background:red;";
|
||||
child.id = "child-element";
|
||||
parent.appendChild(child);
|
||||
|
||||
return root;
|
||||
};
|
||||
return root;
|
||||
};
|
||||
|
||||
info("Building the helper");
|
||||
let env = new HighlighterEnvironment();
|
||||
env.initFromWindow(doc.defaultView);
|
||||
let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
|
||||
info("Building the helper");
|
||||
let env = new HighlighterEnvironment();
|
||||
env.initFromWindow(doc.defaultView);
|
||||
let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
|
||||
|
||||
info("Getting the parent and child elements");
|
||||
let parentEl = helper.getElement("parent-element");
|
||||
let childEl = helper.getElement("child-element");
|
||||
info("Getting the parent and child elements");
|
||||
let parentEl = helper.getElement("parent-element");
|
||||
let childEl = helper.getElement("child-element");
|
||||
|
||||
info("Adding an event listener on both elements");
|
||||
let mouseDownHandled = [];
|
||||
info("Adding an event listener on both elements");
|
||||
let mouseDownHandled = [];
|
||||
|
||||
function onParentMouseDown(e, id) {
|
||||
mouseDownHandled.push(id);
|
||||
}
|
||||
parentEl.addEventListener("mousedown", onParentMouseDown);
|
||||
function onParentMouseDown(e, id) {
|
||||
mouseDownHandled.push(id);
|
||||
}
|
||||
parentEl.addEventListener("mousedown", onParentMouseDown);
|
||||
|
||||
function onChildMouseDown(e, id) {
|
||||
mouseDownHandled.push(id);
|
||||
e.stopPropagation();
|
||||
}
|
||||
childEl.addEventListener("mousedown", onChildMouseDown);
|
||||
function onChildMouseDown(e, id) {
|
||||
mouseDownHandled.push(id);
|
||||
e.stopPropagation();
|
||||
}
|
||||
childEl.addEventListener("mousedown", onChildMouseDown);
|
||||
|
||||
info("Synthesizing an event on the child element");
|
||||
let onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
yield onDocMouseDown;
|
||||
function once(target, event) {
|
||||
return new Promise(done => {
|
||||
target.addEventListener(event, done, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
is(mouseDownHandled.length, 1, "The mousedown event was handled only once");
|
||||
is(mouseDownHandled[0], "child-element",
|
||||
"The mousedown event was handled on the child element");
|
||||
info("Synthesizing an event on the child element");
|
||||
let onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(100, 100, doc.defaultView);
|
||||
await onDocMouseDown;
|
||||
|
||||
info("Synthesizing an event on the parent, outside of the child element");
|
||||
mouseDownHandled = [];
|
||||
onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(250, 250, doc.defaultView);
|
||||
yield onDocMouseDown;
|
||||
is(mouseDownHandled.length, 1, "The mousedown event was handled only once");
|
||||
is(mouseDownHandled[0], "child-element",
|
||||
"The mousedown event was handled on the child element");
|
||||
|
||||
is(mouseDownHandled.length, 1, "The mousedown event was handled only once");
|
||||
is(mouseDownHandled[0], "parent-element",
|
||||
"The mousedown event was handled on the parent element");
|
||||
info("Synthesizing an event on the parent, outside of the child element");
|
||||
mouseDownHandled = [];
|
||||
onDocMouseDown = once(doc, "mousedown");
|
||||
synthesizeMouseDown(250, 250, doc.defaultView);
|
||||
await onDocMouseDown;
|
||||
|
||||
info("Removing the event listener");
|
||||
parentEl.removeEventListener("mousedown", onParentMouseDown);
|
||||
childEl.removeEventListener("mousedown", onChildMouseDown);
|
||||
is(mouseDownHandled.length, 1, "The mousedown event was handled only once");
|
||||
is(mouseDownHandled[0], "parent-element",
|
||||
"The mousedown event was handled on the parent element");
|
||||
|
||||
env.destroy();
|
||||
helper.destroy();
|
||||
info("Removing the event listener");
|
||||
parentEl.removeEventListener("mousedown", onParentMouseDown);
|
||||
childEl.removeEventListener("mousedown", onChildMouseDown);
|
||||
|
||||
env.destroy();
|
||||
helper.destroy();
|
||||
|
||||
function synthesizeMouseDown(x, y, win) {
|
||||
// We need to make sure the inserted anonymous content can be targeted by the
|
||||
// event right after having been inserted, and so we need to force a sync
|
||||
// reflow.
|
||||
win.document.documentElement.offsetWidth;
|
||||
// Minimal environment for EventUtils to work.
|
||||
let EventUtils = {
|
||||
window: content,
|
||||
parent: content,
|
||||
_EU_Ci: Components.interfaces,
|
||||
_EU_Cc: Components.classes,
|
||||
};
|
||||
Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
|
||||
EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win);
|
||||
}
|
||||
});
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function synthesizeMouseDown(x, y, win) {
|
||||
// We need to make sure the inserted anonymous content can be targeted by the
|
||||
// event right after having been inserted, and so we need to force a sync
|
||||
// reflow.
|
||||
win.document.documentElement.offsetWidth;
|
||||
EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win);
|
||||
}
|
||||
|
@ -5,18 +5,14 @@
|
||||
"use strict";
|
||||
|
||||
/* eslint no-unused-vars: [2, {"vars": "local"}] */
|
||||
/* import-globals-from ../../../client/framework/test/shared-head.js */
|
||||
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
var Cu = Components.utils;
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
|
||||
this);
|
||||
|
||||
const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
|
||||
const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
const {DebuggerClient} = require("devtools/shared/client/debugger-client");
|
||||
const {DebuggerServer} = require("devtools/server/main");
|
||||
const {defer} = require("promise");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const Services = require("Services");
|
||||
|
||||
const PATH = "browser/devtools/server/tests/browser/";
|
||||
const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
|
||||
|
@ -2337,6 +2337,14 @@ nsDOMWindowUtils::GetUsingAdvancedLayers(bool* retval)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMWindowUtils::GetIsWebRenderRequested(bool* retval)
|
||||
{
|
||||
*retval = gfxPlatform::WebRenderPrefEnabled() ||
|
||||
gfxPlatform::WebRenderEnvvarEnabled();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMWindowUtils::GetCurrentAudioBackend(nsAString& aBackend)
|
||||
{
|
||||
|
@ -1331,6 +1331,13 @@ interface nsIDOMWindowUtils : nsISupports {
|
||||
*/
|
||||
readonly attribute boolean usingAdvancedLayers;
|
||||
|
||||
/**
|
||||
* True if webrender was requested by the user (via pref or env-var), false
|
||||
* otherwise. Note that this doesn't represent whether or not webrender is
|
||||
* *actually* enabled, just whether or not it was requested.
|
||||
*/
|
||||
readonly attribute boolean isWebRenderRequested;
|
||||
|
||||
/**
|
||||
* Returns the current audio backend as a free-form string.
|
||||
*/
|
||||
|
@ -1808,7 +1808,8 @@ ContentChild::RecvPBrowserConstructor(PBrowserChild* aActor,
|
||||
if (!hasRunOnce) {
|
||||
hasRunOnce = true;
|
||||
MOZ_ASSERT(!gFirstIdleTask);
|
||||
RefPtr<CancelableRunnable> firstIdleTask = NewCancelableRunnableFunction(FirstIdle);
|
||||
RefPtr<CancelableRunnable> firstIdleTask = NewCancelableRunnableFunction("FirstIdleRunnable",
|
||||
FirstIdle);
|
||||
gFirstIdleTask = firstIdleTask;
|
||||
NS_IdleDispatchToCurrentThread(firstIdleTask.forget());
|
||||
}
|
||||
|
@ -1761,7 +1761,8 @@ ContentParent::ActorDestroy(ActorDestroyReason why)
|
||||
mIdleListeners.Clear();
|
||||
|
||||
MessageLoop::current()->
|
||||
PostTask(NewRunnableFunction(DelayedDeleteSubprocess, mSubprocess));
|
||||
PostTask(NewRunnableFunction("DelayedDeleteSubprocessRunnable",
|
||||
DelayedDeleteSubprocess, mSubprocess));
|
||||
mSubprocess = nullptr;
|
||||
|
||||
// IPDL rules require actors to live on past ActorDestroy, but it
|
||||
@ -3118,7 +3119,8 @@ ContentParent::OnGenerateMinidumpComplete(bool aDumpResult)
|
||||
|
||||
// EnsureProcessTerminated has responsibilty for closing otherProcessHandle.
|
||||
XRE_GetIOMessageLoop()->PostTask(
|
||||
NewRunnableFunction(&ProcessWatcher::EnsureProcessTerminated,
|
||||
NewRunnableFunction("EnsureProcessTerminatedRunnable",
|
||||
&ProcessWatcher::EnsureProcessTerminated,
|
||||
otherProcessHandle, /*force=*/true));
|
||||
}
|
||||
|
||||
|
@ -831,21 +831,24 @@ class RTCPeerConnection {
|
||||
this._ensureOfferToReceive("video");
|
||||
}
|
||||
|
||||
if (options.offerToReceiveVideo === false) {
|
||||
this.logWarning("offerToReceiveVideo: false is ignored now. If you " +
|
||||
"want to disallow a recv track, use " +
|
||||
"RTCRtpTransceiver.direction");
|
||||
}
|
||||
|
||||
if (options.offerToReceiveAudio) {
|
||||
this._ensureOfferToReceive("audio");
|
||||
}
|
||||
|
||||
if (options.offerToReceiveAudio === false) {
|
||||
this.logWarning("offerToReceiveAudio: false is ignored now. If you " +
|
||||
"want to disallow a recv track, use " +
|
||||
"RTCRtpTransceiver.direction");
|
||||
}
|
||||
this._transceivers
|
||||
.filter(transceiver => {
|
||||
return (options.offerToReceiveVideo === false &&
|
||||
transceiver.receiver.track.kind == "video") ||
|
||||
(options.offerToReceiveAudio === false &&
|
||||
transceiver.receiver.track.kind == "audio");
|
||||
})
|
||||
.forEach(transceiver => {
|
||||
if (transceiver.direction == "sendrecv") {
|
||||
transceiver.setDirectionInternal("sendonly");
|
||||
} else if (transceiver.direction == "recvonly") {
|
||||
transceiver.setDirectionInternal("inactive");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async _createOffer(options) {
|
||||
|
@ -52,7 +52,8 @@ VideoDecoderManagerChild::InitializeThread()
|
||||
VideoDecoderManagerChild::InitForContent(Endpoint<PVideoDecoderManagerChild>&& aVideoManager)
|
||||
{
|
||||
InitializeThread();
|
||||
sVideoDecoderChildThread->Dispatch(NewRunnableFunction(&Open, Move(aVideoManager)), NS_DISPATCH_NORMAL);
|
||||
sVideoDecoderChildThread->Dispatch(NewRunnableFunction("InitForContentRunnable",
|
||||
&Open, Move(aVideoManager)), NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
|
@ -12,7 +12,7 @@ load 497734-1.xhtml
|
||||
load 497734-2.html
|
||||
load 576612-1.html
|
||||
load 752784-1.html
|
||||
skip-if(Android) load 789075-1.html # bug 1374405
|
||||
skip-if(Android||(webrender&&winWidget)) load 789075-1.html # bug 1374405 for android, bug 1426199 for webrender
|
||||
skip-if(Android&&AndroidVersion=='22') HTTP load 795892-1.html # bug 1358718
|
||||
load 844563.html
|
||||
load 846612.html
|
||||
@ -95,7 +95,7 @@ load 1291702.html
|
||||
load 1378826.html
|
||||
load 1384248.html
|
||||
load 1389304.html
|
||||
load 1393272.webm
|
||||
skip-if(webrender&&winWidget) load 1393272.webm # bug 1426199 for webrender
|
||||
load 1411322.html
|
||||
load disconnect-wrong-destination.html
|
||||
load analyser-channels-1.html
|
||||
|
@ -356,14 +356,18 @@
|
||||
let checkAddTransceiverWithOfferToReceive = async kinds => {
|
||||
let pc = new RTCPeerConnection();
|
||||
|
||||
let propsToSet = kinds.map(kind => {
|
||||
if (kind == "audio") {
|
||||
return "offerToReceiveAudio";
|
||||
} else if (kind == "video") {
|
||||
return "offerToReceiveVideo";
|
||||
}
|
||||
});
|
||||
|
||||
let options = {};
|
||||
|
||||
for (let kind of kinds) {
|
||||
if (kind == "audio") {
|
||||
options.offerToReceiveAudio = true;
|
||||
} else if (kind == "video") {
|
||||
options.offerToReceiveVideo = true;
|
||||
}
|
||||
for (let prop of propsToSet) {
|
||||
options[prop] = true;
|
||||
}
|
||||
|
||||
let offer = await pc.createOffer(options);
|
||||
@ -398,6 +402,35 @@
|
||||
|
||||
hasProps(pc.getTransceivers(), expected);
|
||||
|
||||
// Test offerToReceive: false
|
||||
for (let prop of propsToSet) {
|
||||
options[prop] = false;
|
||||
}
|
||||
|
||||
// Check that sendrecv goes to sendonly
|
||||
for (let transceiver of pc.getTransceivers()) {
|
||||
transceiver.direction = "sendrecv";
|
||||
}
|
||||
|
||||
for (let transceiverCheck of expected) {
|
||||
transceiverCheck.direction = "sendonly";
|
||||
}
|
||||
|
||||
offer = await pc.createOffer(options);
|
||||
hasProps(pc.getTransceivers(), expected);
|
||||
|
||||
// Check that recvonly goes to inactive
|
||||
for (let transceiver of pc.getTransceivers()) {
|
||||
transceiver.direction = "recvonly";
|
||||
}
|
||||
|
||||
for (let transceiverCheck of expected) {
|
||||
transceiverCheck.direction = "inactive";
|
||||
}
|
||||
|
||||
offer = await pc.createOffer(options);
|
||||
hasProps(pc.getTransceivers(), expected);
|
||||
|
||||
pc.close();
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,6 @@ HTTP load 570884.html
|
||||
# Plugin arch is going to change anyway with OOP content so skipping
|
||||
# this test for now is OK.
|
||||
skip-if(!haveTestPlugin||http.platform!="X11") HTTP load 598862.html
|
||||
skip-if(Android) HTTP load 626602-1.html # bug 908363
|
||||
skip-if(Android||(webrender&&winWidget)) HTTP load 626602-1.html # bug 908363; bug 1322815 for webrender
|
||||
HTTP load 752340.html
|
||||
HTTP load 843086.xhtml
|
||||
|
@ -24,6 +24,8 @@
|
||||
<a id="l2" tabindex="4">
|
||||
<circle cx="10" cy="260" r="10"/>
|
||||
</a>
|
||||
<rect id="r6" x="0" y="70" width="100" height="100" fill="yellow" tabindex="6"/>
|
||||
<rect id="r7" x="0" y="70" width="100" height="100" fill="yellow" tabindex="7"/>
|
||||
</svg>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
@ -75,7 +77,7 @@ function main()
|
||||
synthesizeKey("VK_TAB", {});
|
||||
// On Mac, SVG link elements should not be focused.
|
||||
if (isMac) {
|
||||
is(document.activeElement.tabIndex, 3, "The active element tabindex is 3");
|
||||
is(document.activeElement.tabIndex, 6, "The active element tabindex is 6");
|
||||
} else {
|
||||
is(document.activeElement.tabIndex, 4, "The active element tabindex is 4");
|
||||
}
|
||||
@ -83,9 +85,7 @@ function main()
|
||||
synthesizeKey("VK_TAB", {});
|
||||
// On Mac, SVG link elements should not be focused.
|
||||
if (isMac) {
|
||||
// This test has to be run with other tests, otherwise,
|
||||
// document.activeElement.tabIndex will be -1 on Mac.
|
||||
is(document.activeElement.tabIndex, 3, "The active element tabindex is 3");
|
||||
is(document.activeElement.tabIndex, 7, "The active element tabindex is 7");
|
||||
} else {
|
||||
is(document.activeElement.tabIndex, 5, "The active element tabindex is 5");
|
||||
}
|
||||
@ -96,7 +96,8 @@ function main()
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
window.addEventListener("load", main);
|
||||
SimpleTest.waitForFocus(main);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -9,8 +9,6 @@
|
||||
|
||||
// https://drafts.csswg.org/cssom/#cssnamespacerule
|
||||
interface CSSNamespaceRule : CSSRule {
|
||||
// Not implemented yet. <See
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1326514>.
|
||||
// readonly attribute DOMString namespaceURI;
|
||||
// readonly attribute DOMString prefix;
|
||||
readonly attribute DOMString namespaceURI;
|
||||
readonly attribute DOMString prefix;
|
||||
};
|
||||
|
@ -227,7 +227,7 @@ ServiceWorkerJob::Finish(ErrorResult& aRv)
|
||||
|
||||
// Async release this object to ensure that our caller methods complete
|
||||
// as well.
|
||||
NS_ReleaseOnMainThreadSystemGroup("ServiceWorkerJob",
|
||||
NS_ReleaseOnMainThreadSystemGroup("ServiceWorkerJobProxyRunnable",
|
||||
kungFuDeathGrip.forget(), true /* always proxy */);
|
||||
}
|
||||
|
||||
|
@ -1017,7 +1017,7 @@ ServiceWorkerPrivate::SendPushEvent(const nsAString& aMessageId,
|
||||
|
||||
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
|
||||
new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(
|
||||
"ServiceWorkerRegistrationInfo", aRegistration, false));
|
||||
"ServiceWorkerRegistrationInfoProxy", aRegistration, false));
|
||||
|
||||
RefPtr<WorkerRunnable> r = new SendPushEventRunnable(mWorkerPrivate,
|
||||
token,
|
||||
@ -1760,7 +1760,7 @@ ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel,
|
||||
|
||||
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
|
||||
new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(
|
||||
"ServiceWorkerRegistrationInfo", registration, false));
|
||||
"ServiceWorkerRegistrationInfoProxy", registration, false));
|
||||
|
||||
RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
|
||||
|
||||
|
@ -285,7 +285,7 @@ ServiceWorkerRegistrationInfo::Activate()
|
||||
|
||||
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> handle(
|
||||
new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(
|
||||
"ServiceWorkerRegistrationInfo", this));
|
||||
"ServiceWorkerRegistrationInfoProxy", this));
|
||||
RefPtr<LifeCycleEventCallback> callback = new ContinueActivateRunnable(handle);
|
||||
|
||||
ServiceWorkerPrivate* workerPrivate = mActiveWorker->WorkerPrivate();
|
||||
|
@ -3875,12 +3875,6 @@ EditorBase::IsContainer(nsINode* aNode)
|
||||
return aNode ? true : false;
|
||||
}
|
||||
|
||||
bool
|
||||
EditorBase::IsContainer(nsIDOMNode* aNode)
|
||||
{
|
||||
return aNode ? true : false;
|
||||
}
|
||||
|
||||
bool
|
||||
EditorBase::IsEditable(nsIDOMNode* aNode)
|
||||
{
|
||||
|
@ -1063,7 +1063,6 @@ public:
|
||||
* Returns true if aNode is a container.
|
||||
*/
|
||||
virtual bool IsContainer(nsINode* aNode);
|
||||
virtual bool IsContainer(nsIDOMNode* aNode);
|
||||
|
||||
/**
|
||||
* returns true if aNode is an editable node.
|
||||
|
@ -3471,17 +3471,6 @@ HTMLEditor::IsContainer(nsINode* aNode)
|
||||
return HTMLEditUtils::IsContainer(tagEnum);
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLEditor::IsContainer(nsIDOMNode* aNode)
|
||||
{
|
||||
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
return IsContainer(node);
|
||||
}
|
||||
|
||||
|
||||
nsresult
|
||||
HTMLEditor::SelectEntireDocument(Selection* aSelection)
|
||||
{
|
||||
@ -4051,7 +4040,7 @@ HTMLEditor::IsEmptyNodeImpl(nsINode* aNode,
|
||||
// anchors are containers, named anchors are "empty" but we don't
|
||||
// want to treat them as such. Also, don't call ListItems or table
|
||||
// cells empty if caller desires. Form Widgets not empty.
|
||||
if (!IsContainer(aNode->AsDOMNode()) ||
|
||||
if (!IsContainer(aNode) ||
|
||||
(HTMLEditUtils::IsNamedAnchor(aNode) ||
|
||||
HTMLEditUtils::IsFormWidget(aNode) ||
|
||||
(aListOrCellNotEmpty &&
|
||||
|
@ -299,7 +299,6 @@ public:
|
||||
* Returns true if aNode is a container.
|
||||
*/
|
||||
virtual bool IsContainer(nsINode* aNode) override;
|
||||
virtual bool IsContainer(nsIDOMNode* aNode) override;
|
||||
|
||||
/**
|
||||
* Make the given selection span the entire document.
|
||||
|
@ -17,7 +17,7 @@
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "mozilla/layers/CompositorOptions.h"
|
||||
#include "mozilla/widget/CompositorWidget.h"
|
||||
#include "mozilla/widget/X11CompositorWidget.h"
|
||||
#include "mozilla/widget/GtkCompositorWidget.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
#include "prenv.h"
|
||||
@ -803,7 +803,7 @@ CreateForWidget(Display* aXDisplay, Window aXWindow,
|
||||
already_AddRefed<GLContext>
|
||||
GLContextProviderGLX::CreateForCompositorWidget(CompositorWidget* aCompositorWidget, bool aForceAccelerated)
|
||||
{
|
||||
X11CompositorWidget* compWidget = aCompositorWidget->AsX11();
|
||||
GtkCompositorWidget* compWidget = aCompositorWidget->AsX11();
|
||||
MOZ_ASSERT(compWidget);
|
||||
|
||||
return CreateForWidget(compWidget->XDisplay(),
|
||||
|
@ -245,7 +245,7 @@ GPUProcessHost::DestroyProcess()
|
||||
}
|
||||
|
||||
MessageLoop::current()->
|
||||
PostTask(NewRunnableFunction(DelayedDeleteSubprocess, this));
|
||||
PostTask(NewRunnableFunction("DestroyProcessRunnable", DelayedDeleteSubprocess, this));
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
|
@ -196,7 +196,9 @@ PaintThread::Shutdown()
|
||||
return;
|
||||
}
|
||||
|
||||
sThread->Dispatch(NewRunnableFunction(DestroyPaintThread, Move(pt)));
|
||||
sThread->Dispatch(NewRunnableFunction("DestroyPaintThreadRunnable",
|
||||
DestroyPaintThread,
|
||||
Move(pt)));
|
||||
sThread->Shutdown();
|
||||
sThread = nullptr;
|
||||
}
|
||||
|
@ -2384,7 +2384,8 @@ void
|
||||
APZCTreeManager::SetLongTapEnabled(bool aLongTapEnabled)
|
||||
{
|
||||
APZThreadUtils::RunOnControllerThread(
|
||||
NewRunnableFunction(GestureEventListener::SetLongTapEnabled, aLongTapEnabled));
|
||||
NewRunnableFunction("SetLongTapEnabledRunnable",
|
||||
GestureEventListener::SetLongTapEnabled, aLongTapEnabled));
|
||||
}
|
||||
|
||||
RefPtr<HitTestingTreeNode>
|
||||
|
@ -338,13 +338,15 @@ DeallocateTextureClient(TextureDeallocParams params)
|
||||
bool done = false;
|
||||
ReentrantMonitor barrier("DeallocateTextureClient");
|
||||
ReentrantMonitorAutoEnter autoMon(barrier);
|
||||
ipdlMsgLoop->PostTask(NewRunnableFunction(DeallocateTextureClientSyncProxy,
|
||||
ipdlMsgLoop->PostTask(NewRunnableFunction("DeallocateTextureClientSyncProxyRunnable",
|
||||
DeallocateTextureClientSyncProxy,
|
||||
params, &barrier, &done));
|
||||
while (!done) {
|
||||
barrier.Wait();
|
||||
}
|
||||
} else {
|
||||
ipdlMsgLoop->PostTask(NewRunnableFunction(DeallocateTextureClient,
|
||||
ipdlMsgLoop->PostTask(NewRunnableFunction("DeallocateTextureClientRunnable",
|
||||
DeallocateTextureClient,
|
||||
params));
|
||||
}
|
||||
// The work has been forwarded to the IPDL thread, we are done.
|
||||
@ -840,7 +842,8 @@ void CancelTextureClientRecycle(uint64_t aTextureId, LayersIPCChannel* aAllocato
|
||||
if (MessageLoop::current() == msgLoop) {
|
||||
aAllocator->CancelWaitForRecycle(aTextureId);
|
||||
} else {
|
||||
msgLoop->PostTask(NewRunnableFunction(CancelTextureClientRecycle,
|
||||
msgLoop->PostTask(NewRunnableFunction("CancelTextureClientRecycleRunnable",
|
||||
CancelTextureClientRecycle,
|
||||
aTextureId, aAllocator));
|
||||
}
|
||||
}
|
||||
|
@ -495,7 +495,8 @@ CompositorBridgeChild::RecvCaptureAllPlugins(const uintptr_t& aParentWidget)
|
||||
// Bounce the call to SendAllPluginsCaptured off the ImageBridgeChild loop,
|
||||
// to make sure that the image updates on that thread have been processed.
|
||||
ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(
|
||||
NewRunnableFunction(&ScheduleSendAllPluginsCaptured, this,
|
||||
NewRunnableFunction("ScheduleSendAllPluginsCapturedRunnable",
|
||||
&ScheduleSendAllPluginsCaptured, this,
|
||||
MessageLoop::current()));
|
||||
return IPC_OK();
|
||||
#else
|
||||
|
@ -382,10 +382,12 @@ CompositorBridgeParent::Initialize()
|
||||
// can destroy this instance is initialized on the compositor thread after
|
||||
// this task has been processed.
|
||||
MOZ_ASSERT(CompositorLoop());
|
||||
CompositorLoop()->PostTask(NewRunnableFunction(&AddCompositor,
|
||||
CompositorLoop()->PostTask(NewRunnableFunction("AddCompositorRunnable",
|
||||
&AddCompositor,
|
||||
this, &mCompositorBridgeID));
|
||||
|
||||
CompositorLoop()->PostTask(NewRunnableFunction(SetThreadPriority));
|
||||
CompositorLoop()->PostTask(NewRunnableFunction("SetThreadPriorityRunnable",
|
||||
SetThreadPriority));
|
||||
|
||||
|
||||
{ // scope lock
|
||||
@ -1788,7 +1790,8 @@ CompositorBridgeParent::DeallocateLayerTreeId(uint64_t aId)
|
||||
gfxCriticalError() << "Attempting to post to a invalid Compositor Loop";
|
||||
return;
|
||||
}
|
||||
CompositorLoop()->PostTask(NewRunnableFunction(&EraseLayerState, aId));
|
||||
CompositorLoop()->PostTask(NewRunnableFunction("EraseLayerStateRunnable",
|
||||
&EraseLayerState, aId));
|
||||
}
|
||||
|
||||
static void
|
||||
@ -1825,7 +1828,8 @@ CompositorBridgeParent::SetControllerForLayerTree(uint64_t aLayersId,
|
||||
{
|
||||
// This ref is adopted by UpdateControllerForLayersId().
|
||||
aController->AddRef();
|
||||
CompositorLoop()->PostTask(NewRunnableFunction(&UpdateControllerForLayersId,
|
||||
CompositorLoop()->PostTask(NewRunnableFunction("UpdateControllerForLayersIdRunnable",
|
||||
&UpdateControllerForLayersId,
|
||||
aLayersId,
|
||||
aController));
|
||||
}
|
||||
@ -1865,7 +1869,8 @@ CompositorBridgeParent::PostInsertVsyncProfilerMarker(TimeStamp aVsyncTimestamp)
|
||||
// Called in the vsync thread
|
||||
if (profiler_is_active() && CompositorThreadHolder::IsActive()) {
|
||||
CompositorLoop()->PostTask(
|
||||
NewRunnableFunction(InsertVsyncProfilerMarker, aVsyncTimestamp));
|
||||
NewRunnableFunction("InsertVsyncProfilerMarkerRunnable", InsertVsyncProfilerMarker,
|
||||
aVsyncTimestamp));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ function runTest() {
|
||||
var windowutils;
|
||||
var acceleratedWindows = 0;
|
||||
var advancedLayersWindows = 0;
|
||||
var webrenderWindows = 0;
|
||||
var layerManagerLog = [];
|
||||
while (windows.hasMoreElements()) {
|
||||
windowutils = windows.getNext().QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
@ -43,6 +44,9 @@ function runTest() {
|
||||
if (windowutils.layerManagerType != "Basic") {
|
||||
acceleratedWindows++;
|
||||
}
|
||||
if (windowutils.layerManagerType == "WebRender") {
|
||||
webrenderWindows++;
|
||||
}
|
||||
if (windowutils.usingAdvancedLayers) {
|
||||
advancedLayersWindows++;
|
||||
}
|
||||
@ -108,6 +112,11 @@ function runTest() {
|
||||
ok(gfxInfo.DWriteEnabled, "DirectWrite enabled on Windows 7 or newer");
|
||||
}
|
||||
|
||||
var shouldGetWR = false;
|
||||
try {
|
||||
shouldGetWR = SpecialPowers.DOMWindowUtils.isWebRenderRequested;
|
||||
} catch (e) {}
|
||||
|
||||
var advancedLayersEnabled = false;
|
||||
var advancedLayersEnabledOnWin7 = false;
|
||||
try {
|
||||
@ -118,12 +127,22 @@ function runTest() {
|
||||
if (version < 6.2) {
|
||||
shouldGetAL &= advancedLayersEnabledOnWin7;
|
||||
}
|
||||
if (shouldGetWR) {
|
||||
shouldGetAL = false;
|
||||
}
|
||||
|
||||
if (shouldGetAL) {
|
||||
isnot(advancedLayersWindows, 0, "Advanced Layers enabled on Windows; "
|
||||
+ layerManagerLog.join(","));
|
||||
} else {
|
||||
is(advancedLayersWindows, 0, "Advanced Layers disabled on Windows");
|
||||
}
|
||||
|
||||
if (shouldGetWR) {
|
||||
isnot(webrenderWindows, 0, "WebRender enabled on Windows");
|
||||
} else {
|
||||
is(webrenderWindows, 0, "WebRender disabled on Windows");
|
||||
}
|
||||
break;
|
||||
|
||||
case "Linux":
|
||||
|
@ -2460,10 +2460,23 @@ gfxPlatform::InitCompositorAccelerationPrefs()
|
||||
}
|
||||
}
|
||||
|
||||
/*static*/ bool
|
||||
gfxPlatform::WebRenderPrefEnabled()
|
||||
{
|
||||
return Preferences::GetBool("gfx.webrender.enabled", false);
|
||||
}
|
||||
|
||||
/*static*/ bool
|
||||
gfxPlatform::WebRenderEnvvarEnabled()
|
||||
{
|
||||
const char* env = PR_GetEnv("MOZ_WEBRENDER");
|
||||
return (env && *env == '1');
|
||||
}
|
||||
|
||||
void
|
||||
gfxPlatform::InitWebRenderConfig()
|
||||
{
|
||||
bool prefEnabled = Preferences::GetBool("gfx.webrender.enabled", false);
|
||||
bool prefEnabled = WebRenderPrefEnabled();
|
||||
|
||||
ScopedGfxFeatureReporter reporter("WR", prefEnabled);
|
||||
if (!XRE_IsParentProcess()) {
|
||||
@ -2485,11 +2498,8 @@ gfxPlatform::InitWebRenderConfig()
|
||||
|
||||
if (prefEnabled) {
|
||||
featureWebRender.UserEnable("Enabled by pref");
|
||||
} else {
|
||||
const char* env = PR_GetEnv("MOZ_WEBRENDER");
|
||||
if (env && *env == '1') {
|
||||
featureWebRender.UserEnable("Enabled by envvar");
|
||||
}
|
||||
} else if (WebRenderEnvvarEnabled()) {
|
||||
featureWebRender.UserEnable("Enabled by envvar");
|
||||
}
|
||||
|
||||
// HW_COMPOSITING being disabled implies interfacing with the GPU might break
|
||||
|
@ -710,6 +710,11 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// you probably want to use gfxVars::UseWebRender() instead of this
|
||||
static bool WebRenderPrefEnabled();
|
||||
// you probably want to use gfxVars::UseWebRender() instead of this
|
||||
static bool WebRenderEnvvarEnabled();
|
||||
|
||||
protected:
|
||||
gfxPlatform();
|
||||
virtual ~gfxPlatform();
|
||||
|
@ -679,6 +679,10 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
gfxUserFontSet* GetUserFontSet() const { return mFontSet; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
const uint8_t* SanitizeOpenTypeData(const uint8_t* aData,
|
||||
uint32_t aLength,
|
||||
|
@ -156,7 +156,8 @@ VRManagerChild::Destroy()
|
||||
// The DeferredDestroyVRManager task takes ownership of
|
||||
// the VRManagerChild and will release it when it runs.
|
||||
MessageLoop::current()->PostTask(
|
||||
NewRunnableFunction(DeferredDestroy, selfRef));
|
||||
NewRunnableFunction("VRManagerChildDestroyRunnable",
|
||||
DeferredDestroy, selfRef));
|
||||
}
|
||||
|
||||
PVRLayerChild*
|
||||
|
@ -121,7 +121,8 @@ VRManagerParent::CreateSameProcess()
|
||||
RefPtr<VRManagerParent> vmp = new VRManagerParent(base::GetCurrentProcId(), false);
|
||||
vmp->mVRListenerThreadHolder = VRListenerThreadHolder::GetSingleton();
|
||||
vmp->mSelfRef = vmp;
|
||||
loop->PostTask(NewRunnableFunction(RegisterVRManagerInVRListenerThread, vmp.get()));
|
||||
loop->PostTask(NewRunnableFunction("RegisterVRManagerInVRListenerThreadRunnable",
|
||||
RegisterVRManagerInVRListenerThread, vmp.get()));
|
||||
return vmp.get();
|
||||
}
|
||||
|
||||
|
@ -234,6 +234,7 @@ RenderThread::UpdateAndRender(wr::WindowId aWindowId)
|
||||
|
||||
auto epochs = renderer->FlushRenderedEpochs();
|
||||
layers::CompositorThreadHolder::Loop()->PostTask(NewRunnableFunction(
|
||||
"NotifyDidRenderRunnable",
|
||||
&NotifyDidRender,
|
||||
renderer->GetCompositorBridge(),
|
||||
epochs,
|
||||
|
@ -227,6 +227,7 @@ void
|
||||
RendererOGL::NotifyWebRenderError(WebRenderError aError)
|
||||
{
|
||||
layers::CompositorThreadHolder::Loop()->PostTask(NewRunnableFunction(
|
||||
"DoNotifyWebRenderErrorRunnable",
|
||||
&DoNotifyWebRenderError,
|
||||
mBridge,
|
||||
aError
|
||||
|
@ -490,16 +490,16 @@ static inline wr::SideOffsets2D_f32 ToSideOffsets2D_f32(float top, float right,
|
||||
return offset;
|
||||
}
|
||||
|
||||
static inline wr::RepeatMode ToRepeatMode(uint8_t repeatMode)
|
||||
static inline wr::RepeatMode ToRepeatMode(mozilla::StyleBorderImageRepeat repeatMode)
|
||||
{
|
||||
switch (repeatMode) {
|
||||
case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
|
||||
case mozilla::StyleBorderImageRepeat::Stretch:
|
||||
return wr::RepeatMode::Stretch;
|
||||
case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
|
||||
case mozilla::StyleBorderImageRepeat::Repeat:
|
||||
return wr::RepeatMode::Repeat;
|
||||
case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
|
||||
case mozilla::StyleBorderImageRepeat::Round:
|
||||
return wr::RepeatMode::Round;
|
||||
case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
|
||||
case mozilla::StyleBorderImageRepeat::Space:
|
||||
return wr::RepeatMode::Space;
|
||||
default:
|
||||
MOZ_ASSERT(false);
|
||||
|
@ -459,7 +459,7 @@ ClippedImage::DrawSingleTile(gfxContext* aContext,
|
||||
MOZ_ASSERT(!MustCreateSurface(aContext, aSize, aRegion, aFlags),
|
||||
"Shouldn't need to create a surface");
|
||||
|
||||
gfxRect clip(mClip.x, mClip.y, mClip.Width(), mClip.Height());
|
||||
gfxRect clip(mClip.X(), mClip.Y(), mClip.Width(), mClip.Height());
|
||||
nsIntSize size(aSize), innerSize(aSize);
|
||||
bool needScale = false;
|
||||
if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) {
|
||||
@ -486,11 +486,11 @@ ClippedImage::DrawSingleTile(gfxContext* aContext,
|
||||
// We restrict our drawing to only the clipping region, and translate so that
|
||||
// the clipping region is placed at the position the caller expects.
|
||||
ImageRegion region(aRegion);
|
||||
region.MoveBy(clip.x, clip.y);
|
||||
region.MoveBy(clip.X(), clip.Y());
|
||||
region = region.Intersect(clip);
|
||||
|
||||
gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
|
||||
aContext->Multiply(gfxMatrix::Translation(-clip.x, -clip.y));
|
||||
aContext->Multiply(gfxMatrix::Translation(-clip.X(), -clip.Y()));
|
||||
|
||||
auto unclipViewport = [&](const SVGImageContext& aOldContext) {
|
||||
// Map the viewport to the inner image. Note that we don't take the aSize
|
||||
@ -601,7 +601,7 @@ ClippedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
|
||||
|
||||
nsIntRect rect(InnerImage()->GetImageSpaceInvalidationRect(aRect));
|
||||
rect = rect.Intersect(mClip);
|
||||
rect.MoveBy(-mClip.x, -mClip.y);
|
||||
rect.MoveBy(-mClip.X(), -mClip.Y());
|
||||
return rect;
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ Downscaler::BeginFrame(const nsIntSize& aOriginalSize,
|
||||
}
|
||||
|
||||
mFrameRect = aFrameRect.valueOr(nsIntRect(nsIntPoint(), aOriginalSize));
|
||||
MOZ_ASSERT(mFrameRect.x >= 0 && mFrameRect.y >= 0 &&
|
||||
MOZ_ASSERT(mFrameRect.X() >= 0 && mFrameRect.Y() >= 0 &&
|
||||
mFrameRect.Width() >= 0 && mFrameRect.Height() >= 0,
|
||||
"Frame rect must have non-negative components");
|
||||
MOZ_ASSERT(nsIntRect(0, 0, aOriginalSize.width, aOriginalSize.height)
|
||||
@ -168,7 +168,7 @@ Downscaler::ResetForNextProgressivePass()
|
||||
SkipToRow(mOriginalSize.height - 1);
|
||||
} else {
|
||||
// If we have a vertical offset, commit rows to shift us past it.
|
||||
SkipToRow(mFrameRect.y);
|
||||
SkipToRow(mFrameRect.Y());
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,7 +219,7 @@ Downscaler::CommitRow()
|
||||
|
||||
// If we're at the end of the part of the original image that has data, commit
|
||||
// rows to shift us to the end.
|
||||
if (mCurrentInLine == (mFrameRect.y + mFrameRect.Height())) {
|
||||
if (mCurrentInLine == (mFrameRect.Y() + mFrameRect.Height())) {
|
||||
SkipToRow(mOriginalSize.height - 1);
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ public:
|
||||
/// Retrieves the buffer into which the Decoder should write each row.
|
||||
uint8_t* RowBuffer()
|
||||
{
|
||||
return mRowBuffer.get() + mFrameRect.x * sizeof(uint32_t);
|
||||
return mRowBuffer.get() + mFrameRect.X() * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
/// Clears the current row buffer.
|
||||
|
@ -602,9 +602,7 @@ FrameAnimator::DoBlend(DrawableSurface& aFrames,
|
||||
? prevFrameData.mRect.Intersect(*prevFrameData.mBlendRect)
|
||||
: prevFrameData.mRect;
|
||||
|
||||
bool isFullPrevFrame = prevRect.x == 0 && prevRect.y == 0 &&
|
||||
prevRect.Width() == mSize.width &&
|
||||
prevRect.Height() == mSize.height;
|
||||
bool isFullPrevFrame = prevRect.IsEqualRect(0, 0, mSize.width, mSize.height);
|
||||
|
||||
// Optimization: DisposeClearAll if the previous frame is the same size as
|
||||
// container and it's clearing itself
|
||||
@ -619,9 +617,7 @@ FrameAnimator::DoBlend(DrawableSurface& aFrames,
|
||||
? nextFrameData.mRect.Intersect(*nextFrameData.mBlendRect)
|
||||
: nextFrameData.mRect;
|
||||
|
||||
bool isFullNextFrame = nextRect.x == 0 && nextRect.y == 0 &&
|
||||
nextRect.Width() == mSize.width &&
|
||||
nextRect.Height() == mSize.height;
|
||||
bool isFullNextFrame = nextRect.IsEqualRect(0, 0, mSize.width, mSize.height);
|
||||
|
||||
if (!nextFrame->GetIsPaletted()) {
|
||||
// Optimization: Skip compositing if the previous frame wants to clear the
|
||||
@ -719,7 +715,7 @@ FrameAnimator::DoBlend(DrawableSurface& aFrames,
|
||||
// No need to blank the composite frame
|
||||
needToBlankComposite = false;
|
||||
} else {
|
||||
if ((prevRect.x >= nextRect.x) && (prevRect.y >= nextRect.y) &&
|
||||
if ((prevRect.X() >= nextRect.X()) && (prevRect.Y() >= nextRect.Y()) &&
|
||||
(prevRect.XMost() <= nextRect.XMost()) &&
|
||||
(prevRect.YMost() <= nextRect.YMost())) {
|
||||
// Optimization: No need to dispose prev.frame when
|
||||
@ -893,8 +889,8 @@ FrameAnimator::ClearFrame(uint8_t* aFrameData, const IntRect& aFrameRect,
|
||||
}
|
||||
|
||||
uint32_t bytesPerRow = aFrameRect.Width() * 4;
|
||||
for (int row = toClear.y; row < toClear.YMost(); ++row) {
|
||||
memset(aFrameData + toClear.x * 4 + row * bytesPerRow, 0,
|
||||
for (int row = toClear.Y(); row < toClear.YMost(); ++row) {
|
||||
memset(aFrameData + toClear.X() * 4 + row * bytesPerRow, 0,
|
||||
toClear.Width() * 4);
|
||||
}
|
||||
}
|
||||
@ -930,24 +926,25 @@ FrameAnimator::DrawFrameTo(const uint8_t* aSrcData, const IntRect& aSrcRect,
|
||||
NS_ENSURE_ARG_POINTER(aDstPixels);
|
||||
|
||||
// According to both AGIF and APNG specs, offsets are unsigned
|
||||
if (aSrcRect.x < 0 || aSrcRect.y < 0) {
|
||||
if (aSrcRect.X() < 0 || aSrcRect.Y() < 0) {
|
||||
NS_WARNING("FrameAnimator::DrawFrameTo: negative offsets not allowed");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Outside the destination frame, skip it
|
||||
if ((aSrcRect.x > aDstRect.Width()) || (aSrcRect.y > aDstRect.Height())) {
|
||||
if ((aSrcRect.X() > aDstRect.Width()) || (aSrcRect.Y() > aDstRect.Height())) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (aSrcPaletteLength) {
|
||||
// Larger than the destination frame, clip it
|
||||
int32_t width = std::min(aSrcRect.Width(), aDstRect.Width() - aSrcRect.x);
|
||||
int32_t height = std::min(aSrcRect.Height(), aDstRect.Height() - aSrcRect.y);
|
||||
int32_t width = std::min(aSrcRect.Width(), aDstRect.Width() - aSrcRect.X());
|
||||
int32_t height = std::min(aSrcRect.Height(), aDstRect.Height() - aSrcRect.Y());
|
||||
|
||||
// The clipped image must now fully fit within destination image frame
|
||||
NS_ASSERTION((aSrcRect.x >= 0) && (aSrcRect.y >= 0) &&
|
||||
(aSrcRect.x + width <= aDstRect.Width()) &&
|
||||
(aSrcRect.y + height <= aDstRect.Height()),
|
||||
NS_ASSERTION((aSrcRect.X() >= 0) && (aSrcRect.Y() >= 0) &&
|
||||
(aSrcRect.X() + width <= aDstRect.Width()) &&
|
||||
(aSrcRect.Y() + height <= aDstRect.Height()),
|
||||
"FrameAnimator::DrawFrameTo: Invalid aSrcRect");
|
||||
|
||||
// clipped image size may be smaller than source, but not larger
|
||||
@ -960,7 +957,7 @@ FrameAnimator::DrawFrameTo(const uint8_t* aSrcData, const IntRect& aSrcRect,
|
||||
const uint32_t* colormap = reinterpret_cast<const uint32_t*>(aSrcData);
|
||||
|
||||
// Skip to the right offset
|
||||
dstPixels += aSrcRect.x + (aSrcRect.y * aDstRect.Width());
|
||||
dstPixels += aSrcRect.X() + (aSrcRect.Y() * aDstRect.Width());
|
||||
if (!aSrcHasAlpha) {
|
||||
for (int32_t r = height; r > 0; --r) {
|
||||
for (int32_t c = 0; c < width; c++) {
|
||||
@ -1030,7 +1027,7 @@ FrameAnimator::DrawFrameTo(const uint8_t* aSrcData, const IntRect& aSrcRect,
|
||||
dst,
|
||||
0, 0,
|
||||
0, 0,
|
||||
aSrcRect.x, aSrcRect.y,
|
||||
aSrcRect.X(), aSrcRect.Y(),
|
||||
aSrcRect.Width(), aSrcRect.Height());
|
||||
} else {
|
||||
// We need to do the OVER followed by SOURCE trick above.
|
||||
@ -1040,15 +1037,15 @@ FrameAnimator::DrawFrameTo(const uint8_t* aSrcData, const IntRect& aSrcRect,
|
||||
dst,
|
||||
0, 0,
|
||||
0, 0,
|
||||
aSrcRect.x, aSrcRect.y,
|
||||
aSrcRect.X(), aSrcRect.Y(),
|
||||
aSrcRect.Width(), aSrcRect.Height());
|
||||
pixman_image_composite32(PIXMAN_OP_SRC,
|
||||
src,
|
||||
nullptr,
|
||||
dst,
|
||||
aBlendRect->x, aBlendRect->y,
|
||||
aBlendRect->X(), aBlendRect->Y(),
|
||||
0, 0,
|
||||
aBlendRect->x, aBlendRect->y,
|
||||
aBlendRect->X(), aBlendRect->Y(),
|
||||
aBlendRect->Width(), aBlendRect->Height());
|
||||
}
|
||||
|
||||
|
@ -395,10 +395,10 @@ OrientedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
|
||||
|
||||
// Transform the invalidation rect into the correct orientation.
|
||||
gfxMatrix matrix(OrientationMatrix(innerSize));
|
||||
gfxRect invalidRect(matrix.TransformBounds(gfxRect(rect.x, rect.y,
|
||||
gfxRect invalidRect(matrix.TransformBounds(gfxRect(rect.X(), rect.Y(),
|
||||
rect.Width(), rect.Height())));
|
||||
|
||||
return IntRect::RoundOut(invalidRect.x, invalidRect.y,
|
||||
return IntRect::RoundOut(invalidRect.X(), invalidRect.Y(),
|
||||
invalidRect.Width(), invalidRect.Height());
|
||||
}
|
||||
|
||||
|
@ -401,10 +401,13 @@ ProgressTracker::SyncNotify(IProgressObserver* aObserver)
|
||||
|
||||
nsIntRect rect;
|
||||
if (image) {
|
||||
if (NS_FAILED(image->GetWidth(&rect.width)) ||
|
||||
NS_FAILED(image->GetHeight(&rect.height))) {
|
||||
int32_t width, height;
|
||||
if (NS_FAILED(image->GetWidth(&width)) ||
|
||||
NS_FAILED(image->GetHeight(&height))) {
|
||||
// Either the image has no intrinsic size, or it has an error.
|
||||
rect = GetMaxSizedIntRect();
|
||||
} else {
|
||||
rect.SizeTo(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -423,12 +423,12 @@ protected:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mRow = mUnclampedFrameRect.y;
|
||||
mRow = mUnclampedFrameRect.Y();
|
||||
|
||||
// Advance the next pipeline stage to the beginning of the frame rect,
|
||||
// outputting blank rows.
|
||||
if (mFrameRect.y > 0) {
|
||||
for (int32_t rowToOutput = 0; rowToOutput < mFrameRect.y ; ++rowToOutput) {
|
||||
if (mFrameRect.Y() > 0) {
|
||||
for (int32_t rowToOutput = 0; rowToOutput < mFrameRect.Y() ; ++rowToOutput) {
|
||||
mNext.WriteEmptyRow();
|
||||
}
|
||||
}
|
||||
@ -459,7 +459,7 @@ protected:
|
||||
const int32_t currentRow = mRow;
|
||||
mRow++;
|
||||
|
||||
if (currentRow < mFrameRect.y) {
|
||||
if (currentRow < mFrameRect.Y()) {
|
||||
// This row is outside of the frame rect, so just drop it on the floor.
|
||||
rowPtr = mBuffer ? mBuffer.get() : mNext.CurrentRowPointer();
|
||||
return AdjustRowPointer(rowPtr);
|
||||
@ -474,13 +474,13 @@ protected:
|
||||
// is negative; if that's the case, we have to skip the portion of the
|
||||
// unclamped frame rect that's outside the row.
|
||||
uint32_t* source = reinterpret_cast<uint32_t*>(mBuffer.get()) -
|
||||
std::min(mUnclampedFrameRect.x, 0);
|
||||
std::min(mUnclampedFrameRect.X(), 0);
|
||||
|
||||
// We write |mFrameRect.width| columns starting at |mFrameRect.x|; we've
|
||||
// already clamped these values to the size of the output, so we don't
|
||||
// have to worry about bounds checking here (though WriteBuffer() will do
|
||||
// it for us in any case).
|
||||
WriteState state = mNext.WriteBuffer(source, mFrameRect.x, mFrameRect.Width());
|
||||
WriteState state = mNext.WriteBuffer(source, mFrameRect.X(), mFrameRect.Width());
|
||||
|
||||
rowPtr = state == WriteState::NEED_MORE_DATA ? mBuffer.get()
|
||||
: nullptr;
|
||||
@ -516,7 +516,7 @@ private:
|
||||
return nullptr; // Nothing left to write.
|
||||
}
|
||||
|
||||
return aNextRowPointer + mFrameRect.x * sizeof(uint32_t);
|
||||
return aNextRowPointer + mFrameRect.X() * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
Next mNext; /// The next SurfaceFilter in the chain.
|
||||
|
@ -797,10 +797,10 @@ nsGIFDecoder2::FinishImageDescriptor(const char* aData)
|
||||
IntRect frameRect;
|
||||
|
||||
// Get image offsets with respect to the screen origin.
|
||||
frameRect.x = LittleEndian::readUint16(aData + 0);
|
||||
frameRect.y = LittleEndian::readUint16(aData + 2);
|
||||
frameRect.SetWidth(LittleEndian::readUint16(aData + 4));
|
||||
frameRect.SetHeight(LittleEndian::readUint16(aData + 6));
|
||||
frameRect.SetRect(LittleEndian::readUint16(aData + 0),
|
||||
LittleEndian::readUint16(aData + 2),
|
||||
LittleEndian::readUint16(aData + 4),
|
||||
LittleEndian::readUint16(aData + 6));
|
||||
|
||||
if (!mGIFStruct.images_decoded) {
|
||||
// Work around GIF files where
|
||||
|
@ -507,7 +507,7 @@ imgFrame::SurfaceForDrawing(bool aDoPartialDecode,
|
||||
mFormat);
|
||||
}
|
||||
|
||||
gfxRect available = gfxRect(mDecoded.x, mDecoded.y, mDecoded.Width(),
|
||||
gfxRect available = gfxRect(mDecoded.X(), mDecoded.Y(), mDecoded.Width(),
|
||||
mDecoded.Height());
|
||||
|
||||
if (aDoTile) {
|
||||
@ -523,7 +523,7 @@ imgFrame::SurfaceForDrawing(bool aDoPartialDecode,
|
||||
|
||||
SurfacePattern pattern(aSurface,
|
||||
aRegion.GetExtendMode(),
|
||||
Matrix::Translation(mDecoded.x, mDecoded.y));
|
||||
Matrix::Translation(mDecoded.X(), mDecoded.Y()));
|
||||
target->FillRect(ToRect(aRegion.Intersect(available).Rect()), pattern);
|
||||
|
||||
RefPtr<SourceSurface> newsurf = target->Snapshot();
|
||||
|
@ -170,7 +170,7 @@ PalettedRowsAreSolidColor(Decoder* aDecoder,
|
||||
{
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
IntRect frameRect = currentFrame->GetRect();
|
||||
IntRect solidColorRect(frameRect.x, aStartRow, frameRect.Width(), aRowCount);
|
||||
IntRect solidColorRect(frameRect.X(), aStartRow, frameRect.Width(), aRowCount);
|
||||
return PalettedRectIsSolidColor(aDecoder, solidColorRect, aColor);
|
||||
}
|
||||
|
||||
@ -196,8 +196,8 @@ RectIsSolidColor(SourceSurface* aSurface,
|
||||
ASSERT_TRUE_OR_RETURN(data != nullptr, false);
|
||||
|
||||
int32_t rowLength = mapping.GetStride();
|
||||
for (int32_t row = rect.y; row < rect.YMost(); ++row) {
|
||||
for (int32_t col = rect.x; col < rect.XMost(); ++col) {
|
||||
for (int32_t row = rect.Y(); row < rect.YMost(); ++row) {
|
||||
for (int32_t col = rect.X(); col < rect.XMost(); ++col) {
|
||||
int32_t i = row * rowLength + col * 4;
|
||||
if (aFuzz != 0) {
|
||||
ASSERT_LE_OR_RETURN(abs(aColor.mBlue - data[i + 0]), aFuzz, false);
|
||||
@ -243,8 +243,8 @@ PalettedRectIsSolidColor(Decoder* aDecoder, const IntRect& aRect, uint8_t aColor
|
||||
// Walk through the image data and make sure that the entire rect has the
|
||||
// palette index |aColor|.
|
||||
int32_t rowLength = frameRect.Width();
|
||||
for (int32_t row = rect.y; row < rect.YMost(); ++row) {
|
||||
for (int32_t col = rect.x; col < rect.XMost(); ++col) {
|
||||
for (int32_t row = rect.Y(); row < rect.YMost(); ++row) {
|
||||
for (int32_t col = rect.X(); col < rect.XMost(); ++col) {
|
||||
int32_t i = row * rowLength + col;
|
||||
ASSERT_EQ_OR_RETURN(aColor, imageData[i], false);
|
||||
}
|
||||
@ -342,18 +342,18 @@ CheckGeneratedImage(Decoder* aDecoder,
|
||||
|
||||
// Check that the area above the output rect is transparent. (Region 'A'.)
|
||||
EXPECT_TRUE(RectIsSolidColor(surface,
|
||||
IntRect(0, 0, surfaceSize.width, aRect.y),
|
||||
IntRect(0, 0, surfaceSize.width, aRect.Y()),
|
||||
BGRAColor::Transparent(), aFuzz));
|
||||
|
||||
// Check that the area to the left of the output rect is transparent. (Region 'B'.)
|
||||
EXPECT_TRUE(RectIsSolidColor(surface,
|
||||
IntRect(0, aRect.y, aRect.x, aRect.YMost()),
|
||||
IntRect(0, aRect.Y(), aRect.X(), aRect.YMost()),
|
||||
BGRAColor::Transparent(), aFuzz));
|
||||
|
||||
// Check that the area to the right of the output rect is transparent. (Region 'D'.)
|
||||
const int32_t widthOnRight = surfaceSize.width - aRect.XMost();
|
||||
EXPECT_TRUE(RectIsSolidColor(surface,
|
||||
IntRect(aRect.XMost(), aRect.y, widthOnRight, aRect.YMost()),
|
||||
IntRect(aRect.XMost(), aRect.Y(), widthOnRight, aRect.YMost()),
|
||||
BGRAColor::Transparent(), aFuzz));
|
||||
|
||||
// Check that the area below the output rect is transparent. (Region 'E'.)
|
||||
@ -386,18 +386,18 @@ CheckGeneratedPalettedImage(Decoder* aDecoder, const IntRect& aRect)
|
||||
|
||||
// Check that the area above the output rect is all 0's. (Region 'A'.)
|
||||
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder,
|
||||
IntRect(0, 0, imageSize.width, aRect.y),
|
||||
IntRect(0, 0, imageSize.width, aRect.Y()),
|
||||
0));
|
||||
|
||||
// Check that the area to the left of the output rect is all 0's. (Region 'B'.)
|
||||
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder,
|
||||
IntRect(0, aRect.y, aRect.x, aRect.YMost()),
|
||||
IntRect(0, aRect.Y(), aRect.X(), aRect.YMost()),
|
||||
0));
|
||||
|
||||
// Check that the area to the right of the output rect is all 0's. (Region 'D'.)
|
||||
const int32_t widthOnRight = imageSize.width - aRect.XMost();
|
||||
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder,
|
||||
IntRect(aRect.XMost(), aRect.y, widthOnRight, aRect.YMost()),
|
||||
IntRect(aRect.XMost(), aRect.Y(), widthOnRight, aRect.YMost()),
|
||||
0));
|
||||
|
||||
// Check that the area below the output rect is transparent. (Region 'E'.)
|
||||
|
@ -335,8 +335,8 @@ NewRunnableMethod(T* object, Method method, Args&&... args) {
|
||||
template <class Function, class Params>
|
||||
class RunnableFunction : public mozilla::CancelableRunnable {
|
||||
public:
|
||||
RunnableFunction(Function function, Params&& params)
|
||||
: mozilla::CancelableRunnable("RunnableFunction")
|
||||
RunnableFunction(const char* name, Function function, Params&& params)
|
||||
: mozilla::CancelableRunnable(name)
|
||||
, function_(function)
|
||||
, params_(mozilla::Forward<Params>(params))
|
||||
{
|
||||
@ -362,20 +362,20 @@ class RunnableFunction : public mozilla::CancelableRunnable {
|
||||
|
||||
template <class Function, typename... Args>
|
||||
inline already_AddRefed<mozilla::CancelableRunnable>
|
||||
NewCancelableRunnableFunction(Function function, Args&&... args) {
|
||||
NewCancelableRunnableFunction(const char* name, Function function, Args&&... args) {
|
||||
typedef mozilla::Tuple<typename mozilla::Decay<Args>::Type...> ArgsTuple;
|
||||
RefPtr<mozilla::CancelableRunnable> t =
|
||||
new RunnableFunction<Function, ArgsTuple>(function,
|
||||
new RunnableFunction<Function, ArgsTuple>(name, function,
|
||||
mozilla::MakeTuple(mozilla::Forward<Args>(args)...));
|
||||
return t.forget();
|
||||
}
|
||||
|
||||
template <class Function, typename... Args>
|
||||
inline already_AddRefed<mozilla::Runnable>
|
||||
NewRunnableFunction(Function function, Args&&... args) {
|
||||
NewRunnableFunction(const char* name, Function function, Args&&... args) {
|
||||
typedef mozilla::Tuple<typename mozilla::Decay<Args>::Type...> ArgsTuple;
|
||||
RefPtr<mozilla::Runnable> t =
|
||||
new RunnableFunction<Function, ArgsTuple>(function,
|
||||
new RunnableFunction<Function, ArgsTuple>(name, function,
|
||||
mozilla::MakeTuple(mozilla::Forward<Args>(args)...));
|
||||
return t.forget();
|
||||
}
|
||||
|
@ -335,7 +335,7 @@ BinASTParser::parseBlockStatementAux(const BinKind kind, const BinFields& fields
|
||||
|
||||
// In case of absent optional fields, inject default values.
|
||||
if (!body)
|
||||
body = factory_.newStatementList(tokenizer_->pos());
|
||||
TRY_VAR(body, factory_.newStatementList(tokenizer_->pos()));
|
||||
|
||||
MOZ_TRY_VAR(body, appendDirectivesToBody(body, directives));
|
||||
|
||||
|
@ -744,15 +744,6 @@ RecomputePosition(nsIFrame* aFrame)
|
||||
if (display->IsRelativelyPositionedStyle()) {
|
||||
// Move the frame
|
||||
if (display->mPosition == NS_STYLE_POSITION_STICKY) {
|
||||
if (display->IsInnerTableStyle()) {
|
||||
// We don't currently support sticky positioning of inner table
|
||||
// elements (bug 975644). Bail.
|
||||
//
|
||||
// When this is fixed, remove the null-check for the computed
|
||||
// offsets in nsTableRowFrame::ReflowChildren.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Update sticky positioning for an entire element at once, starting with
|
||||
// the first continuation or ib-split sibling.
|
||||
// It's rare that the frame we already have isn't already the first
|
||||
|
@ -383,7 +383,7 @@ load 629035-1.html
|
||||
load 629908-1.html
|
||||
load 635329.html
|
||||
load 636229-1.html
|
||||
== 640272.html 640272-ref.html
|
||||
skip-if(webrender&&winWidget) == 640272.html 640272-ref.html # bug 1426199 for webrender
|
||||
load 645193.html
|
||||
load 645572-1.html
|
||||
load 650475.xhtml
|
||||
|
@ -45,7 +45,7 @@ StickyScrollContainer::GetStickyScrollContainerForFrame(nsIFrame* aFrame)
|
||||
// <html style="position: fixed">
|
||||
return nullptr;
|
||||
}
|
||||
auto frame = static_cast<nsIFrame*>(do_QueryFrame(scrollFrame));
|
||||
nsIFrame* frame = do_QueryFrame(scrollFrame);
|
||||
StickyScrollContainer* s =
|
||||
frame->GetProperty(StickyScrollContainerProperty());
|
||||
if (!s) {
|
||||
@ -177,6 +177,15 @@ StickyScrollContainer::ComputeStickyLimits(nsIFrame* aFrame, nsRect* aStick,
|
||||
nsRect rect =
|
||||
nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame->GetParent());
|
||||
|
||||
// FIXME(bug 1421660): Table row groups aren't supposed to be containing
|
||||
// blocks, but we treat them as such (maybe it's the right thing to do!).
|
||||
// Anyway, not having this basically disables position: sticky on table cells,
|
||||
// which would be really unfortunate, and doesn't match what other browsers
|
||||
// do.
|
||||
if (cbFrame != scrolledFrame && cbFrame->IsTableRowGroupFrame()) {
|
||||
cbFrame = cbFrame->GetContainingBlock();
|
||||
}
|
||||
|
||||
// Containing block limits for the position of aFrame relative to its parent.
|
||||
// The margin box of the sticky element stays within the content box of the
|
||||
// contaning-block element.
|
||||
|
@ -690,15 +690,12 @@ nsFrame::Init(nsIContent* aContent,
|
||||
}
|
||||
if (disp->mPosition == NS_STYLE_POSITION_STICKY &&
|
||||
!aPrevInFlow &&
|
||||
!(mState & NS_FRAME_IS_NONDISPLAY) &&
|
||||
!disp->IsInnerTableStyle()) {
|
||||
!(mState & NS_FRAME_IS_NONDISPLAY)) {
|
||||
// Note that we only add first continuations, but we really only
|
||||
// want to add first continuation-or-ib-split-siblings. But since we
|
||||
// don't yet know if we're a later part of a block-in-inline split,
|
||||
// we'll just add later members of a block-in-inline split here, and
|
||||
// then StickyScrollContainer will remove them later.
|
||||
// We don't currently support relative positioning of inner table
|
||||
// elements (bug 35168), so exclude them from sticky positioning too.
|
||||
StickyScrollContainer* ssc =
|
||||
StickyScrollContainer::GetStickyScrollContainerForFrame(this);
|
||||
if (ssc) {
|
||||
@ -726,8 +723,7 @@ nsFrame::Init(nsIContent* aContent,
|
||||
"root frame should always be a container");
|
||||
}
|
||||
|
||||
if (PresShell()->AssumeAllFramesVisible() &&
|
||||
TrackingVisibility()) {
|
||||
if (PresShell()->AssumeAllFramesVisible() && TrackingVisibility()) {
|
||||
IncApproximateVisibleCount();
|
||||
}
|
||||
|
||||
@ -7556,6 +7552,9 @@ GetNearestBlockContainer(nsIFrame* frame)
|
||||
// Since the parent of such a block is either a normal block or
|
||||
// another such pseudo, this shouldn't cause anything bad to happen.
|
||||
// Also the anonymous blocks inside table cells are not containing blocks.
|
||||
//
|
||||
// If we ever start skipping table row groups from being containing blocks,
|
||||
// you need to remove the StickyScrollContainer hack referencing bug 1421660.
|
||||
while (frame->IsFrameOfType(nsIFrame::eLineParticipant) ||
|
||||
frame->IsBlockWrapper() ||
|
||||
// Table rows are not containing blocks either
|
||||
|
@ -749,10 +749,10 @@ nsCSSRendering::CreateWebRenderCommandsForBorder(nsDisplayItem* aItem,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (styleBorder->mBorderImageRepeatH == NS_STYLE_BORDER_IMAGE_REPEAT_ROUND ||
|
||||
styleBorder->mBorderImageRepeatH == NS_STYLE_BORDER_IMAGE_REPEAT_SPACE ||
|
||||
styleBorder->mBorderImageRepeatV == NS_STYLE_BORDER_IMAGE_REPEAT_ROUND ||
|
||||
styleBorder->mBorderImageRepeatV == NS_STYLE_BORDER_IMAGE_REPEAT_SPACE) {
|
||||
if (styleBorder->mBorderImageRepeatH == StyleBorderImageRepeat::Round ||
|
||||
styleBorder->mBorderImageRepeatH == StyleBorderImageRepeat::Space ||
|
||||
styleBorder->mBorderImageRepeatV == StyleBorderImageRepeat::Round ||
|
||||
styleBorder->mBorderImageRepeatV == StyleBorderImageRepeat::Space) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -3754,7 +3754,7 @@ nsCSSBorderImageRenderer::DrawBorderImage(nsPresContext* aPresContext,
|
||||
|
||||
for (int i = LEFT; i <= RIGHT; i++) {
|
||||
for (int j = TOP; j <= BOTTOM; j++) {
|
||||
uint8_t fillStyleH, fillStyleV;
|
||||
StyleBorderImageRepeat fillStyleH, fillStyleV;
|
||||
nsSize unitSize;
|
||||
|
||||
if (i == MIDDLE && j == MIDDLE) {
|
||||
@ -3808,7 +3808,7 @@ nsCSSBorderImageRenderer::DrawBorderImage(nsPresContext* aPresContext,
|
||||
unitSize.width = sliceWidth[i] * factor;
|
||||
unitSize.height = borderHeight[j];
|
||||
fillStyleH = mRepeatModeHorizontal;
|
||||
fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
|
||||
fillStyleV = StyleBorderImageRepeat::Stretch;
|
||||
|
||||
} else if (j == MIDDLE) { // left, right
|
||||
gfxFloat factor;
|
||||
@ -3819,15 +3819,15 @@ nsCSSBorderImageRenderer::DrawBorderImage(nsPresContext* aPresContext,
|
||||
|
||||
unitSize.width = borderWidth[i];
|
||||
unitSize.height = sliceHeight[j] * factor;
|
||||
fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
|
||||
fillStyleH = StyleBorderImageRepeat::Stretch;
|
||||
fillStyleV = mRepeatModeVertical;
|
||||
|
||||
} else {
|
||||
// Corners are always stretched to fit the corner.
|
||||
unitSize.width = borderWidth[i];
|
||||
unitSize.height = borderHeight[j];
|
||||
fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
|
||||
fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
|
||||
fillStyleH = StyleBorderImageRepeat::Stretch;
|
||||
fillStyleV = StyleBorderImageRepeat::Stretch;
|
||||
}
|
||||
|
||||
nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]);
|
||||
|
@ -336,8 +336,8 @@ private:
|
||||
nsMargin mImageOutset;
|
||||
nsRect mArea;
|
||||
nsRect mClip;
|
||||
uint8_t mRepeatModeHorizontal;
|
||||
uint8_t mRepeatModeVertical;
|
||||
mozilla::StyleBorderImageRepeat mRepeatModeHorizontal;
|
||||
mozilla::StyleBorderImageRepeat mRepeatModeVertical;
|
||||
uint8_t mFill;
|
||||
|
||||
friend class nsDisplayBorder;
|
||||
|
@ -745,35 +745,35 @@ nsImageRenderer::BuildWebRenderDisplayItemsForLayer(nsPresContext* aPresCo
|
||||
* tile used to fill the dest rect.
|
||||
* aFill The destination rect to be filled
|
||||
* aHFill and aVFill are the repeat patterns for the component -
|
||||
* NS_STYLE_BORDER_IMAGE_REPEAT_* - i.e., how a tiling unit is used to fill aFill
|
||||
* StyleBorderImageRepeat - i.e., how a tiling unit is used to fill aFill
|
||||
* aUnitSize The size of the source rect in dest coords.
|
||||
*/
|
||||
static nsRect
|
||||
ComputeTile(nsRect& aFill,
|
||||
uint8_t aHFill,
|
||||
uint8_t aVFill,
|
||||
StyleBorderImageRepeat aHFill,
|
||||
StyleBorderImageRepeat aVFill,
|
||||
const nsSize& aUnitSize,
|
||||
nsSize& aRepeatSize)
|
||||
{
|
||||
nsRect tile;
|
||||
switch (aHFill) {
|
||||
case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
|
||||
case StyleBorderImageRepeat::Stretch:
|
||||
tile.x = aFill.x;
|
||||
tile.width = aFill.width;
|
||||
aRepeatSize.width = tile.width;
|
||||
break;
|
||||
case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
|
||||
case StyleBorderImageRepeat::Repeat:
|
||||
tile.x = aFill.x + aFill.width/2 - aUnitSize.width/2;
|
||||
tile.width = aUnitSize.width;
|
||||
aRepeatSize.width = tile.width;
|
||||
break;
|
||||
case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
|
||||
case StyleBorderImageRepeat::Round:
|
||||
tile.x = aFill.x;
|
||||
tile.width = nsCSSRendering::ComputeRoundedSize(aUnitSize.width,
|
||||
aFill.width);
|
||||
aRepeatSize.width = tile.width;
|
||||
break;
|
||||
case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
|
||||
case StyleBorderImageRepeat::Space:
|
||||
{
|
||||
nscoord space;
|
||||
aRepeatSize.width =
|
||||
@ -790,23 +790,23 @@ ComputeTile(nsRect& aFill,
|
||||
}
|
||||
|
||||
switch (aVFill) {
|
||||
case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
|
||||
case StyleBorderImageRepeat::Stretch:
|
||||
tile.y = aFill.y;
|
||||
tile.height = aFill.height;
|
||||
aRepeatSize.height = tile.height;
|
||||
break;
|
||||
case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
|
||||
case StyleBorderImageRepeat::Repeat:
|
||||
tile.y = aFill.y + aFill.height/2 - aUnitSize.height/2;
|
||||
tile.height = aUnitSize.height;
|
||||
aRepeatSize.height = tile.height;
|
||||
break;
|
||||
case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
|
||||
case StyleBorderImageRepeat::Round:
|
||||
tile.y = aFill.y;
|
||||
tile.height = nsCSSRendering::ComputeRoundedSize(aUnitSize.height,
|
||||
aFill.height);
|
||||
aRepeatSize.height = tile.height;
|
||||
break;
|
||||
case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
|
||||
case StyleBorderImageRepeat::Space:
|
||||
{
|
||||
nscoord space;
|
||||
aRepeatSize.height =
|
||||
@ -832,14 +832,14 @@ ComputeTile(nsRect& aFill,
|
||||
*/
|
||||
static bool
|
||||
RequiresScaling(const nsRect& aFill,
|
||||
uint8_t aHFill,
|
||||
uint8_t aVFill,
|
||||
StyleBorderImageRepeat aHFill,
|
||||
StyleBorderImageRepeat aVFill,
|
||||
const nsSize& aUnitSize)
|
||||
{
|
||||
// If we have no tiling in either direction, we can skip the intermediate
|
||||
// scaling step.
|
||||
return (aHFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH ||
|
||||
aVFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH) &&
|
||||
return (aHFill != StyleBorderImageRepeat::Stretch ||
|
||||
aVFill != StyleBorderImageRepeat::Stretch) &&
|
||||
(aUnitSize.width != aFill.width ||
|
||||
aUnitSize.height != aFill.height);
|
||||
}
|
||||
@ -850,8 +850,8 @@ nsImageRenderer::DrawBorderImageComponent(nsPresContext* aPresContext,
|
||||
const nsRect& aDirtyRect,
|
||||
const nsRect& aFill,
|
||||
const CSSIntRect& aSrc,
|
||||
uint8_t aHFill,
|
||||
uint8_t aVFill,
|
||||
StyleBorderImageRepeat aHFill,
|
||||
StyleBorderImageRepeat aVFill,
|
||||
const nsSize& aUnitSize,
|
||||
uint8_t aIndex,
|
||||
const Maybe<nsSize>& aSVGViewportSize,
|
||||
|
@ -247,8 +247,8 @@ public:
|
||||
const nsRect& aDirtyRect,
|
||||
const nsRect& aFill,
|
||||
const mozilla::CSSIntRect& aSrc,
|
||||
uint8_t aHFill,
|
||||
uint8_t aVFill,
|
||||
mozilla::StyleBorderImageRepeat aHFill,
|
||||
mozilla::StyleBorderImageRepeat aVFill,
|
||||
const nsSize& aUnitSize,
|
||||
uint8_t aIndex,
|
||||
const mozilla::Maybe<nsSize>& aSVGViewportSize,
|
||||
|
@ -1,26 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<html>
|
||||
<head>
|
||||
<link rel="author" title="Corey Ford" href="mailto:corey@coreyford.name">
|
||||
</head>
|
||||
<body>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>a</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr>
|
||||
<td>b</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>c</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>d</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user