Merge mozilla-central to inbound r=merge a=merge on a CLOSED TREE

This commit is contained in:
arthur.iakab 2017-12-22 00:28:49 +02:00
commit 042d2e8d74
329 changed files with 2812 additions and 2501 deletions

View File

@ -37,7 +37,6 @@ module.exports = {
"dom/media/**",
"extensions/pref/**",
"mobile/android/**",
"security/**",
"testing/**",
"tools/profiler/**",
],

View File

@ -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");
}
});

View File

@ -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

View File

@ -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.

View File

@ -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;

View File

@ -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");

View File

@ -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]

View 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();
}

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
);

View File

@ -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});

View File

@ -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) {

View File

@ -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");

View File

@ -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),

View File

@ -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.");
},
);

View File

@ -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)`
);
}
}
}
}

View File

@ -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.
*/

View File

@ -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();
}
/**

View File

@ -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);
});
}

View File

@ -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;

View File

@ -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");

View File

@ -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)");
/**

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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"));

View File

@ -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

View File

@ -24,7 +24,7 @@ DevToolsModules(
'RequestListColumnLatency.js',
'RequestListColumnMethod.js',
'RequestListColumnProtocol.js',
'RequestListColumnRemoteIp.js',
'RequestListColumnRemoteIP.js',
'RequestListColumnResponseHeader.js',
'RequestListColumnResponseTime.js',
'RequestListColumnScheme.js',

View File

@ -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;

View File

@ -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);
});
}

View File

@ -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);

View File

@ -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]

View File

@ -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();
});

View File

@ -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();
});

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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)
{

View File

@ -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.
*/

View File

@ -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());
}

View File

@ -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));
}

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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();
};

View File

@ -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

View File

@ -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>

View File

@ -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;
};

View File

@ -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 */);
}

View File

@ -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();

View File

@ -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();

View File

@ -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)
{

View File

@ -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.

View File

@ -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 &&

View File

@ -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.

View File

@ -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(),

View File

@ -245,7 +245,7 @@ GPUProcessHost::DestroyProcess()
}
MessageLoop::current()->
PostTask(NewRunnableFunction(DelayedDeleteSubprocess, this));
PostTask(NewRunnableFunction("DestroyProcessRunnable", DelayedDeleteSubprocess, this));
}
} // namespace gfx

View File

@ -196,7 +196,9 @@ PaintThread::Shutdown()
return;
}
sThread->Dispatch(NewRunnableFunction(DestroyPaintThread, Move(pt)));
sThread->Dispatch(NewRunnableFunction("DestroyPaintThreadRunnable",
DestroyPaintThread,
Move(pt)));
sThread->Shutdown();
sThread = nullptr;
}

View File

@ -2384,7 +2384,8 @@ void
APZCTreeManager::SetLongTapEnabled(bool aLongTapEnabled)
{
APZThreadUtils::RunOnControllerThread(
NewRunnableFunction(GestureEventListener::SetLongTapEnabled, aLongTapEnabled));
NewRunnableFunction("SetLongTapEnabledRunnable",
GestureEventListener::SetLongTapEnabled, aLongTapEnabled));
}
RefPtr<HitTestingTreeNode>

View File

@ -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));
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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":

View File

@ -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

View File

@ -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();

View File

@ -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,

View File

@ -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*

View File

@ -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();
}

View File

@ -234,6 +234,7 @@ RenderThread::UpdateAndRender(wr::WindowId aWindowId)
auto epochs = renderer->FlushRenderedEpochs();
layers::CompositorThreadHolder::Loop()->PostTask(NewRunnableFunction(
"NotifyDidRenderRunnable",
&NotifyDidRender,
renderer->GetCompositorBridge(),
epochs,

View File

@ -227,6 +227,7 @@ void
RendererOGL::NotifyWebRenderError(WebRenderError aError)
{
layers::CompositorThreadHolder::Loop()->PostTask(NewRunnableFunction(
"DoNotifyWebRenderErrorRunnable",
&DoNotifyWebRenderError,
mBridge,
aError

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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());
}

View File

@ -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());
}

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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

View File

@ -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();

View File

@ -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'.)

View File

@ -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();
}

View File

@ -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));

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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;
}

View File

@ -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]);

View File

@ -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;

View File

@ -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,

View File

@ -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,

View File

@ -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