зеркало из https://github.com/mozilla/gecko-dev.git
Merge autoland to mozilla-central a=merge
This commit is contained in:
Коммит
e9d995e793
|
@ -189,3 +189,7 @@ config/external/icu4x
|
|||
|
||||
# Ignore the index files generated by clangd.
|
||||
.cache/clangd/index/
|
||||
|
||||
# Ignore Storybook generated files
|
||||
browser/components/storybook/node_modules/
|
||||
browser/components/storybook/storybook-static/
|
||||
|
|
|
@ -157,7 +157,6 @@ _OPT\.OBJ/
|
|||
^tools/browsertime/node_modules/
|
||||
^tools/lint/eslint/eslint-plugin-mozilla/node_modules/
|
||||
^browser/components/newtab/node_modules/
|
||||
^browser/components/storybook/node_modules/
|
||||
|
||||
# Ignore talos virtualenv and tp5n files.
|
||||
# The tp5n set is supposed to be decompressed at
|
||||
|
@ -243,3 +242,7 @@ toolkit/components/certviewer/content/package-lock.json
|
|||
|
||||
# Ignore mypy files
|
||||
\.mypy_cache/
|
||||
|
||||
# Ignore Storybook generated files
|
||||
^browser/components/storybook/node_modules/
|
||||
^browser/components/storybook/storybook-static/
|
||||
|
|
|
@ -2,6 +2,4 @@
|
|||
- 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/. -->
|
||||
|
||||
<!ENTITY brandShorterName "Firefox">
|
||||
<!ENTITY brandShortName "Firefox Developer Edition">
|
||||
<!ENTITY brandFullName "Firefox Developer Edition">
|
||||
|
|
|
@ -2,6 +2,4 @@
|
|||
- 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/. -->
|
||||
|
||||
<!ENTITY brandShorterName "Nightly">
|
||||
<!ENTITY brandShortName "Nightly">
|
||||
<!ENTITY brandFullName "Firefox Nightly">
|
||||
|
|
|
@ -2,6 +2,4 @@
|
|||
- 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/. -->
|
||||
|
||||
<!ENTITY brandShorterName "Firefox">
|
||||
<!ENTITY brandShortName "Firefox">
|
||||
<!ENTITY brandFullName "Mozilla Firefox">
|
||||
|
|
|
@ -2,6 +2,4 @@
|
|||
- 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/. -->
|
||||
|
||||
<!ENTITY brandShorterName "Nightly">
|
||||
<!ENTITY brandShortName "Nightly">
|
||||
<!ENTITY brandFullName "Nightly">
|
||||
|
|
|
@ -44,10 +44,12 @@ class ColorwaySelector extends HTMLFieldSetElement {
|
|||
for (let input of this.children) {
|
||||
if (input.value == this.activeTheme.id) {
|
||||
input.classList.add("active");
|
||||
input.setAttribute("aria-current", true);
|
||||
this.updateName(this.selectedTheme.name);
|
||||
this.updateDescription(input.value);
|
||||
} else {
|
||||
input.classList.remove("active");
|
||||
input.setAttribute("aria-current", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +59,7 @@ class ColorwaySelector extends HTMLFieldSetElement {
|
|||
input.type = "radio";
|
||||
input.name = "colorway";
|
||||
input.value = theme.id;
|
||||
input.setAttribute("title", theme.name);
|
||||
input.style.setProperty("--colorway-icon", `url(${theme.iconURL})`);
|
||||
input.onclick = () => {
|
||||
this.selectedTheme = theme;
|
||||
|
|
|
@ -40,6 +40,24 @@ body > header {
|
|||
padding: .2em 1em;
|
||||
}
|
||||
|
||||
#use-fx-home-controls:not(.success) > .success-prompt,
|
||||
#use-fx-home-controls.success > .reset-prompt {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#use-fx-home-controls > .success-prompt::before {
|
||||
display: inline-block;
|
||||
content: "";
|
||||
background: var(--green-50) url('chrome://global/skin/icons/check.svg') center center no-repeat;
|
||||
-moz-context-properties: fill;
|
||||
fill: white;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 15px;
|
||||
vertical-align: middle;
|
||||
margin-inline-end: 0.5em;
|
||||
}
|
||||
|
||||
body > section {
|
||||
grid-area: main;
|
||||
}
|
||||
|
|
|
@ -4,3 +4,7 @@
|
|||
|
||||
colorway-collection-life-in-color = Life In Color
|
||||
colorway-collection-true-colors = True Colors
|
||||
colorway-fx-home-link = Use { -brand-product-name } Home for colorful new tabs
|
||||
colorway-fx-home-link-success = { -brand-product-name } Home is now your home page
|
||||
colorway-fx-home-apply-button = Apply
|
||||
colorway-fx-home-undo-button = Undo
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
href="chrome://global/skin/in-content/common.css">
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="chrome://browser/content/colorwaycloset.css">
|
||||
<link rel="localization" href="browser/colorways.ftl"/>
|
||||
<link rel="localization" href="branding/brand.ftl">
|
||||
<link rel="localization" href="preview/colorwaycloset.ftl">
|
||||
<script src="chrome://browser/content/colorwaycloset.js" defer="async"></script>
|
||||
<script type="module" src="chrome://browser/content/ColorwayClosetSelector.js"></script>
|
||||
</head>
|
||||
|
@ -30,6 +31,16 @@
|
|||
<p id="colorway-description"></p>
|
||||
</div>
|
||||
</section>
|
||||
<section id="use-fx-home-controls" hidden>
|
||||
<div class="reset-prompt">
|
||||
<span data-l10n-id="colorway-fx-home-link"></span>
|
||||
<button data-l10n-id="colorway-fx-home-apply-button"></button>
|
||||
</div>
|
||||
<div class="success-prompt">
|
||||
<span data-l10n-id="colorway-fx-home-link-success"></span>
|
||||
<button data-l10n-id="colorway-fx-home-undo-button"></button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -5,19 +5,40 @@
|
|||
const { BuiltInThemes } = ChromeUtils.import(
|
||||
"resource:///modules/BuiltInThemes.jsm"
|
||||
);
|
||||
const { HomePage } = ChromeUtils.import("resource:///modules/HomePage.jsm");
|
||||
|
||||
function showUseFXHomeControls(fluentStrings) {
|
||||
let homeState;
|
||||
const useFXHomeControls = document.getElementById("use-fx-home-controls");
|
||||
useFXHomeControls.hidden = HomePage.isDefault;
|
||||
if (!HomePage.isDefault) {
|
||||
useFXHomeControls
|
||||
.querySelector(".reset-prompt > button")
|
||||
.addEventListener("click", () => {
|
||||
homeState = HomePage.get();
|
||||
HomePage.reset();
|
||||
useFXHomeControls.classList.add("success");
|
||||
});
|
||||
useFXHomeControls
|
||||
.querySelector(".success-prompt > button")
|
||||
.addEventListener("click", () => {
|
||||
HomePage.set(homeState);
|
||||
useFXHomeControls.classList.remove("success");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const collection = BuiltInThemes.findActiveColorwayCollection();
|
||||
if (collection) {
|
||||
const { expiry, l10nId } = collection;
|
||||
const fluentStrings = new Localization(["preview/colorwaycloset.ftl"], true);
|
||||
const formatter = new Intl.DateTimeFormat("default", {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
document.getElementById(
|
||||
"collection-title"
|
||||
).innerText = fluentStrings.formatValueSync(l10nId);
|
||||
const collectionTitle = document.getElementById("collection-title");
|
||||
document.l10n.setAttributes(collectionTitle, l10nId);
|
||||
document.querySelector(
|
||||
"#collection-expiry-date > span"
|
||||
).innerText = formatter.format(expiry);
|
||||
showUseFXHomeControls();
|
||||
}
|
||||
|
|
|
@ -31,6 +31,18 @@ add_task(async function about_colorwaycloset_smoke_test() {
|
|||
document.getElementById("colorway-description"),
|
||||
"colorway description exists"
|
||||
);
|
||||
|
||||
const useFXHomeControls = document.getElementById("use-fx-home-controls");
|
||||
ok(useFXHomeControls, "firefox home controls exists");
|
||||
useFXHomeControls.toggleAttribute("hidden", false);
|
||||
ok(
|
||||
document.querySelector("#use-fx-home-controls > .reset-prompt"),
|
||||
"firefox home controls reset prompt exists"
|
||||
);
|
||||
ok(
|
||||
document.querySelector("#use-fx-home-controls > .success-prompt"),
|
||||
"firefox home controls reset prompt exists"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -339,9 +339,10 @@ this.chrome_settings_overrides = class extends ExtensionAPI {
|
|||
extension.startupReason
|
||||
);
|
||||
}
|
||||
// Ensure the item is disabled. If addSetting was called above,
|
||||
// Item may be null, and enabled may be undefined.
|
||||
if (disable && item?.enabled !== false) {
|
||||
|
||||
// Ensure the item is disabled (either if exists and is not default or if it does not
|
||||
// exist yet).
|
||||
if (disable) {
|
||||
item = await ExtensionSettingsStore.disable(
|
||||
extension.id,
|
||||
DEFAULT_SEARCH_STORE_TYPE,
|
||||
|
@ -466,6 +467,37 @@ this.chrome_settings_overrides = class extends ExtensionAPI {
|
|||
DEFAULT_SEARCH_STORE_TYPE,
|
||||
DEFAULT_SEARCH_SETTING_NAME
|
||||
);
|
||||
|
||||
// Check for an inconsistency between the value returned by getLevelOfcontrol
|
||||
// and the current engine actually set.
|
||||
if (
|
||||
control === "controlled_by_this_extension" &&
|
||||
Services.search.defaultEngine.name !== engineName
|
||||
) {
|
||||
// Check for and fix any inconsistency between the extensions settings storage
|
||||
// and the current engine actually set. If settings claims the extension is default
|
||||
// but the search service claims otherwise, select what the search service claims
|
||||
// (See Bug 1767550).
|
||||
const allSettings = ExtensionSettingsStore.getAllSettings(
|
||||
DEFAULT_SEARCH_STORE_TYPE,
|
||||
DEFAULT_SEARCH_SETTING_NAME
|
||||
);
|
||||
for (const setting of allSettings) {
|
||||
if (setting.value !== Services.search.defaultEngine.name) {
|
||||
await ExtensionSettingsStore.disable(
|
||||
setting.id,
|
||||
DEFAULT_SEARCH_STORE_TYPE,
|
||||
DEFAULT_SEARCH_SETTING_NAME
|
||||
);
|
||||
}
|
||||
}
|
||||
control = await ExtensionSettingsStore.getLevelOfControl(
|
||||
extension.id,
|
||||
DEFAULT_SEARCH_STORE_TYPE,
|
||||
DEFAULT_SEARCH_SETTING_NAME
|
||||
);
|
||||
}
|
||||
|
||||
if (control === "controlled_by_this_extension") {
|
||||
await Services.search.setDefault(
|
||||
Services.search.getEngineByName(engineName)
|
||||
|
|
|
@ -337,6 +337,47 @@ add_task(async function test_overrides_update_homepage_change() {
|
|||
await extension.unload();
|
||||
});
|
||||
|
||||
async function withHandlingDefaultSearchPrompt({ extensionId, respond }, cb) {
|
||||
const promptResponseHandled = TestUtils.topicObserved(
|
||||
"webextension-defaultsearch-prompt-response"
|
||||
);
|
||||
const prompted = TestUtils.topicObserved(
|
||||
"webextension-defaultsearch-prompt",
|
||||
(subject, message) => {
|
||||
if (subject.wrappedJSObject.id == extensionId) {
|
||||
return subject.wrappedJSObject.respond(respond);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
await Promise.all([cb(), prompted, promptResponseHandled]);
|
||||
}
|
||||
|
||||
async function assertUpdateDoNotPrompt(extension, updateExtensionInfo) {
|
||||
let deferredUpgradePrompt = topicObservable(
|
||||
"webextension-defaultsearch-prompt",
|
||||
(subject, message) => {
|
||||
if (subject.wrappedJSObject.id == extension.id) {
|
||||
ok(false, "should not prompt on update");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
await Promise.race([
|
||||
extension.upgrade(updateExtensionInfo),
|
||||
deferredUpgradePrompt.promise,
|
||||
]);
|
||||
deferredUpgradePrompt.resolve();
|
||||
|
||||
await AddonTestUtils.waitForSearchProviderStartup(extension);
|
||||
|
||||
equal(
|
||||
extension.version,
|
||||
updateExtensionInfo.manifest.version,
|
||||
"The updated addon has the expected version."
|
||||
);
|
||||
}
|
||||
|
||||
add_task(async function test_default_search_prompts() {
|
||||
/* This tests the scenario where an addon did not gain
|
||||
* default search during install, and later upgrades.
|
||||
|
@ -368,22 +409,15 @@ add_task(async function test_default_search_prompts() {
|
|||
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionInfo);
|
||||
|
||||
// Mock a response from the default search prompt where we
|
||||
// say no to setting this as the default when installing.
|
||||
let prompted = TestUtils.topicObserved(
|
||||
"webextension-defaultsearch-prompt",
|
||||
(subject, message) => {
|
||||
if (subject.wrappedJSObject.id == extension.id) {
|
||||
return subject.wrappedJSObject.respond(false);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let defaultEngineName = (await Services.search.getDefault()).name;
|
||||
ok(defaultEngineName !== "Example", "Search is not Example.");
|
||||
|
||||
await extension.startup();
|
||||
await prompted;
|
||||
// Mock a response from the default search prompt where we
|
||||
// say no to setting this as the default when installing.
|
||||
await withHandlingDefaultSearchPrompt(
|
||||
{ extensionId: EXTENSION_ID, respond: false },
|
||||
() => extension.startup()
|
||||
);
|
||||
|
||||
equal(
|
||||
extension.version,
|
||||
|
@ -396,70 +430,354 @@ add_task(async function test_default_search_prompts() {
|
|||
"Default engine is the default after startup."
|
||||
);
|
||||
|
||||
extensionInfo.manifest = {
|
||||
version: "2.0",
|
||||
applications: {
|
||||
gecko: {
|
||||
id: EXTENSION_ID,
|
||||
},
|
||||
},
|
||||
chrome_settings_overrides: {
|
||||
search_provider: {
|
||||
name: "Example",
|
||||
search_url: "https://example.com/?q={searchTerms}",
|
||||
is_default: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let deferredUpgradePrompt = topicObservable(
|
||||
"webextension-defaultsearch-prompt",
|
||||
(subject, message) => {
|
||||
if (subject.wrappedJSObject.id == extension.id) {
|
||||
ok(false, "should not prompt on update");
|
||||
}
|
||||
}
|
||||
info(
|
||||
"Verify that updating the extension does not prompt and does not take over the default engine"
|
||||
);
|
||||
|
||||
await Promise.race([
|
||||
extension.upgrade(extensionInfo),
|
||||
deferredUpgradePrompt.promise,
|
||||
]);
|
||||
deferredUpgradePrompt.resolve();
|
||||
|
||||
await AddonTestUtils.waitForSearchProviderStartup(extension);
|
||||
|
||||
equal(
|
||||
extension.version,
|
||||
"2.0",
|
||||
"The updated addon has the expected version."
|
||||
);
|
||||
// An upgraded extension does not become the default engine.
|
||||
extensionInfo.manifest.version = "2.0";
|
||||
await assertUpdateDoNotPrompt(extension, extensionInfo);
|
||||
equal(
|
||||
(await Services.search.getDefault()).name,
|
||||
defaultEngineName,
|
||||
"Default engine is still the default after startup."
|
||||
"Default engine is still the default after update."
|
||||
);
|
||||
|
||||
info("Verify that disable/enable the extension does prompt the user");
|
||||
|
||||
let addon = await AddonManager.getAddonByID(EXTENSION_ID);
|
||||
await addon.disable();
|
||||
|
||||
prompted = TestUtils.topicObserved(
|
||||
"webextension-defaultsearch-prompt",
|
||||
(subject, message) => {
|
||||
if (subject.wrappedJSObject.id == extension.id) {
|
||||
return subject.wrappedJSObject.respond(false);
|
||||
}
|
||||
await withHandlingDefaultSearchPrompt(
|
||||
{ extensionId: EXTENSION_ID, respond: false },
|
||||
async () => {
|
||||
await addon.disable();
|
||||
await addon.enable();
|
||||
}
|
||||
);
|
||||
await Promise.all([addon.enable(), prompted]);
|
||||
|
||||
// we still said no.
|
||||
equal(
|
||||
(await Services.search.getDefault()).name,
|
||||
defaultEngineName,
|
||||
"Default engine is the default after startup."
|
||||
"Default engine is the default after being disabling/enabling."
|
||||
);
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
async function test_default_search_on_updating_addons_installed_before_bug1757760({
|
||||
builtinAsInitialDefault,
|
||||
}) {
|
||||
/* This tests covers a scenario similar to the previous test but with an extension-settings.json file
|
||||
content like the one that would be available in the profile if the add-on was installed on firefox
|
||||
versions that didn't include the changes from Bug 1757760 (See Bug 1767550).
|
||||
*/
|
||||
|
||||
const EXTENSION_ID = `test_old_addon@tests.mozilla.org`;
|
||||
const EXTENSION_ID2 = `test_old_addon2@tests.mozilla.org`;
|
||||
|
||||
const extensionInfo = {
|
||||
useAddonManager: "permanent",
|
||||
manifest: {
|
||||
version: "1.1",
|
||||
browser_specific_settings: {
|
||||
gecko: {
|
||||
id: EXTENSION_ID,
|
||||
},
|
||||
},
|
||||
chrome_settings_overrides: {
|
||||
search_provider: {
|
||||
name: "Test SearchEngine",
|
||||
search_url: "https://example.com/?q={searchTerms}",
|
||||
is_default: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const extensionInfo2 = {
|
||||
useAddonManager: "permanent",
|
||||
manifest: {
|
||||
version: "1.2",
|
||||
browser_specific_settings: {
|
||||
gecko: {
|
||||
id: EXTENSION_ID2,
|
||||
},
|
||||
},
|
||||
chrome_settings_overrides: {
|
||||
search_provider: {
|
||||
name: "Test SearchEngine2",
|
||||
search_url: "https://example.com/?q={searchTerms}",
|
||||
is_default: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const { ExtensionSettingsStore } = ChromeUtils.import(
|
||||
"resource://gre/modules/ExtensionSettingsStore.jsm"
|
||||
);
|
||||
|
||||
async function assertExtensionSettingsStore(
|
||||
extensionInfo,
|
||||
expectedLevelOfControl
|
||||
) {
|
||||
const { id } = extensionInfo.manifest.browser_specific_settings.gecko;
|
||||
info(`Asserting ExtensionSettingsStore for ${id}`);
|
||||
const item = ExtensionSettingsStore.getSetting(
|
||||
"default_search",
|
||||
"defaultSearch",
|
||||
id
|
||||
);
|
||||
equal(
|
||||
item.value,
|
||||
extensionInfo.manifest.chrome_settings_overrides.search_provider.name,
|
||||
"Got the expected item returned by ExtensionSettingsStore.getSetting"
|
||||
);
|
||||
const control = await ExtensionSettingsStore.getLevelOfControl(
|
||||
id,
|
||||
"default_search",
|
||||
"defaultSearch"
|
||||
);
|
||||
equal(
|
||||
control,
|
||||
expectedLevelOfControl,
|
||||
`Got expected levelOfControl for ${id}`
|
||||
);
|
||||
}
|
||||
|
||||
info("Install test extensions without opt-in to the related search engines");
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionInfo);
|
||||
let extension2 = ExtensionTestUtils.loadExtension(extensionInfo2);
|
||||
|
||||
// Mock a response from the default search prompt where we
|
||||
// say no to setting this as the default when installing.
|
||||
await withHandlingDefaultSearchPrompt(
|
||||
{ extensionId: EXTENSION_ID, respond: false },
|
||||
() => extension.startup()
|
||||
);
|
||||
|
||||
equal(
|
||||
extension.version,
|
||||
"1.1",
|
||||
"first installed addon has the expected version."
|
||||
);
|
||||
|
||||
// Mock a response from the default search prompt where we
|
||||
// say no to setting this as the default when installing.
|
||||
await withHandlingDefaultSearchPrompt(
|
||||
{ extensionId: EXTENSION_ID2, respond: false },
|
||||
() => extension2.startup()
|
||||
);
|
||||
|
||||
equal(
|
||||
extension2.version,
|
||||
"1.2",
|
||||
"second installed addon has the expected version."
|
||||
);
|
||||
|
||||
info("Setup preconditions (set the initial default search engine)");
|
||||
|
||||
// Sanity check to be sure the initial engine expected as precondition
|
||||
// for the scenario covered by the current test case.
|
||||
let initialEngine;
|
||||
if (builtinAsInitialDefault) {
|
||||
initialEngine = Services.search.originalDefaultEngine;
|
||||
} else {
|
||||
initialEngine = Services.search.getEngineByName(
|
||||
extensionInfo.manifest.chrome_settings_overrides.search_provider.name
|
||||
);
|
||||
}
|
||||
await Services.search.setDefault(initialEngine);
|
||||
|
||||
let defaultEngineName = (await Services.search.getDefault()).name;
|
||||
Assert.equal(
|
||||
defaultEngineName,
|
||||
initialEngine.name,
|
||||
`initial default search engine expected to be ${
|
||||
builtinAsInitialDefault ? "app-provided" : EXTENSION_ID
|
||||
}`
|
||||
);
|
||||
Assert.notEqual(
|
||||
defaultEngineName,
|
||||
extensionInfo2.manifest.chrome_settings_overrides.search_provider.name,
|
||||
"initial default search engine name should not be the same as the second extension search_provider"
|
||||
);
|
||||
|
||||
equal(
|
||||
(await Services.search.getDefault()).name,
|
||||
initialEngine.name,
|
||||
`Default engine should still be set to the ${
|
||||
builtinAsInitialDefault ? "app-provided" : EXTENSION_ID
|
||||
}.`
|
||||
);
|
||||
|
||||
// Mock an update from settings stored as in an older Firefox version where Bug 1757760 was not landed yet.
|
||||
info(
|
||||
"Setup preconditions (inject mock extension-settings.json data and assert on the expected setting and levelOfControl)"
|
||||
);
|
||||
|
||||
let addon = await AddonManager.getAddonByID(EXTENSION_ID);
|
||||
let addon2 = await AddonManager.getAddonByID(EXTENSION_ID2);
|
||||
|
||||
const extensionSettingsData = {
|
||||
version: 2,
|
||||
url_overrides: {},
|
||||
prefs: {},
|
||||
homepageNotification: {},
|
||||
tabHideNotification: {},
|
||||
default_search: {
|
||||
defaultSearch: {
|
||||
initialValue: Services.search.originalDefaultEngine.name,
|
||||
precedenceList: [
|
||||
{
|
||||
id: EXTENSION_ID2,
|
||||
// The install dates are used in ExtensionSettingsStore.getLevelOfControl
|
||||
// and to recreate the expected preconditions the last extension installed
|
||||
// should have a installDate timestamp > then the first one.
|
||||
installDate: addon2.installDate.getTime() + 1000,
|
||||
value:
|
||||
extensionInfo2.manifest.chrome_settings_overrides.search_provider
|
||||
.name,
|
||||
// When an addon with a default search engine override is installed in Firefox versions
|
||||
// without the changes landed from Bug 1757760, `enabled` will be set to true in all cases
|
||||
// (Prompt never answered, or when No or Yes is selected by the user).
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: EXTENSION_ID,
|
||||
installDate: addon.installDate.getTime(),
|
||||
value:
|
||||
extensionInfo.manifest.chrome_settings_overrides.search_provider
|
||||
.name,
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
newTabNotification: {},
|
||||
commands: {},
|
||||
};
|
||||
|
||||
const file = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
||||
file.append("extension-settings.json");
|
||||
|
||||
info(`writing mock settings data into ${file.path}`);
|
||||
await IOUtils.writeJSON(file.path, extensionSettingsData);
|
||||
await ExtensionSettingsStore._reloadFile(false);
|
||||
|
||||
equal(
|
||||
(await Services.search.getDefault()).name,
|
||||
initialEngine.name,
|
||||
"Default engine is still set to the initial one."
|
||||
);
|
||||
|
||||
// The following assertions verify that the migration applied from ExtensionSettingsStore
|
||||
// fixed the inconsistent state and kept the search engine unchanged.
|
||||
//
|
||||
// - With the fixed settings we expect both to be resolved to "controllable_by_this_extension".
|
||||
// - Without the fix applied during the migration the levelOfControl resolved would be:
|
||||
// - for the last installed: "controlled_by_this_extension"
|
||||
// - for the first installed: "controlled_by_other_extensions"
|
||||
await assertExtensionSettingsStore(
|
||||
extensionInfo2,
|
||||
"controlled_by_this_extension"
|
||||
);
|
||||
await assertExtensionSettingsStore(
|
||||
extensionInfo,
|
||||
"controlled_by_other_extensions"
|
||||
);
|
||||
|
||||
info(
|
||||
"Verify that updating the extension does not prompt and does not take over the default engine"
|
||||
);
|
||||
|
||||
extensionInfo2.manifest.version = "2.2";
|
||||
await assertUpdateDoNotPrompt(extension2, extensionInfo2);
|
||||
|
||||
extensionInfo.manifest.version = "2.1";
|
||||
await assertUpdateDoNotPrompt(extension, extensionInfo);
|
||||
|
||||
equal(
|
||||
(await Services.search.getDefault()).name,
|
||||
initialEngine.name,
|
||||
"Default engine is still the same after updating both the test extensions."
|
||||
);
|
||||
|
||||
// After both the extensions have been updated and their inconsistent state
|
||||
// updated internally, both extensions should have levelOfControl "controllable_*".
|
||||
await assertExtensionSettingsStore(
|
||||
extensionInfo2,
|
||||
"controllable_by_this_extension"
|
||||
);
|
||||
await assertExtensionSettingsStore(
|
||||
extensionInfo,
|
||||
// We expect levelOfControl to be controlled_by_this_extension if the test case
|
||||
// is expecting the third party extension to stay set as default.
|
||||
builtinAsInitialDefault
|
||||
? "controllable_by_this_extension"
|
||||
: "controlled_by_this_extension"
|
||||
);
|
||||
|
||||
info("Verify that disable/enable the extension does prompt the user");
|
||||
|
||||
await withHandlingDefaultSearchPrompt(
|
||||
{ extensionId: EXTENSION_ID2, respond: false },
|
||||
async () => {
|
||||
await addon2.disable();
|
||||
await addon2.enable();
|
||||
}
|
||||
);
|
||||
|
||||
// we said no.
|
||||
equal(
|
||||
(await Services.search.getDefault()).name,
|
||||
initialEngine.name,
|
||||
`Default engine should still be the same after disabling/enabling ${EXTENSION_ID2}.`
|
||||
);
|
||||
|
||||
await withHandlingDefaultSearchPrompt(
|
||||
{ extensionId: EXTENSION_ID, respond: false },
|
||||
async () => {
|
||||
await addon.disable();
|
||||
await addon.enable();
|
||||
}
|
||||
);
|
||||
|
||||
// we said no.
|
||||
equal(
|
||||
(await Services.search.getDefault()).name,
|
||||
Services.search.originalDefaultEngine.name,
|
||||
`Default engine should be set to the original default after disabling/enabling ${EXTENSION_ID}.`
|
||||
);
|
||||
|
||||
await withHandlingDefaultSearchPrompt(
|
||||
{ extensionId: EXTENSION_ID, respond: true },
|
||||
async () => {
|
||||
await addon.disable();
|
||||
await addon.enable();
|
||||
}
|
||||
);
|
||||
|
||||
// we responded yes.
|
||||
equal(
|
||||
(await Services.search.getDefault()).name,
|
||||
extensionInfo.manifest.chrome_settings_overrides.search_provider.name,
|
||||
"Default engine should be set to the one opted-in from the last prompt."
|
||||
);
|
||||
|
||||
await extension.unload();
|
||||
await extension2.unload();
|
||||
}
|
||||
|
||||
add_task(function test_builtin_default_search_after_updating_old_addons() {
|
||||
return test_default_search_on_updating_addons_installed_before_bug1757760({
|
||||
builtinAsInitialDefault: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_third_party_default_search_after_updating_old_addons() {
|
||||
return test_default_search_on_updating_addons_installed_before_bug1757760({
|
||||
builtinAsInitialDefault: false,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,12 +6,13 @@ body {
|
|||
display: flex;
|
||||
align-items: stretch;
|
||||
padding-block: 40px 80px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Ubuntu", "Helvetica Neue", sans-serif;
|
||||
font: message-box;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
font-weight: 500;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
body > nav {
|
||||
|
@ -77,6 +78,7 @@ body > main > aside {
|
|||
|
||||
.page-section-header > .section-description {
|
||||
grid-area: desc;
|
||||
color: var(--in-content-deemphasized-text)
|
||||
}
|
||||
|
||||
.setup-step > h2 {
|
||||
|
@ -101,7 +103,7 @@ body > main > aside {
|
|||
|
||||
.closed-tab-li {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
grid-template-columns: min-content repeat(8, 1fr);
|
||||
column-gap: 16px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
|
@ -119,6 +121,7 @@ body > main > aside {
|
|||
.closed-tab-li-title {
|
||||
grid-column: span 5;
|
||||
padding-inline-start: 2px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.closed-tab-li-url {
|
||||
|
@ -130,14 +133,23 @@ body > main > aside {
|
|||
text-align: end;
|
||||
}
|
||||
|
||||
.closed-tab-li-url, .closed-tab-li-time {
|
||||
color: var(--in-content-deemphasized-text);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.closed-tab-li-title, .closed-tab-li-url {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon {
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
display: inline-block;
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
@ -152,3 +164,13 @@ body > main > aside {
|
|||
.icon.history {
|
||||
background-image: url('chrome://browser/skin/history.svg');
|
||||
}
|
||||
|
||||
.favicon {
|
||||
background-size: cover;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.favicon, .icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'; img-src data: chrome:;">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<title data-l10n-id="firefoxview-page-title"></title>
|
||||
<link rel="localization" href="branding/brand.ftl">
|
||||
|
|
|
@ -29,4 +29,5 @@ window.addEventListener("load", () => {
|
|||
|
||||
window.addEventListener("unload", () => {
|
||||
tabsSetupFlowManager?.uninit();
|
||||
document.getElementById("recently-closed-tabs-container").cleanup();
|
||||
});
|
||||
|
|
|
@ -14,8 +14,19 @@ XPCOMUtils.defineLazyModuleGetters(globalThis, {
|
|||
});
|
||||
|
||||
const relativeTimeFormat = new Services.intl.RelativeTimeFormat(undefined, {});
|
||||
const SS_NOTIFY_CLOSED_OBJECTS_CHANGED = "sessionstore-closed-objects-changed";
|
||||
|
||||
function getWindow() {
|
||||
return window.browsingContext.embedderWindowGlobal.browsingContext.window;
|
||||
}
|
||||
|
||||
class RecentlyClosedTabsList extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.maxTabsLength = 25;
|
||||
this.closedTabsData = [];
|
||||
}
|
||||
|
||||
get tabsList() {
|
||||
return this.querySelector("ol");
|
||||
}
|
||||
|
@ -29,20 +40,18 @@ class RecentlyClosedTabsList extends HTMLElement {
|
|||
|
||||
connectedCallback() {
|
||||
this.addEventListener("click", this);
|
||||
this.addEventListener("keydown", this);
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
if (event.type == "click") {
|
||||
const item = event.target.closest(".closed-tab-li");
|
||||
event.preventDefault();
|
||||
this.openTab(item.dataset.targetURI);
|
||||
if (
|
||||
event.type == "click" ||
|
||||
(event.type == "keydown" && event.keyCode == KeyEvent.DOM_VK_RETURN)
|
||||
) {
|
||||
this.openTabAndUpdate(event);
|
||||
}
|
||||
}
|
||||
|
||||
getWindow() {
|
||||
return window.windowRoot.ownerGlobal;
|
||||
}
|
||||
|
||||
convertTimestamp(timestamp) {
|
||||
const elapsed = Date.now() - timestamp;
|
||||
const nowThresholdMs = 91000;
|
||||
|
@ -79,66 +88,145 @@ class RecentlyClosedTabsList extends HTMLElement {
|
|||
return displayHost.length ? displayHost : uriString;
|
||||
}
|
||||
|
||||
getTargetURI(tab) {
|
||||
let targetURI = "";
|
||||
getTabStateValue(tab, key) {
|
||||
let value = "";
|
||||
const tabEntries = tab.state.entries;
|
||||
const activeIndex = tabEntries.length - 1;
|
||||
|
||||
if (activeIndex >= 0 && tabEntries[activeIndex]) {
|
||||
targetURI = tabEntries[activeIndex].url;
|
||||
value = tabEntries[activeIndex][key];
|
||||
}
|
||||
|
||||
return targetURI;
|
||||
return value;
|
||||
}
|
||||
|
||||
openTab(targetURI) {
|
||||
window.open(targetURI, "_blank");
|
||||
openTabAndUpdate(event) {
|
||||
event.preventDefault();
|
||||
const item = event.target.closest(".closed-tab-li");
|
||||
const index = [...this.tabsList.children].indexOf(item);
|
||||
|
||||
SessionStore.undoCloseTab(getWindow(), index);
|
||||
this.tabsList.removeChild(item);
|
||||
}
|
||||
|
||||
generateTabs() {
|
||||
let closedTabs = SessionStore.getClosedTabData(this.getWindow());
|
||||
closedTabs = closedTabs.slice(0, 25);
|
||||
initiateTabsList() {
|
||||
let closedTabs = SessionStore.getClosedTabData(getWindow());
|
||||
closedTabs = closedTabs.slice(0, this.maxTabsLength);
|
||||
this.closedTabsData = closedTabs;
|
||||
|
||||
for (const tab of closedTabs) {
|
||||
let li = document.createElement("li");
|
||||
li.classList.add("closed-tab-li");
|
||||
|
||||
if (tab.image) {
|
||||
// TODO - figure out how to render this properly
|
||||
PlacesUIUtils.setImage(tab, li);
|
||||
}
|
||||
|
||||
let title = document.createElement("span");
|
||||
title.textContent = `${tab.title}`;
|
||||
title.classList.add("closed-tab-li-title");
|
||||
|
||||
const targetURI = this.getTargetURI(tab);
|
||||
li.dataset.targetURI = targetURI;
|
||||
document.l10n.setAttributes(li, "firefoxview-closed-tabs-tab-button", {
|
||||
targetURI,
|
||||
});
|
||||
|
||||
let url = document.createElement("span");
|
||||
|
||||
if (targetURI) {
|
||||
url.textContent = this.formatURIForDisplay(targetURI);
|
||||
url.classList.add("closed-tab-li-url");
|
||||
}
|
||||
|
||||
let time = document.createElement("span");
|
||||
time.textContent = this.convertTimestamp(tab.closedAt);
|
||||
time.classList.add("closed-tab-li-time");
|
||||
|
||||
li.append(title, url, time);
|
||||
this.tabsList.appendChild(li);
|
||||
const li = this.generateListItem(tab);
|
||||
this.tabsList.append(li);
|
||||
}
|
||||
this.tabsList.hidden = false;
|
||||
}
|
||||
|
||||
updateTabsList() {
|
||||
let newClosedTabs = SessionStore.getClosedTabData(getWindow());
|
||||
newClosedTabs = newClosedTabs.slice(0, this.maxTabsLength);
|
||||
|
||||
if (this.closedTabsData.length && !newClosedTabs.length) {
|
||||
// if a user purges history, clear the list
|
||||
[...this.tabsList.children].forEach(node =>
|
||||
this.tabsList.removeChild(node)
|
||||
);
|
||||
document
|
||||
.getElementById("recently-closed-tabs-container")
|
||||
.togglePlaceholderVisibility(true);
|
||||
this.tabsList.hidden = true;
|
||||
this.closedTabsData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const tabsToAdd = newClosedTabs.filter(
|
||||
newTab =>
|
||||
!this.closedTabsData.some(tab => {
|
||||
return (
|
||||
this.getTabStateValue(tab, "ID") ==
|
||||
this.getTabStateValue(newTab, "ID")
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
if (!tabsToAdd.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let tab of tabsToAdd.reverse()) {
|
||||
if (this.tabsList.children.length == this.maxTabsLength) {
|
||||
this.tabsList.lastChild.remove();
|
||||
}
|
||||
let li = this.generateListItem(tab);
|
||||
this.tabsList.prepend(li);
|
||||
}
|
||||
|
||||
this.closedTabsData = newClosedTabs;
|
||||
|
||||
// for situations where the tab list will initially be empty (such as
|
||||
// with new profiles or automatic session restore is disabled) and
|
||||
// this.initiateTabsList won't be called
|
||||
if (this.tabsList.hidden) {
|
||||
this.tabsList.hidden = false;
|
||||
document
|
||||
.getElementById("recently-closed-tabs-container")
|
||||
.togglePlaceholderVisibility(false);
|
||||
}
|
||||
}
|
||||
|
||||
setFavicon(tab) {
|
||||
const imageUrl = tab.image
|
||||
? PlacesUIUtils.getImageURL(tab)
|
||||
: "chrome://global/skin/icons/defaultFavicon.svg";
|
||||
let favicon = document.createElement("div");
|
||||
|
||||
favicon.style.backgroundImage = `url('${imageUrl}')`;
|
||||
favicon.classList.add("favicon");
|
||||
favicon.setAttribute("role", "presentation");
|
||||
return favicon;
|
||||
}
|
||||
|
||||
generateListItem(tab) {
|
||||
const li = document.createElement("li");
|
||||
li.classList.add("closed-tab-li");
|
||||
li.setAttribute("tabindex", 0);
|
||||
li.setAttribute("role", "button");
|
||||
|
||||
const title = document.createElement("span");
|
||||
title.textContent = `${tab.title}`;
|
||||
title.classList.add("closed-tab-li-title");
|
||||
|
||||
const favicon = this.setFavicon(tab);
|
||||
li.append(favicon);
|
||||
|
||||
const targetURI = this.getTabStateValue(tab, "url");
|
||||
li.dataset.targetURI = targetURI;
|
||||
document.l10n.setAttributes(li, "firefoxview-closed-tabs-tab-button", {
|
||||
targetURI,
|
||||
});
|
||||
|
||||
const url = document.createElement("span");
|
||||
|
||||
if (targetURI) {
|
||||
url.textContent = this.formatURIForDisplay(targetURI);
|
||||
url.classList.add("closed-tab-li-url");
|
||||
}
|
||||
|
||||
const time = document.createElement("span");
|
||||
time.textContent = this.convertTimestamp(tab.closedAt);
|
||||
time.classList.add("closed-tab-li-time");
|
||||
|
||||
li.append(title, url, time);
|
||||
return li;
|
||||
}
|
||||
}
|
||||
customElements.define("recently-closed-tabs-list", RecentlyClosedTabsList);
|
||||
|
||||
class RecentlyClosedTabsContainer extends HTMLElement {
|
||||
getWindow = () => window.windowRoot.ownerGlobal;
|
||||
constructor() {
|
||||
super();
|
||||
this.observerAdded = false;
|
||||
this.boundObserve = (...args) => this.observe(...args);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.noTabsElement = this.querySelector(
|
||||
|
@ -149,28 +237,77 @@ class RecentlyClosedTabsContainer extends HTMLElement {
|
|||
"#collapsible-tabs-container"
|
||||
);
|
||||
this.collapsibleButton = this.querySelector("#collapsible-tabs-button");
|
||||
|
||||
this.collapsibleButton.addEventListener("click", this);
|
||||
|
||||
getWindow().gBrowser.tabContainer.addEventListener("TabSelect", this);
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
getWindow().gBrowser.tabContainer.removeEventListener("TabSelect", this);
|
||||
|
||||
if (this.observerAdded) {
|
||||
Services.obs.removeObserver(
|
||||
this.boundObserve,
|
||||
SS_NOTIFY_CLOSED_OBJECTS_CHANGED
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// we observe when a tab closes but since this notification fires more frequently and on
|
||||
// all windows, we remove the observer when another tab is selected; we check for changes
|
||||
// to the session store once the user return to this tab.
|
||||
handleObservers(contentDocument) {
|
||||
if (
|
||||
!this.observerAdded &&
|
||||
contentDocument &&
|
||||
contentDocument.URL == "about:firefoxview"
|
||||
) {
|
||||
Services.obs.addObserver(
|
||||
this.boundObserve,
|
||||
SS_NOTIFY_CLOSED_OBJECTS_CHANGED
|
||||
);
|
||||
this.observerAdded = true;
|
||||
this.list.updateTabsList();
|
||||
} else if (this.observerAdded) {
|
||||
Services.obs.removeObserver(
|
||||
this.boundObserve,
|
||||
SS_NOTIFY_CLOSED_OBJECTS_CHANGED
|
||||
);
|
||||
this.observerAdded = false;
|
||||
}
|
||||
}
|
||||
|
||||
observe = () => this.list.updateTabsList();
|
||||
|
||||
onLoad() {
|
||||
if (this.getClosedTabCount() == 0) {
|
||||
this.noTabsElement.hidden = false;
|
||||
this.collapsibleContainer.classList.add("empty-container");
|
||||
this.togglePlaceholderVisibility(true);
|
||||
} else {
|
||||
this.list.generateTabs();
|
||||
this.list.initiateTabsList();
|
||||
}
|
||||
Services.obs.addObserver(
|
||||
this.boundObserve,
|
||||
SS_NOTIFY_CLOSED_OBJECTS_CHANGED
|
||||
);
|
||||
this.observerAdded = true;
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
if (event.type == "click" && event.target == this.collapsibleButton) {
|
||||
this.toggleTabs();
|
||||
} else if (event.type == "TabSelect") {
|
||||
this.handleObservers(event.target.linkedBrowser.contentDocument);
|
||||
}
|
||||
}
|
||||
|
||||
togglePlaceholderVisibility(visible) {
|
||||
this.noTabsElement.toggleAttribute("hidden", !visible);
|
||||
this.collapsibleContainer.classList.toggle("empty-container", visible);
|
||||
}
|
||||
|
||||
getClosedTabCount = () => {
|
||||
try {
|
||||
return SessionStore.getClosedTabCount(this.getWindow());
|
||||
return SessionStore.getClosedTabCount(getWindow());
|
||||
} catch (ex) {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
[DEFAULT]
|
||||
run-if = nightly_build # about:firefoxview is only enabled on Nightly
|
||||
|
||||
support-files = head.js
|
||||
|
||||
[browser_firefoxview.js]
|
||||
[browser_firefoxview_tab.js]
|
||||
[browser_recently_closed_tabs.js]
|
||||
[browser_setup_state.js]
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(globalThis, {
|
||||
SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
|
||||
});
|
||||
|
||||
const URLs = [
|
||||
"http://mochi.test:8888/browser/",
|
||||
"http://www.example.com/",
|
||||
"http://example.net",
|
||||
"http://example.org",
|
||||
];
|
||||
|
||||
async function add_new_tab(URL) {
|
||||
let tab = BrowserTestUtils.addTab(gBrowser, URL);
|
||||
await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
return tab;
|
||||
}
|
||||
|
||||
async function close_tab(tab) {
|
||||
const sessionStorePromise = BrowserTestUtils.waitForSessionStoreUpdate(tab);
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
await sessionStorePromise;
|
||||
}
|
||||
|
||||
function clearHistory() {
|
||||
Services.obs.notifyObservers(null, "browser:purge-session-history");
|
||||
}
|
||||
|
||||
add_task(async function test_empty_list() {
|
||||
clearHistory();
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
const closedObjectsChanged = TestUtils.topicObserved(
|
||||
"sessionstore-closed-objects-changed"
|
||||
);
|
||||
|
||||
ok(
|
||||
document
|
||||
.querySelector("#collapsible-tabs-container")
|
||||
.classList.contains("empty-container"),
|
||||
"collapsible container should have correct styling when the list is empty"
|
||||
);
|
||||
|
||||
testVisibility(browser, {
|
||||
expectedVisible: {
|
||||
"#recently-closed-tabs-placeholder": true,
|
||||
"ol.closed-tabs-list": false,
|
||||
},
|
||||
});
|
||||
|
||||
const tab1 = await add_new_tab(URLs[0]);
|
||||
|
||||
await close_tab(tab1);
|
||||
await closedObjectsChanged;
|
||||
|
||||
ok(
|
||||
!document
|
||||
.querySelector("#collapsible-tabs-container")
|
||||
.classList.contains("empty-container"),
|
||||
"collapsible container should have correct styling when the list is not empty"
|
||||
);
|
||||
|
||||
testVisibility(browser, {
|
||||
expectedVisible: {
|
||||
"#recently-closed-tabs-placeholder": false,
|
||||
"ol.closed-tabs-list": true,
|
||||
},
|
||||
});
|
||||
|
||||
ok(
|
||||
document.querySelector("ol.closed-tabs-list").children.length === 1,
|
||||
"recently-closed-tabs-list should have one list item"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_list_ordering() {
|
||||
const existingData = SessionStore.getClosedTabCount(window);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
const closedObjectsChanged = TestUtils.topicObserved(
|
||||
"sessionstore-closed-objects-changed"
|
||||
);
|
||||
|
||||
const tab1 = await add_new_tab(URLs[0]);
|
||||
const tab2 = await add_new_tab(URLs[1]);
|
||||
const tab3 = await add_new_tab(URLs[2]);
|
||||
|
||||
gBrowser.selectedTab = tab3;
|
||||
|
||||
await close_tab(tab3);
|
||||
await closedObjectsChanged;
|
||||
|
||||
await close_tab(tab2);
|
||||
await closedObjectsChanged;
|
||||
|
||||
await close_tab(tab1);
|
||||
await closedObjectsChanged;
|
||||
|
||||
const tabsList = document.querySelector("ol.closed-tabs-list");
|
||||
await BrowserTestUtils.waitForMutationCondition(
|
||||
tabsList,
|
||||
{ childList: true },
|
||||
() => tabsList.children.length > 1
|
||||
);
|
||||
|
||||
ok(
|
||||
document.querySelector("ol.closed-tabs-list").children.length ===
|
||||
3 + existingData,
|
||||
"recently-closed-tabs-list should have one list item"
|
||||
);
|
||||
|
||||
// check that the ordering is correct when user navigates to another tab, and then closes multiple tabs.
|
||||
ok(
|
||||
document
|
||||
.querySelector("ol.closed-tabs-list")
|
||||
.firstChild.textContent.includes("mochi.test"),
|
||||
"first list item in recently-closed-tabs-list is in the correct order"
|
||||
);
|
||||
|
||||
ok(
|
||||
document
|
||||
.querySelector("ol.closed-tabs-list")
|
||||
.children[2].textContent.includes("example.net"),
|
||||
"last list item in recently-closed-tabs-list is in the correct order"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_max_list_items() {
|
||||
// the tabs opened from the previous test provide seed data
|
||||
const mockMaxTabsLength = SessionStore.getClosedTabCount(window);
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
// override this value for testing purposes
|
||||
document.querySelector(
|
||||
"recently-closed-tabs-list"
|
||||
).maxTabsLength = mockMaxTabsLength;
|
||||
|
||||
ok(
|
||||
!document
|
||||
.querySelector("#collapsible-tabs-container")
|
||||
.classList.contains("empty-container"),
|
||||
"collapsible container should have correct styling when the list is not empty"
|
||||
);
|
||||
|
||||
testVisibility(browser, {
|
||||
expectedVisible: {
|
||||
"#recently-closed-tabs-placeholder": false,
|
||||
"ol.closed-tabs-list": true,
|
||||
},
|
||||
});
|
||||
|
||||
ok(
|
||||
document.querySelector("ol.closed-tabs-list").childNodes.length ===
|
||||
mockMaxTabsLength,
|
||||
`recently-closed-tabs-list should have ${mockMaxTabsLength} list items`
|
||||
);
|
||||
|
||||
ok(
|
||||
document
|
||||
.querySelector("ol.closed-tabs-list")
|
||||
.firstChild.textContent.includes("about:firefoxview"),
|
||||
"first list item in recently-closed-tabs-list is from previous test (session store)"
|
||||
);
|
||||
|
||||
const closedObjectsChanged = TestUtils.topicObserved(
|
||||
"sessionstore-closed-objects-changed"
|
||||
);
|
||||
// add another tab
|
||||
const tab = await add_new_tab(URLs[3]);
|
||||
await close_tab(tab);
|
||||
await closedObjectsChanged;
|
||||
|
||||
ok(
|
||||
document
|
||||
.querySelector("ol.closed-tabs-list")
|
||||
.firstChild.textContent.includes("example.org"),
|
||||
"first list item in recently-closed-tabs-list should have been updated"
|
||||
);
|
||||
|
||||
ok(
|
||||
document.querySelector("ol.closed-tabs-list").childNodes.length ===
|
||||
mockMaxTabsLength,
|
||||
`recently-closed-tabs-list should still have ${mockMaxTabsLength} list items`
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
function testVisibility(browser, expected) {
|
||||
const { document } = browser.contentWindow;
|
||||
for (let [selector, shouldBeVisible] of Object.entries(
|
||||
expected.expectedVisible
|
||||
)) {
|
||||
const elem = document.querySelector(selector);
|
||||
if (shouldBeVisible) {
|
||||
ok(
|
||||
BrowserTestUtils.is_visible(elem),
|
||||
`Expected ${selector} to be visible`
|
||||
);
|
||||
} else {
|
||||
ok(BrowserTestUtils.is_hidden(elem), `Expected ${selector} to be hidden`);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1806,14 +1806,13 @@ var PlacesUIUtils = {
|
|||
}
|
||||
},
|
||||
|
||||
setImage(aItem, aElement) {
|
||||
getImageURL(aItem) {
|
||||
let iconURL = aItem.image;
|
||||
// don't initiate a connection just to fetch a favicon (see bug 467828)
|
||||
if (/^https?:/.test(iconURL)) {
|
||||
iconURL = "moz-anno:favicon:" + iconURL;
|
||||
}
|
||||
|
||||
aElement.setAttribute("image", iconURL);
|
||||
return iconURL;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,6 +21,7 @@ support-files =
|
|||
|
||||
[browser_privatebrowsing_DownloadLastDirWithCPS.js]
|
||||
[browser_privatebrowsing_about_default_promo.js]
|
||||
[browser_privatebrowsing_about_focus_promo.js]
|
||||
[browser_privatebrowsing_about_nimbus.js]
|
||||
[browser_privatebrowsing_about_nimbus_messaging.js]
|
||||
[browser_privatebrowsing_about_nimbus_impressions.js]
|
||||
|
@ -44,7 +45,6 @@ skip-if = verify
|
|||
[browser_privatebrowsing_downloadLastDir_c.js]
|
||||
[browser_privatebrowsing_downloadLastDir_toggle.js]
|
||||
[browser_privatebrowsing_favicon.js]
|
||||
[browser_privatebrowsing_focus_promo.js]
|
||||
[browser_privatebrowsing_history_shift_click.js]
|
||||
[browser_privatebrowsing_last_private_browsing_context_exited.js]
|
||||
[browser_privatebrowsing_lastpbcontextexited.js]
|
||||
|
|
|
@ -57,7 +57,7 @@ add_task(async function test_focus_promo_in_disallowed_region() {
|
|||
|
||||
add_task(
|
||||
async function test_klar_promo_in_certain_regions_with_English_locale() {
|
||||
const testLocale = "en-GB"; // British English
|
||||
const testLocale = "en-US"; // US English
|
||||
setLocale(testLocale);
|
||||
|
||||
const testRegion = async region => {
|
|
@ -192,7 +192,8 @@ function createEntry(
|
|||
|
||||
element.setAttribute("label", aMenuLabel);
|
||||
if (aClosedTab.image) {
|
||||
PlacesUIUtils.setImage(aClosedTab, element);
|
||||
const iconURL = PlacesUIUtils.getImageURL(aClosedTab);
|
||||
element.setAttribute("image", iconURL);
|
||||
}
|
||||
if (!aIsWindowsFragment) {
|
||||
element.setAttribute("value", aIndex);
|
||||
|
|
|
@ -1081,6 +1081,7 @@ var SessionStoreInternal = {
|
|||
// Non-SHIP code calls this when the frame script is unloaded.
|
||||
this.onFinalTabStateUpdateComplete(aSubject);
|
||||
}
|
||||
this._notifyOfClosedObjectsChange();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -41,7 +41,11 @@ CONTENT_WIN.addEventListener("DOMContentLoaded", function onDCL(evt) {
|
|||
|
||||
case "childList": {
|
||||
// We really only care about elements appending inside pages.
|
||||
if (!mutation.addedNodes || !mutation.target.closest(".page")) {
|
||||
let parent =
|
||||
mutation.target instanceof HTMLDocument
|
||||
? mutation.target.documentElement
|
||||
: mutation.target;
|
||||
if (!mutation.addedNodes || !parent.closest(".page")) {
|
||||
break;
|
||||
}
|
||||
FormAutofillUtils.localizeMarkup(mutation.target);
|
||||
|
|
|
@ -64,6 +64,12 @@ let AVAILABLE_PIP_OVERRIDES;
|
|||
},
|
||||
},
|
||||
|
||||
hulu: {
|
||||
"https://www.hulu.com/watch/*": {
|
||||
videoWrapperScriptPath: "video-wrappers/hulu.js",
|
||||
},
|
||||
},
|
||||
|
||||
instagram: {
|
||||
"https://www.instagram.com/*": { policy: TOGGLE_POLICIES.ONE_QUARTER },
|
||||
},
|
||||
|
|
|
@ -32,6 +32,7 @@ FINAL_TARGET_FILES.features["pictureinpicture@mozilla.org"]["video-wrappers"] +=
|
|||
"video-wrappers/dailymotion.js",
|
||||
"video-wrappers/funimation.js",
|
||||
"video-wrappers/hotstar.js",
|
||||
"video-wrappers/hulu.js",
|
||||
"video-wrappers/mock-wrapper.js",
|
||||
"video-wrappers/netflix.js",
|
||||
"video-wrappers/piped.js",
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
class PictureInPictureVideoWrapper {
|
||||
constructor(video) {
|
||||
this.player = video.wrappedJSObject.__HuluDashPlayer__;
|
||||
}
|
||||
play() {
|
||||
this.player.play();
|
||||
}
|
||||
pause() {
|
||||
this.player.pause();
|
||||
}
|
||||
isMuted(video) {
|
||||
return video.volume === 0;
|
||||
}
|
||||
setMuted() {
|
||||
let muteButton = document.querySelector(".VolumeControl > div");
|
||||
muteButton.click();
|
||||
}
|
||||
setCaptionContainerObserver(video, updateCaptionsFunction) {
|
||||
let container = document.querySelector(".ClosedCaption");
|
||||
|
||||
if (container) {
|
||||
updateCaptionsFunction("");
|
||||
const callback = function(mutationsList, observer) {
|
||||
let text = container.querySelector(".CaptionBox").innerText;
|
||||
updateCaptionsFunction(text);
|
||||
};
|
||||
|
||||
// immediately invoke the callback function to add subtitles to the PiP window
|
||||
callback([1], null);
|
||||
|
||||
let captionsObserver = new MutationObserver(callback);
|
||||
|
||||
captionsObserver.observe(container, {
|
||||
attributes: false,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
|
|
@ -2,7 +2,7 @@
|
|||
"manifest_version": 2,
|
||||
"name": "Web Compatibility Interventions",
|
||||
"description": "Urgent post-release fixes for web compatibility.",
|
||||
"version": "101.7.0",
|
||||
"version": "101.8.0",
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "webcompat@mozilla.org",
|
||||
|
|
|
@ -9,13 +9,56 @@
|
|||
*
|
||||
* Some sites rely on Maxmind's GeoIP library which gets blocked by ETP's
|
||||
* fingerprinter blocking. With the library window global not being defined
|
||||
* functionality may break or the site does not render at all. This shim adds a
|
||||
* dummy object which returns errors for any request to mitigate the breakage.
|
||||
* functionality may break or the site does not render at all. This shim
|
||||
* has it return the United States as the location for all users.
|
||||
*/
|
||||
|
||||
if (!window.geoip2) {
|
||||
const callback = (_, onError) => {
|
||||
onError("");
|
||||
const continent = {
|
||||
code: "NA",
|
||||
geoname_id: 6255149,
|
||||
names: {
|
||||
de: "Nordamerika",
|
||||
en: "North America",
|
||||
es: "Norteamérica",
|
||||
fr: "Amérique du Nord",
|
||||
ja: "北アメリカ",
|
||||
"pt-BR": "América do Norte",
|
||||
ru: "Северная Америка",
|
||||
"zh-CN": "北美洲",
|
||||
},
|
||||
};
|
||||
|
||||
const country = {
|
||||
geoname_id: 6252001,
|
||||
iso_code: "US",
|
||||
names: {
|
||||
de: "USA",
|
||||
en: "United States",
|
||||
es: "Estados Unidos",
|
||||
fr: "États-Unis",
|
||||
ja: "アメリカ合衆国",
|
||||
"pt-BR": "Estados Unidos",
|
||||
ru: "США",
|
||||
"zh-CN": "美国",
|
||||
},
|
||||
};
|
||||
|
||||
const city = {
|
||||
names: {
|
||||
en: "",
|
||||
},
|
||||
};
|
||||
|
||||
const callback = onSuccess => {
|
||||
requestAnimationFrame(() => {
|
||||
onSuccess({
|
||||
city,
|
||||
continent,
|
||||
country,
|
||||
registered_country: country,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
window.geoip2 = {
|
||||
|
|
|
@ -261,7 +261,7 @@ const ColorwayCollections = [
|
|||
colorwayClosetEnabled && AppConstants.NIGHTLY_BUILD
|
||||
? "2022-04-20"
|
||||
: "2022-05-03",
|
||||
l10nId: "colorway-collection-life-in-color",
|
||||
l10nId: "colorway-collection-true-colors",
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -119,6 +119,7 @@ https://sub2.test1.example.com:443 privileged
|
|||
https://sub2.test2.example.com:443 privileged
|
||||
https://example.net:443 privileged
|
||||
https://nocert.example.com:443 privileged,nocert
|
||||
https://nocert.example.org:443 privileged,nocert
|
||||
https://self-signed.example.com:443 privileged,cert=selfsigned
|
||||
https://untrusted.example.com:443 privileged,cert=untrusted
|
||||
https://expired.example.com:443 privileged,cert=expired
|
||||
|
|
|
@ -1208,7 +1208,22 @@
|
|||
"../node_modules/babel-loader/lib/index.js??ref--1!../packages/devtools-source-map/src/utils/network-request.js": 1056,
|
||||
"../node_modules/babel-loader/lib/index.js??ref--1!../../shared/worker-dispatcher.js": 1057,
|
||||
"../node_modules/babel-loader/lib/index.js??ref--1!../packages/devtools-source-map/src/utils/privileged-network-request.js": 1058,
|
||||
"../node_modules/babel-loader/lib/index.js??ref--1!../../shared/worker-utils.js": 1059
|
||||
"../node_modules/babel-loader/lib/index.js??ref--1!../../shared/worker-utils.js": 1059,
|
||||
"../packages/devtools-source-map/node_modules/whatwg-url/lib/url-state-machine.js": 1060,
|
||||
"../packages/devtools-source-map/node_modules/whatwg-url/lib/urlencoded.js": 1061,
|
||||
"../packages/devtools-source-map/node_modules/webidl-conversions/lib/index.js": 1062,
|
||||
"../packages/devtools-source-map/node_modules/whatwg-url/lib/utils.js": 1063,
|
||||
"../node_modules/node-libs-browser/node_modules/punycode/punycode.js": 1064,
|
||||
"../packages/devtools-source-map/node_modules/whatwg-url/lib/infra.js": 1065,
|
||||
"../packages/devtools-source-map/node_modules/whatwg-url/lib/URLSearchParams.js": 1066,
|
||||
"../packages/devtools-source-map/node_modules/whatwg-url/lib/public-api.js": 1067,
|
||||
"../packages/devtools-source-map/node_modules/whatwg-url/lib/URL.js": 1068,
|
||||
"../packages/devtools-source-map/node_modules/whatwg-url/lib/URL-impl.js": 1069,
|
||||
"../packages/devtools-source-map/node_modules/tr46/index.js": 1070,
|
||||
"../packages/devtools-source-map/node_modules/tr46/lib/regexes.js": 1071,
|
||||
"../node_modules/json-loader/index.js!../packages/devtools-source-map/node_modules/tr46/lib/mappingTable.json": 1072,
|
||||
"../packages/devtools-source-map/node_modules/whatwg-url/lib/URLSearchParams-impl.js": 1073,
|
||||
"../node_modules/lodash.sortby/index.js": 1074
|
||||
},
|
||||
"usedIds": {
|
||||
"0": 0,
|
||||
|
@ -2270,7 +2285,22 @@
|
|||
"1056": 1056,
|
||||
"1057": 1057,
|
||||
"1058": 1058,
|
||||
"1059": 1059
|
||||
"1059": 1059,
|
||||
"1060": 1060,
|
||||
"1061": 1061,
|
||||
"1062": 1062,
|
||||
"1063": 1063,
|
||||
"1064": 1064,
|
||||
"1065": 1065,
|
||||
"1066": 1066,
|
||||
"1067": 1067,
|
||||
"1068": 1068,
|
||||
"1069": 1069,
|
||||
"1070": 1070,
|
||||
"1071": 1071,
|
||||
"1072": 1072,
|
||||
"1073": 1073,
|
||||
"1074": 1074
|
||||
}
|
||||
},
|
||||
"chunks": {
|
||||
|
@ -2415,7 +2445,7 @@
|
|||
"byName": {},
|
||||
"byBlocks": {},
|
||||
"usedIds": {
|
||||
"0": 0
|
||||
"1": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2436,7 +2466,7 @@
|
|||
"byName": {},
|
||||
"byBlocks": {},
|
||||
"usedIds": {
|
||||
"0": 0
|
||||
"1": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,49 +2,6 @@
|
|||
* 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 whatwgUrl = `
|
||||
(() => {
|
||||
let factory;
|
||||
function define(...args) {
|
||||
if (factory) {
|
||||
throw new Error("expected a single define call");
|
||||
}
|
||||
|
||||
if (
|
||||
args.length !== 2 ||
|
||||
!Array.isArray(args[0]) ||
|
||||
args[0].length !== 0 ||
|
||||
typeof args[1] !== "function"
|
||||
) {
|
||||
throw new Error("whatwg-url had unexpected factory arguments.");
|
||||
}
|
||||
|
||||
factory = args[1];
|
||||
}
|
||||
define.amd = true;
|
||||
|
||||
const existingDefine = Object.getOwnPropertyDescriptor(globalThis, "define");
|
||||
globalThis.define = define;
|
||||
let err;
|
||||
try {
|
||||
importScripts("resource://devtools/client/shared/vendor/whatwg-url.js");
|
||||
|
||||
if (!factory) {
|
||||
throw new Error("Failed to load whatwg-url factory");
|
||||
}
|
||||
} finally {
|
||||
if (existingDefine) {
|
||||
Object.defineProperty(globalThis, "define", existingDefine);
|
||||
} else {
|
||||
delete globalThis.define;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return factory();
|
||||
})()
|
||||
`;
|
||||
|
||||
module.exports = {
|
||||
"./source-editor": "devtools/client/sourceeditor/editor",
|
||||
"../editor/source-editor": "devtools/client/sourceeditor/editor",
|
||||
|
@ -59,5 +16,4 @@ module.exports = {
|
|||
"devtools-services": "Services",
|
||||
"wasmparser/dist/cjs/WasmParser": "devtools/client/shared/vendor/WasmParser",
|
||||
"wasmparser/dist/cjs/WasmDis": "devtools/client/shared/vendor/WasmDis",
|
||||
"whatwg-url": `var ${whatwgUrl}`,
|
||||
};
|
||||
|
|
|
@ -25,6 +25,7 @@ Array [
|
|||
"thread": "FakeThread",
|
||||
},
|
||||
],
|
||||
"filename": "a",
|
||||
"source": Object {
|
||||
"extensionName": null,
|
||||
"id": "a",
|
||||
|
@ -42,12 +43,8 @@ Array [
|
|||
]
|
||||
`;
|
||||
|
||||
exports[`breakpoints should not re-add a breakpoint 1`] = `Array []`;
|
||||
|
||||
exports[`breakpoints should not show a breakpoint that does not have text 1`] = `Array []`;
|
||||
|
||||
exports[`breakpoints should not show a breakpoint that does not have text 2`] = `Array []`;
|
||||
|
||||
exports[`breakpoints should remap breakpoints on pretty print 1`] = `
|
||||
Object {
|
||||
"disabled": false,
|
||||
|
@ -96,6 +93,7 @@ Array [
|
|||
"thread": "FakeThread",
|
||||
},
|
||||
],
|
||||
"filename": "a",
|
||||
"source": Object {
|
||||
"extensionName": null,
|
||||
"id": "a",
|
||||
|
|
|
@ -42,7 +42,10 @@ class EmptyLines extends Component {
|
|||
shouldComponentUpdate(nextProps) {
|
||||
const { breakableLines, selectedSource } = this.props;
|
||||
return (
|
||||
breakableLines != nextProps.breakableLines ||
|
||||
// Breakable lines are something that evolves over time,
|
||||
// but we either have them loaded or not. So only compare the size
|
||||
// as sometimes we always get a blank new empty Set instance.
|
||||
breakableLines.size != nextProps.breakableLines.size ||
|
||||
selectedSource.id != nextProps.selectedSource.id
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,10 +15,7 @@ import actions from "../../../actions";
|
|||
import { getSelectedLocation } from "../../../utils/selected-location";
|
||||
import { createHeadlessEditor } from "../../../utils/editor/create-editor";
|
||||
|
||||
import {
|
||||
makeBreakpointId,
|
||||
sortSelectedBreakpoints,
|
||||
} from "../../../utils/breakpoint";
|
||||
import { makeBreakpointId } from "../../../utils/breakpoint";
|
||||
|
||||
import { getSelectedSource, getBreakpointSources } from "../../../selectors";
|
||||
|
||||
|
@ -88,23 +85,18 @@ class Breakpoints extends Component {
|
|||
}
|
||||
|
||||
const editor = this.getEditor();
|
||||
const sources = [...breakpointSources.map(({ source }) => source)];
|
||||
const sources = breakpointSources.map(({ source }) => source);
|
||||
|
||||
return (
|
||||
<div className="pane breakpoints-list">
|
||||
{breakpointSources.map(({ source, breakpoints }) => {
|
||||
const sortedBreakpoints = sortSelectedBreakpoints(
|
||||
breakpoints,
|
||||
selectedSource
|
||||
);
|
||||
|
||||
return [
|
||||
<BreakpointHeading
|
||||
key={source.id}
|
||||
source={source}
|
||||
sources={sources}
|
||||
/>,
|
||||
...sortedBreakpoints.map(breakpoint => (
|
||||
breakpoints.map(breakpoint => (
|
||||
<Breakpoint
|
||||
breakpoint={breakpoint}
|
||||
source={source}
|
||||
|
|
|
@ -3,55 +3,83 @@
|
|||
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
|
||||
|
||||
import { createSelector } from "reselect";
|
||||
import { getSelectedSource, getSourceFromId } from "./sources";
|
||||
import { getSelectedSource, getSourcesMap } from "./sources";
|
||||
import { getBreakpointsList } from "./breakpoints";
|
||||
import { getFilename } from "../utils/source";
|
||||
import { getSelectedLocation } from "../utils/selected-location";
|
||||
import { sortSelectedBreakpoints } from "../utils/breakpoint";
|
||||
|
||||
function getBreakpointsForSource(source, selectedSource, breakpoints) {
|
||||
return sortSelectedBreakpoints(breakpoints, selectedSource)
|
||||
.filter(
|
||||
bp =>
|
||||
!bp.options.hidden &&
|
||||
(bp.text || bp.originalText || bp.options.condition || bp.disabled)
|
||||
)
|
||||
.filter(
|
||||
bp => getSelectedLocation(bp, selectedSource).sourceId == source.id
|
||||
);
|
||||
// Returns all the breakpoints for the given selected source
|
||||
// Depending on the selected source, this will match original or generated
|
||||
// location of the given selected source.
|
||||
function _getBreakpointsForSource(visibleBreakpoints, source, selectedSource) {
|
||||
return visibleBreakpoints.filter(
|
||||
bp => getSelectedLocation(bp, selectedSource).sourceId == source.id
|
||||
);
|
||||
}
|
||||
|
||||
const getSourcesForBreakpoints = state => {
|
||||
const selectedSource = getSelectedSource(state);
|
||||
const breakpointSourceIds = getBreakpointsList(state).map(
|
||||
// Returns a sorted list of sources for which we have breakpoints
|
||||
// We will return generated or original source IDs based on the currently selected source.
|
||||
const _getSourcesForBreakpoints = (breakpoints, sourcesMap, selectedSource) => {
|
||||
const breakpointSourceIds = breakpoints.map(
|
||||
breakpoint => getSelectedLocation(breakpoint, selectedSource).sourceId
|
||||
);
|
||||
|
||||
return [...new Set(breakpointSourceIds)]
|
||||
.map(sourceId => {
|
||||
const source = getSourceFromId(state, sourceId);
|
||||
const filename = getFilename(source);
|
||||
return { source, filename };
|
||||
})
|
||||
.filter(({ source }) => source && !source.isBlackBoxed)
|
||||
.sort((a, b) => a.filename - b.filename)
|
||||
.map(({ source }) => source);
|
||||
const sources = [];
|
||||
// We may have more than one breakpoint per sourceId,
|
||||
// so use a Set to have a unique list of source IDs.
|
||||
for (const sourceId of [...new Set(breakpointSourceIds)]) {
|
||||
const source = sourcesMap.get(sourceId);
|
||||
|
||||
// Ignore any source that is no longer in the sources reducer
|
||||
// or blackboxed sources.
|
||||
if (!source || source.isBlackBoxed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bps = _getBreakpointsForSource(breakpoints, source, selectedSource);
|
||||
|
||||
// Ignore sources which have no breakpoints
|
||||
if (bps.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sources.push({
|
||||
source,
|
||||
breakpoints: bps,
|
||||
filename: getFilename(source),
|
||||
});
|
||||
}
|
||||
|
||||
return sources.sort((a, b) => a.filename.localeCompare(b.filename));
|
||||
};
|
||||
|
||||
// Returns a list of sources with their related breakpoints:
|
||||
// [{ source, breakpoints [breakpoint1, ...] }, ...]
|
||||
//
|
||||
// This only returns sources for which we have a visible breakpoint.
|
||||
// This will return either generated or original source based on the currently
|
||||
// selected source.
|
||||
export const getBreakpointSources = createSelector(
|
||||
getBreakpointsList,
|
||||
getSourcesForBreakpoints,
|
||||
getSourcesMap,
|
||||
getSelectedSource,
|
||||
(breakpoints, sources, selectedSource) => {
|
||||
return sources
|
||||
.map(source => ({
|
||||
source,
|
||||
breakpoints: getBreakpointsForSource(
|
||||
source,
|
||||
selectedSource,
|
||||
breakpoints
|
||||
),
|
||||
}))
|
||||
.filter(({ breakpoints: bps }) => bps.length > 0);
|
||||
(breakpoints, sourcesMap, selectedSource) => {
|
||||
const visibleBreakpoints = breakpoints.filter(
|
||||
bp =>
|
||||
!bp.options.hidden &&
|
||||
(bp.text || bp.originalText || bp.options.condition || bp.disabled)
|
||||
);
|
||||
|
||||
const sortedVisibleBreakpoints = sortSelectedBreakpoints(
|
||||
visibleBreakpoints,
|
||||
selectedSource
|
||||
);
|
||||
|
||||
return _getSourcesForBreakpoints(
|
||||
sortedVisibleBreakpoints,
|
||||
sourcesMap,
|
||||
selectedSource
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -90,15 +90,24 @@ export function getSourceActorBreakableLines(state, id) {
|
|||
* @param {Object} state
|
||||
* @param {Array<String>} ids
|
||||
* List of Source Actor IDs
|
||||
* @return {AsyncValue<Array<Number>>}
|
||||
* @param {Boolean} isHTML
|
||||
* True, if we are fetching the breakable lines for an HTML source.
|
||||
* For them, we have to aggregate the lines of each source actors.
|
||||
* Otherwise, we might still have many source actors, but one per thread.
|
||||
* In this case, we simply return the first source actor to have the lines ready.
|
||||
* @return {Array<Number>}
|
||||
* List of all the breakable lines.
|
||||
*/
|
||||
export function getBreakableLinesForSourceActors(state, ids) {
|
||||
export function getBreakableLinesForSourceActors(state, ids, isHTML) {
|
||||
const allBreakableLines = [];
|
||||
for (const id of ids) {
|
||||
const { breakableLines } = getSourceActor(state, id);
|
||||
if (breakableLines && breakableLines.state == "fulfilled") {
|
||||
allBreakableLines.push(...breakableLines.value);
|
||||
if (isHTML) {
|
||||
allBreakableLines.push(...breakableLines.value);
|
||||
} else {
|
||||
return breakableLines.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return allBreakableLines;
|
||||
|
|
|
@ -140,7 +140,7 @@ export function getHasSiblingOfSameName(state, source) {
|
|||
return getSourcesUrlsInSources(state, source.url).length > 1;
|
||||
}
|
||||
|
||||
// This is only used externaly by tabs selectors
|
||||
// This is only used externaly by tabs and breakpointSources selectors
|
||||
export function getSourcesMap(state) {
|
||||
return state.sources.sources;
|
||||
}
|
||||
|
@ -377,7 +377,7 @@ export function getBreakableLines(state, sourceId) {
|
|||
|
||||
// We pull generated file breakable lines directly from the source actors
|
||||
// so that breakable lines can be added as new source actors on HTML loads.
|
||||
return getBreakableLinesForSourceActors(state, sourceActorIDs);
|
||||
return getBreakableLinesForSourceActors(state, sourceActorIDs, source.isHTML);
|
||||
}
|
||||
|
||||
export const getSelectedBreakableLines = createSelector(
|
||||
|
|
|
@ -113,11 +113,7 @@ describe("sources-tree", () => {
|
|||
expect(base.name).toBe("webpack://");
|
||||
expect(base.contents).toHaveLength(1);
|
||||
|
||||
const emptyNode = base.contents[0];
|
||||
expect(emptyNode.name).toBe("");
|
||||
expect(emptyNode.contents).toHaveLength(1);
|
||||
|
||||
const userNode = emptyNode.contents[0];
|
||||
const userNode = base.contents[0];
|
||||
expect(userNode.name).toBe("Users");
|
||||
expect(userNode.contents).toHaveLength(1);
|
||||
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
* 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/>. */
|
||||
|
||||
import { URL as URLParser } from "whatwg-url";
|
||||
|
||||
const defaultUrl = {
|
||||
hash: "",
|
||||
host: "",
|
||||
|
@ -57,7 +55,7 @@ export function parse(url) {
|
|||
|
||||
let urlObj;
|
||||
try {
|
||||
urlObj = new URLParser(url);
|
||||
urlObj = new URL(url);
|
||||
} catch (err) {
|
||||
urlObj = { ...defaultUrl };
|
||||
// If we're given simply a filename...
|
||||
|
@ -89,6 +87,10 @@ export function parse(url) {
|
|||
urlObj.pathname = url;
|
||||
}
|
||||
}
|
||||
// When provided a special URL like "webpack:///webpack/foo",
|
||||
// prevents passing the three slashes in the path, and pass only onea.
|
||||
// This will prevent displaying modules in empty-name sub folders.
|
||||
urlObj.pathname = urlObj.pathname.replace(/\/+/, "/");
|
||||
urlObj.path = urlObj.pathname + urlObj.search;
|
||||
|
||||
// Cache the result
|
||||
|
|
|
@ -21,7 +21,6 @@ const mappings = {
|
|||
"devtools-services": "Services",
|
||||
"wasmparser/dist/cjs/WasmParser": "devtools/client/shared/vendor/WasmParser",
|
||||
"wasmparser/dist/cjs/WasmDis": "devtools/client/shared/vendor/WasmDis",
|
||||
"whatwg-url": "devtools/client/shared/vendor/whatwg-url",
|
||||
"framework-actions": "devtools/client/framework/actions/index",
|
||||
"inspector-shared-utils": "devtools/client/inspector/shared/utils",
|
||||
};
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -23,7 +23,6 @@ DevToolsModules(
|
|||
'seamless-immutable.js',
|
||||
'WasmDis.js',
|
||||
'WasmParser.js',
|
||||
'whatwg-url.js',
|
||||
)
|
||||
|
||||
# react dev versions are used if enable-debug-js-modules is set in .mozconfig.
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -3561,6 +3561,7 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
|
|||
const char* errorDescriptionID = nullptr;
|
||||
AutoTArray<nsString, 3> formatStrs;
|
||||
bool addHostPort = false;
|
||||
bool isBadStsCertError = false;
|
||||
nsresult rv = NS_OK;
|
||||
nsAutoString messageStr;
|
||||
nsAutoCString cssClass;
|
||||
|
@ -3710,6 +3711,7 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
|
|||
// In the future we should differentiate between an HSTS host and a
|
||||
// pinned host and display a more informative message to the user.
|
||||
if (isStsHost || isPinnedHost) {
|
||||
isBadStsCertError = true;
|
||||
cssClass.AssignLiteral("badStsCert");
|
||||
}
|
||||
|
||||
|
@ -3870,19 +3872,21 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
|
|||
}
|
||||
}
|
||||
|
||||
nsresult delegateErrorCode = aError;
|
||||
// If the HTTPS-Only Mode upgraded this request and the upgrade might have
|
||||
// caused this error, we replace the error-page with about:httpsonlyerror
|
||||
bool isHttpsOnlyError =
|
||||
nsHTTPSOnlyUtils::CouldBeHttpsOnlyError(aFailedChannel, aError);
|
||||
if (isHttpsOnlyError) {
|
||||
if (nsHTTPSOnlyUtils::CouldBeHttpsOnlyError(aFailedChannel, aError)) {
|
||||
errorPage.AssignLiteral("httpsonlyerror");
|
||||
delegateErrorCode = NS_ERROR_HTTPS_ONLY;
|
||||
} else if (isBadStsCertError) {
|
||||
delegateErrorCode = NS_ERROR_BAD_HSTS_CERT;
|
||||
}
|
||||
|
||||
if (nsCOMPtr<nsILoadURIDelegate> loadURIDelegate = GetLoadURIDelegate()) {
|
||||
nsresult code = isHttpsOnlyError ? NS_ERROR_HTTPS_ONLY : aError;
|
||||
nsCOMPtr<nsIURI> errorPageURI;
|
||||
rv = loadURIDelegate->HandleLoadError(aURI, code, NS_ERROR_GET_MODULE(code),
|
||||
getter_AddRefs(errorPageURI));
|
||||
rv = loadURIDelegate->HandleLoadError(
|
||||
aURI, delegateErrorCode, NS_ERROR_GET_MODULE(delegateErrorCode),
|
||||
getter_AddRefs(errorPageURI));
|
||||
// If the docshell is going away there's no point in showing an error page.
|
||||
if (NS_FAILED(rv) || mIsBeingDestroyed) {
|
||||
*aDisplayedErrorPage = false;
|
||||
|
|
|
@ -15,20 +15,9 @@
|
|||
|
||||
namespace mozilla::dom {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(AbortController)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbortController)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal, mSignal)
|
||||
tmp->mReason.setUndefined();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbortController)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal, mSignal)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AbortController)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReason)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(AbortController,
|
||||
(mGlobal, mSignal),
|
||||
(mReason))
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(AbortController)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(AbortController)
|
||||
|
|
|
@ -566,38 +566,6 @@ void ChromeUtils::Import(const GlobalObject& aGlobal,
|
|||
aRetval.set(exports);
|
||||
}
|
||||
|
||||
/* static */
|
||||
void ChromeUtils::ImportModule(const GlobalObject& aGlobal,
|
||||
const nsAString& aResourceURI,
|
||||
JS::MutableHandle<JSObject*> aRetval,
|
||||
ErrorResult& aRv) {
|
||||
RefPtr<mozJSComponentLoader> moduleloader = mozJSComponentLoader::Get();
|
||||
MOZ_ASSERT(moduleloader);
|
||||
|
||||
NS_ConvertUTF16toUTF8 registryLocation(aResourceURI);
|
||||
|
||||
AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE(
|
||||
"ChromeUtils::ImportModule", OTHER, registryLocation);
|
||||
|
||||
JSContext* cx = aGlobal.Context();
|
||||
|
||||
JS::RootedObject moduleNamespace(cx);
|
||||
nsresult rv =
|
||||
moduleloader->ImportModule(cx, registryLocation, &moduleNamespace);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!JS_IsExceptionPending(cx));
|
||||
|
||||
if (!JS_WrapObject(cx, &moduleNamespace)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
aRetval.set(moduleNamespace);
|
||||
}
|
||||
|
||||
namespace module_getter {
|
||||
static const size_t SLOT_ID = 0;
|
||||
static const size_t SLOT_URI = 1;
|
||||
|
|
|
@ -196,11 +196,6 @@ class ChromeUtils {
|
|||
const Optional<JS::Handle<JSObject*>>& aTargetObj,
|
||||
JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv);
|
||||
|
||||
static void ImportModule(const GlobalObject& aGlobal,
|
||||
const nsAString& aResourceURI,
|
||||
JS::MutableHandle<JSObject*> aRetval,
|
||||
ErrorResult& aRv);
|
||||
|
||||
static void DefineModuleGetter(const GlobalObject& global,
|
||||
JS::Handle<JSObject*> target,
|
||||
const nsAString& id,
|
||||
|
|
|
@ -147,24 +147,9 @@ NS_INTERFACE_MAP_END
|
|||
NS_IMPL_CYCLE_COLLECTING_ADDREF(Exception)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(Exception)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(Exception)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Exception)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocation)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mData)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Exception)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mThrownJSVal)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Exception)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mData)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||
tmp->mThrownJSVal.setNull();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(Exception,
|
||||
(mLocation, mData),
|
||||
(mThrownJSVal))
|
||||
|
||||
Exception::Exception(const nsACString& aMessage, nsresult aResult,
|
||||
const nsACString& aName, nsIStackFrame* aLocation,
|
||||
|
|
|
@ -316,7 +316,8 @@ static BrowsingContextOrigin SimilarOrigin(const Element& aTarget,
|
|||
|
||||
// NOTE: This returns nullptr if |aDocument| is in another process from the top
|
||||
// level content document.
|
||||
static Document* GetTopLevelContentDocumentInThisProcess(Document& aDocument) {
|
||||
static const Document* GetTopLevelContentDocumentInThisProcess(
|
||||
const Document& aDocument) {
|
||||
auto* wc = aDocument.GetTopLevelWindowContext();
|
||||
return wc ? wc->GetExtantDoc() : nullptr;
|
||||
}
|
||||
|
@ -462,9 +463,9 @@ struct OopIframeMetrics {
|
|||
nsRect mRemoteDocumentVisibleRect;
|
||||
};
|
||||
|
||||
static Maybe<OopIframeMetrics> GetOopIframeMetrics(Document& aDocument,
|
||||
Document* aRootDocument) {
|
||||
Document* rootDoc =
|
||||
static Maybe<OopIframeMetrics> GetOopIframeMetrics(
|
||||
const Document& aDocument, const Document* aRootDocument) {
|
||||
const Document* rootDoc =
|
||||
nsContentUtils::GetInProcessSubtreeRootDocument(&aDocument);
|
||||
MOZ_ASSERT(rootDoc);
|
||||
|
||||
|
@ -522,9 +523,10 @@ static Maybe<OopIframeMetrics> GetOopIframeMetrics(Document& aDocument,
|
|||
}
|
||||
|
||||
// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
|
||||
// (step 2)
|
||||
void DOMIntersectionObserver::Update(Document* aDocument,
|
||||
DOMHighResTimeStamp time) {
|
||||
// step 2.1
|
||||
IntersectionInput DOMIntersectionObserver::ComputeInput(
|
||||
const Document& aDocument, const nsINode* aRoot,
|
||||
const StyleRect<LengthPercentage>* aRootMargin) {
|
||||
// 1 - Let rootBounds be observer's root intersection rectangle.
|
||||
// ... but since the intersection rectangle depends on the target, we defer
|
||||
// the inflation until later.
|
||||
|
@ -533,10 +535,11 @@ void DOMIntersectionObserver::Update(Document* aDocument,
|
|||
// document.
|
||||
nsRect rootRect;
|
||||
nsIFrame* rootFrame = nullptr;
|
||||
nsINode* root = mRoot;
|
||||
const nsINode* root = aRoot;
|
||||
const bool isImplicitRoot = !aRoot;
|
||||
Maybe<nsRect> remoteDocumentVisibleRect;
|
||||
if (mRoot && mRoot->IsElement()) {
|
||||
if ((rootFrame = mRoot->AsElement()->GetPrimaryFrame())) {
|
||||
if (aRoot && aRoot->IsElement()) {
|
||||
if ((rootFrame = aRoot->AsElement()->GetPrimaryFrame())) {
|
||||
nsRect rootRectRelativeToRootFrame;
|
||||
if (nsIScrollableFrame* scrollFrame = do_QueryFrame(rootFrame)) {
|
||||
// rootRectRelativeToRootFrame should be the content rect of rootFrame,
|
||||
|
@ -552,10 +555,10 @@ void DOMIntersectionObserver::Update(Document* aDocument,
|
|||
rootFrame, rootRectRelativeToRootFrame, containingBlock);
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(!mRoot || mRoot->IsDocument());
|
||||
Document* rootDocument =
|
||||
mRoot ? mRoot->AsDocument()
|
||||
: GetTopLevelContentDocumentInThisProcess(*aDocument);
|
||||
MOZ_ASSERT(!aRoot || aRoot->IsDocument());
|
||||
const Document* rootDocument =
|
||||
aRoot ? aRoot->AsDocument()
|
||||
: GetTopLevelContentDocumentInThisProcess(aDocument);
|
||||
root = rootDocument;
|
||||
|
||||
if (rootDocument) {
|
||||
|
@ -581,7 +584,7 @@ void DOMIntersectionObserver::Update(Document* aDocument,
|
|||
}
|
||||
|
||||
if (Maybe<OopIframeMetrics> metrics =
|
||||
GetOopIframeMetrics(*aDocument, rootDocument)) {
|
||||
GetOopIframeMetrics(aDocument, rootDocument)) {
|
||||
rootFrame = metrics->mInProcessRootFrame;
|
||||
if (!rootDocument) {
|
||||
rootRect = metrics->mInProcessRootRect;
|
||||
|
@ -592,101 +595,104 @@ void DOMIntersectionObserver::Update(Document* aDocument,
|
|||
|
||||
nsMargin rootMargin; // This root margin is NOT applied in `implicit root`
|
||||
// case, e.g. in out-of-process iframes.
|
||||
for (const auto side : mozilla::AllPhysicalSides()) {
|
||||
nscoord basis = side == eSideTop || side == eSideBottom ? rootRect.Height()
|
||||
: rootRect.Width();
|
||||
rootMargin.Side(side) = mRootMargin.Get(side).Resolve(
|
||||
basis, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp));
|
||||
if (aRootMargin) {
|
||||
for (const auto side : mozilla::AllPhysicalSides()) {
|
||||
nscoord basis = side == eSideTop || side == eSideBottom
|
||||
? rootRect.Height()
|
||||
: rootRect.Width();
|
||||
rootMargin.Side(side) = aRootMargin->Get(side).Resolve(
|
||||
basis, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp));
|
||||
}
|
||||
}
|
||||
return {isImplicitRoot, root, rootFrame,
|
||||
rootRect, rootMargin, remoteDocumentVisibleRect};
|
||||
}
|
||||
|
||||
// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
|
||||
// (steps 2.1 - 2.5)
|
||||
IntersectionOutput DOMIntersectionObserver::Intersect(
|
||||
const IntersectionInput& aInput, Element& aTarget) {
|
||||
const bool isSimilarOrigin = SimilarOrigin(aTarget, aInput.mRootNode) ==
|
||||
BrowsingContextOrigin::Similar;
|
||||
nsIFrame* targetFrame = aTarget.GetPrimaryFrame();
|
||||
if (!targetFrame || !aInput.mRootFrame) {
|
||||
return {isSimilarOrigin};
|
||||
}
|
||||
|
||||
// "From the perspective of an IntersectionObserver, the skipped contents
|
||||
// of an element are never intersecting the intersection root. This is
|
||||
// true even if both the root and the target elements are in the skipped
|
||||
// contents."
|
||||
// https://drafts.csswg.org/css-contain/#cv-notes
|
||||
if (targetFrame->AncestorHidesContent()) {
|
||||
return {isSimilarOrigin};
|
||||
}
|
||||
|
||||
// 2.2. If the intersection root is not the implicit root, and target is
|
||||
// not in the same Document as the intersection root, skip to step 11.
|
||||
if (!aInput.mIsImplicitRoot &&
|
||||
aInput.mRootNode->OwnerDoc() != aTarget.OwnerDoc()) {
|
||||
return {isSimilarOrigin};
|
||||
}
|
||||
|
||||
// 2.3. If the intersection root is an element and target is not a descendant
|
||||
// of the intersection root in the containing block chain, skip to step 11.
|
||||
//
|
||||
// NOTE(emilio): We also do this if target is the implicit root, pending
|
||||
// clarification in
|
||||
// https://github.com/w3c/IntersectionObserver/issues/456.
|
||||
if (aInput.mRootFrame == targetFrame ||
|
||||
!nsLayoutUtils::IsAncestorFrameCrossDocInProcess(aInput.mRootFrame,
|
||||
targetFrame)) {
|
||||
return {isSimilarOrigin};
|
||||
}
|
||||
|
||||
nsRect rootBounds = aInput.mRootRect;
|
||||
if (isSimilarOrigin) {
|
||||
rootBounds.Inflate(aInput.mRootMargin);
|
||||
}
|
||||
|
||||
// 2.4. Set targetRect to the DOMRectReadOnly obtained by running the
|
||||
// getBoundingClientRect() algorithm on target.
|
||||
nsRect targetRect = targetFrame->GetBoundingClientRect();
|
||||
|
||||
// 2.5. Let intersectionRect be the result of running the compute the
|
||||
// intersection algorithm on target and observer’s intersection root.
|
||||
Maybe<nsRect> intersectionRect =
|
||||
ComputeTheIntersection(targetFrame, aInput.mRootFrame, rootBounds,
|
||||
aInput.mRemoteDocumentVisibleRect);
|
||||
|
||||
return {isSimilarOrigin, rootBounds, targetRect, intersectionRect};
|
||||
}
|
||||
|
||||
// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
|
||||
// (step 2)
|
||||
void DOMIntersectionObserver::Update(Document* aDocument,
|
||||
DOMHighResTimeStamp time) {
|
||||
auto input = ComputeInput(*aDocument, mRoot, &mRootMargin);
|
||||
|
||||
// 2. For each target in observer’s internal [[ObservationTargets]] slot,
|
||||
// processed in the same order that observe() was called on each target:
|
||||
for (Element* target : mObservationTargets) {
|
||||
nsIFrame* targetFrame = target->GetPrimaryFrame();
|
||||
BrowsingContextOrigin origin = SimilarOrigin(*target, root);
|
||||
|
||||
Maybe<nsRect> intersectionRect;
|
||||
nsRect targetRect;
|
||||
nsRect rootBounds;
|
||||
|
||||
const bool canComputeIntersection = [&] {
|
||||
if (!targetFrame || !rootFrame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// "From the perspective of an IntersectionObserver, the skipped contents
|
||||
// of an element are never intersecting the intersection root. This is
|
||||
// true even if both the root and the target elements are in the skipped
|
||||
// contents."
|
||||
// https://drafts.csswg.org/css-contain/#cv-notes
|
||||
if (targetFrame->AncestorHidesContent()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.1. If the intersection root is not the implicit root and target is
|
||||
// not a descendant of the intersection root in the containing block
|
||||
// chain, skip further processing for target.
|
||||
//
|
||||
// NOTE(emilio): We don't just "skip further processing" because that
|
||||
// violates the invariant that there's at least one observation for a
|
||||
// target (though that is also violated by 2.2), but it also causes
|
||||
// different behavior when `target` is `display: none`, or not, which is
|
||||
// really really odd, see:
|
||||
// https://github.com/w3c/IntersectionObserver/issues/457
|
||||
//
|
||||
// NOTE(emilio): We also do this if target is the implicit root, pending
|
||||
// clarification in
|
||||
// https://github.com/w3c/IntersectionObserver/issues/456.
|
||||
if (rootFrame == targetFrame ||
|
||||
!nsLayoutUtils::IsAncestorFrameCrossDocInProcess(rootFrame,
|
||||
targetFrame)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.2. If the intersection root is not the implicit root, and target is
|
||||
// not in the same Document as the intersection root, skip further
|
||||
// processing for target.
|
||||
//
|
||||
// NOTE(emilio): We don't just "skip further processing", because that
|
||||
// doesn't match reality and other browsers, see
|
||||
// https://github.com/w3c/IntersectionObserver/issues/457.
|
||||
if (mRoot && mRoot->OwnerDoc() != target->OwnerDoc()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}();
|
||||
|
||||
if (canComputeIntersection) {
|
||||
rootBounds = rootRect;
|
||||
if (origin == BrowsingContextOrigin::Similar) {
|
||||
rootBounds.Inflate(rootMargin);
|
||||
}
|
||||
|
||||
// 2.3. Let targetRect be a DOMRectReadOnly obtained by running the
|
||||
// getBoundingClientRect() algorithm on target.
|
||||
targetRect = targetFrame->GetBoundingClientRect();
|
||||
|
||||
// 2.4. Let intersectionRect be the result of running the compute the
|
||||
// intersection algorithm on target.
|
||||
intersectionRect = ComputeTheIntersection(
|
||||
targetFrame, rootFrame, rootBounds, remoteDocumentVisibleRect);
|
||||
}
|
||||
// 2.1 - 2.4.
|
||||
IntersectionOutput output = Intersect(input, *target);
|
||||
|
||||
// 2.5. Let targetArea be targetRect’s area.
|
||||
int64_t targetArea =
|
||||
(int64_t)targetRect.Width() * (int64_t)targetRect.Height();
|
||||
int64_t targetArea = (int64_t)output.mTargetRect.Width() *
|
||||
(int64_t)output.mTargetRect.Height();
|
||||
|
||||
// 2.6. Let intersectionArea be intersectionRect’s area.
|
||||
int64_t intersectionArea = !intersectionRect
|
||||
? 0
|
||||
: (int64_t)intersectionRect->Width() *
|
||||
(int64_t)intersectionRect->Height();
|
||||
int64_t intersectionArea =
|
||||
!output.mIntersectionRect
|
||||
? 0
|
||||
: (int64_t)output.mIntersectionRect->Width() *
|
||||
(int64_t)output.mIntersectionRect->Height();
|
||||
|
||||
// 2.7. Let isIntersecting be true if targetRect and rootBounds intersect or
|
||||
// are edge-adjacent, even if the intersection has zero area (because
|
||||
// rootBounds or targetRect have zero area); otherwise, let isIntersecting
|
||||
// be false.
|
||||
const bool isIntersecting = intersectionRect.isSome();
|
||||
const bool isIntersecting = output.Intersects();
|
||||
|
||||
// 2.8. If targetArea is non-zero, let intersectionRatio be intersectionArea
|
||||
// divided by targetArea. Otherwise, let intersectionRatio be 1 if
|
||||
|
@ -729,9 +735,9 @@ void DOMIntersectionObserver::Update(Document* aDocument,
|
|||
// entry's isIntersecting value.
|
||||
QueueIntersectionObserverEntry(
|
||||
target, time,
|
||||
origin == BrowsingContextOrigin::Similar ? Some(rootBounds)
|
||||
: Nothing(),
|
||||
targetRect, intersectionRect, thresholdIndex > 0, intersectionRatio);
|
||||
output.mIsSimilarOrigin ? Some(output.mRootBounds) : Nothing(),
|
||||
output.mTargetRect, output.mIntersectionRect, thresholdIndex > 0,
|
||||
intersectionRatio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,6 +80,32 @@ class DOMIntersectionObserverEntry final : public nsISupports,
|
|||
} \
|
||||
}
|
||||
|
||||
// An input suitable to compute intersections with multiple targets.
|
||||
struct IntersectionInput {
|
||||
// Whether the root is implicit (null, originally).
|
||||
const bool mIsImplicitRoot = false;
|
||||
// The computed root node. For the implicit root, this will be the in-process
|
||||
// root document we can compute coordinates against (along with the remote
|
||||
// document visible rect if appropriate).
|
||||
const nsINode* mRootNode = nullptr;
|
||||
nsIFrame* mRootFrame = nullptr;
|
||||
// The rect of mRootFrame in client coordinates.
|
||||
nsRect mRootRect;
|
||||
// The root margin computed against the root rect.
|
||||
nsMargin mRootMargin;
|
||||
// If this is in an OOP iframe, the visible rect of the OOP frame.
|
||||
Maybe<nsRect> mRemoteDocumentVisibleRect;
|
||||
};
|
||||
|
||||
struct IntersectionOutput {
|
||||
const bool mIsSimilarOrigin;
|
||||
const nsRect mRootBounds;
|
||||
const nsRect mTargetRect;
|
||||
const Maybe<nsRect> mIntersectionRect;
|
||||
|
||||
bool Intersects() const { return mIntersectionRect.isSome(); }
|
||||
};
|
||||
|
||||
class DOMIntersectionObserver final : public nsISupports,
|
||||
public nsWrapperCache {
|
||||
virtual ~DOMIntersectionObserver() { Disconnect(); }
|
||||
|
@ -122,6 +148,11 @@ class DOMIntersectionObserver final : public nsISupports,
|
|||
|
||||
void TakeRecords(nsTArray<RefPtr<DOMIntersectionObserverEntry>>& aRetVal);
|
||||
|
||||
static IntersectionInput ComputeInput(
|
||||
const Document& aDocument, const nsINode* aRoot,
|
||||
const StyleRect<LengthPercentage>* aRootMargin);
|
||||
static IntersectionOutput Intersect(const IntersectionInput&, Element&);
|
||||
|
||||
void Update(Document* aDocument, DOMHighResTimeStamp time);
|
||||
MOZ_CAN_RUN_SCRIPT void Notify();
|
||||
|
||||
|
|
|
@ -36,26 +36,10 @@ DOMRequest::DOMRequest(nsIGlobalObject* aGlobal)
|
|||
|
||||
DOMRequest::~DOMRequest() { mozilla::DropJSObjects(this); }
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(DOMRequest)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMRequest,
|
||||
DOMEventTargetHelper)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMRequest,
|
||||
DOMEventTargetHelper)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
|
||||
tmp->mResult.setUndefined();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(DOMRequest, DOMEventTargetHelper)
|
||||
// Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because
|
||||
// DOMEventTargetHelper does it for us.
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResult)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS(DOMRequest,
|
||||
DOMEventTargetHelper,
|
||||
(mError, mPromise),
|
||||
(mResult))
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMRequest)
|
||||
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
||||
|
|
|
@ -14206,8 +14206,7 @@ void Document::TryCancelDialog() {
|
|||
// Check if the document is blocked by modal dialog
|
||||
for (const nsWeakPtr& weakPtr : Reversed(mTopLayer)) {
|
||||
nsCOMPtr<Element> element(do_QueryReferent(weakPtr));
|
||||
if (HTMLDialogElement* dialog =
|
||||
HTMLDialogElement::FromNodeOrNull(element)) {
|
||||
if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
|
||||
dialog->QueueCancelDialog();
|
||||
break;
|
||||
}
|
||||
|
@ -14532,24 +14531,22 @@ static void UpdateViewportScrollbarOverrideForFullscreen(Document* aDoc) {
|
|||
}
|
||||
}
|
||||
|
||||
static void NotifyFullScreenChangedForMediaElement(Element* aElement,
|
||||
bool aIsInFullScreen) {
|
||||
static void NotifyFullScreenChangedForMediaElement(Element& aElement) {
|
||||
// When a media element enters the fullscreen, we would like to notify that
|
||||
// to the media controller in order to update its status.
|
||||
if (!aElement->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video)) {
|
||||
return;
|
||||
if (auto* mediaElem = HTMLMediaElement::FromNode(aElement)) {
|
||||
mediaElem->NotifyFullScreenChanged();
|
||||
}
|
||||
HTMLMediaElement* mediaElem = HTMLMediaElement::FromNodeOrNull(aElement);
|
||||
mediaElem->NotifyFullScreenChanged();
|
||||
}
|
||||
|
||||
static void ClearFullscreenStateOnElement(Element* aElement) {
|
||||
/* static */
|
||||
void Document::ClearFullscreenStateOnElement(Element& aElement) {
|
||||
// Remove styles from existing top element.
|
||||
EventStateManager::SetFullscreenState(aElement, false);
|
||||
NotifyFullScreenChangedForMediaElement(aElement, false);
|
||||
aElement.RemoveStates(NS_EVENT_STATE_FULLSCREEN);
|
||||
NotifyFullScreenChangedForMediaElement(aElement);
|
||||
// Reset iframe fullscreen flag.
|
||||
if (aElement->IsHTMLElement(nsGkAtoms::iframe)) {
|
||||
static_cast<HTMLIFrameElement*>(aElement)->SetFullscreenFlag(false);
|
||||
if (auto* iframe = HTMLIFrameElement::FromNode(aElement)) {
|
||||
iframe->SetFullscreenFlag(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14569,7 +14566,7 @@ void Document::CleanupFullscreenState() {
|
|||
}
|
||||
|
||||
if (element->State().HasState(NS_EVENT_STATE_FULLSCREEN)) {
|
||||
ClearFullscreenStateOnElement(element);
|
||||
ClearFullscreenStateOnElement(*element);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -14594,29 +14591,30 @@ void Document::UnsetFullscreenElement() {
|
|||
});
|
||||
|
||||
MOZ_ASSERT(removedElement->State().HasState(NS_EVENT_STATE_FULLSCREEN));
|
||||
ClearFullscreenStateOnElement(removedElement);
|
||||
ClearFullscreenStateOnElement(*removedElement);
|
||||
UpdateViewportScrollbarOverrideForFullscreen(this);
|
||||
}
|
||||
|
||||
void Document::SetFullscreenElement(Element* aElement) {
|
||||
void Document::SetFullscreenElement(Element& aElement) {
|
||||
TopLayerPush(aElement);
|
||||
EventStateManager::SetFullscreenState(aElement, true);
|
||||
NotifyFullScreenChangedForMediaElement(aElement, true);
|
||||
aElement.AddStates(NS_EVENT_STATE_FULLSCREEN);
|
||||
NotifyFullScreenChangedForMediaElement(aElement);
|
||||
UpdateViewportScrollbarOverrideForFullscreen(this);
|
||||
}
|
||||
|
||||
void Document::TopLayerPush(Element* aElement) {
|
||||
NS_ASSERTION(aElement, "Must pass non-null to TopLayerPush()");
|
||||
void Document::TopLayerPush(Element& aElement) {
|
||||
auto predictFunc = [&aElement](Element* element) {
|
||||
return element == aElement;
|
||||
return element == &aElement;
|
||||
};
|
||||
TopLayerPop(predictFunc);
|
||||
|
||||
mTopLayer.AppendElement(do_GetWeakReference(aElement));
|
||||
NS_ASSERTION(GetTopLayerTop() == aElement, "Should match");
|
||||
mTopLayer.AppendElement(do_GetWeakReference(&aElement));
|
||||
NS_ASSERTION(GetTopLayerTop() == &aElement, "Should match");
|
||||
}
|
||||
|
||||
void Document::SetBlockedByModalDialog(HTMLDialogElement& aDialogElement) {
|
||||
void Document::AddModalDialog(HTMLDialogElement& aDialogElement) {
|
||||
TopLayerPush(aDialogElement);
|
||||
|
||||
Element* root = GetRootElement();
|
||||
MOZ_RELEASE_ASSERT(root, "dialog in document without root?");
|
||||
|
||||
|
@ -14626,7 +14624,8 @@ void Document::SetBlockedByModalDialog(HTMLDialogElement& aDialogElement) {
|
|||
// NS_EVENT_STATE_TOPMOST_MODAL_DIALOG to remove the inertness
|
||||
// explicitly.
|
||||
root->AddStates(NS_EVENT_STATE_MOZINERT);
|
||||
aDialogElement.AddStates(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
|
||||
aDialogElement.AddStates(NS_EVENT_STATE_MODAL_DIALOG |
|
||||
NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
|
||||
|
||||
// It's possible that there's another modal dialog has opened
|
||||
// previously which doesn't have the inertness (because we've
|
||||
|
@ -14646,8 +14645,16 @@ void Document::SetBlockedByModalDialog(HTMLDialogElement& aDialogElement) {
|
|||
}
|
||||
}
|
||||
|
||||
void Document::UnsetBlockedByModalDialog(HTMLDialogElement& aDialogElement) {
|
||||
aDialogElement.RemoveStates(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
|
||||
void Document::RemoveModalDialog(HTMLDialogElement& aDialogElement) {
|
||||
aDialogElement.RemoveStates(NS_EVENT_STATE_MODAL_DIALOG |
|
||||
NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
|
||||
|
||||
auto predicate = [&aDialogElement](Element* element) -> bool {
|
||||
return element == &aDialogElement;
|
||||
};
|
||||
|
||||
DebugOnly<Element*> removedElement = TopLayerPop(predicate);
|
||||
MOZ_ASSERT(removedElement == &aDialogElement);
|
||||
|
||||
// The document could still be blocked by another modal dialog.
|
||||
// We need to remove the inertness from this modal dialog.
|
||||
|
@ -14656,9 +14663,8 @@ void Document::UnsetBlockedByModalDialog(HTMLDialogElement& aDialogElement) {
|
|||
if (auto* dialog = HTMLDialogElement::FromNodeOrNull(element)) {
|
||||
if (dialog != &aDialogElement) {
|
||||
dialog->AddStates(NS_EVENT_STATE_TOPMOST_MODAL_DIALOG);
|
||||
// Return here because we want to keep the inertness for the
|
||||
// root element as the document is still blocked by a modal
|
||||
// dialog
|
||||
// Return early here because we want to keep the inertness for the root
|
||||
// element as the document is still blocked by a modal dialog.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -14670,7 +14676,7 @@ void Document::UnsetBlockedByModalDialog(HTMLDialogElement& aDialogElement) {
|
|||
}
|
||||
}
|
||||
|
||||
Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicateFunc) {
|
||||
Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicate) {
|
||||
if (mTopLayer.IsEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -14681,7 +14687,7 @@ Element* Document::TopLayerPop(FunctionRef<bool(Element*)> aPredicateFunc) {
|
|||
Element* removedElement = nullptr;
|
||||
for (auto i : Reversed(IntegerRange(mTopLayer.Length()))) {
|
||||
nsCOMPtr<Element> element(do_QueryReferent(mTopLayer[i]));
|
||||
if (element && aPredicateFunc(element)) {
|
||||
if (element && aPredicate(element)) {
|
||||
removedElement = element;
|
||||
mTopLayer.RemoveElementAt(i);
|
||||
break;
|
||||
|
@ -15182,7 +15188,7 @@ bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
|
|||
// element, and the fullscreen-ancestor styles on ancestors of the element
|
||||
// in this document.
|
||||
Element* elem = aRequest->Element();
|
||||
SetFullscreenElement(elem);
|
||||
SetFullscreenElement(*elem);
|
||||
// Set the iframe fullscreen flag.
|
||||
if (auto* iframe = HTMLIFrameElement::FromNode(elem)) {
|
||||
iframe->SetFullscreenFlag(true);
|
||||
|
@ -15229,7 +15235,7 @@ bool Document::ApplyFullscreen(UniquePtr<FullscreenRequest> aRequest) {
|
|||
}
|
||||
|
||||
Document* parent = child->GetInProcessParentDocument();
|
||||
parent->SetFullscreenElement(element);
|
||||
parent->SetFullscreenElement(*element);
|
||||
changed.AppendElement(parent);
|
||||
child = parent;
|
||||
}
|
||||
|
|
|
@ -1905,32 +1905,33 @@ class Document : public nsINode,
|
|||
void RequestFullscreenInParentProcess(UniquePtr<FullscreenRequest> aRequest,
|
||||
bool applyFullScreenDirectly);
|
||||
|
||||
static void ClearFullscreenStateOnElement(Element&);
|
||||
|
||||
// Pushes aElement onto the top layer
|
||||
void TopLayerPush(Element&);
|
||||
|
||||
// Removes the topmost element for which aPredicate returns true from the top
|
||||
// layer. The removed element, if any, is returned.
|
||||
Element* TopLayerPop(FunctionRef<bool(Element*)> aPredicate);
|
||||
|
||||
public:
|
||||
// Removes all the elements with fullscreen flag set from the top layer, and
|
||||
// clears their fullscreen flag.
|
||||
void CleanupFullscreenState();
|
||||
|
||||
// Pushes aElement onto the top layer
|
||||
void TopLayerPush(Element* aElement);
|
||||
|
||||
// Removes the topmost element which have aPredicate return true from the top
|
||||
// layer. The removed element, if any, is returned.
|
||||
Element* TopLayerPop(FunctionRef<bool(Element*)> aPredicateFunc);
|
||||
|
||||
// Pops the fullscreen element from the top layer and clears its
|
||||
// fullscreen flag.
|
||||
void UnsetFullscreenElement();
|
||||
|
||||
// Pushes the given element into the top of top layer and set fullscreen
|
||||
// flag.
|
||||
void SetFullscreenElement(Element* aElement);
|
||||
void SetFullscreenElement(Element&);
|
||||
|
||||
// Cancel the dialog element if the document is blocked by the dialog
|
||||
void TryCancelDialog();
|
||||
|
||||
void SetBlockedByModalDialog(HTMLDialogElement&);
|
||||
|
||||
void UnsetBlockedByModalDialog(HTMLDialogElement&);
|
||||
void AddModalDialog(HTMLDialogElement&);
|
||||
void RemoveModalDialog(HTMLDialogElement&);
|
||||
|
||||
/**
|
||||
* Called when a frame in a child process has entered fullscreen or when a
|
||||
|
|
|
@ -12,32 +12,10 @@
|
|||
|
||||
namespace mozilla::dom {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(Pose)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Pose)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||
tmp->mPosition = nullptr;
|
||||
tmp->mLinearVelocity = nullptr;
|
||||
tmp->mLinearAcceleration = nullptr;
|
||||
tmp->mOrientation = nullptr;
|
||||
tmp->mAngularVelocity = nullptr;
|
||||
tmp->mAngularAcceleration = nullptr;
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Pose)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Pose)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPosition)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLinearVelocity)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLinearAcceleration)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mOrientation)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAngularVelocity)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAngularAcceleration)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(
|
||||
Pose, (mParent),
|
||||
(mPosition, mLinearVelocity, mLinearAcceleration, mOrientation,
|
||||
mAngularVelocity, mAngularAcceleration))
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Pose, AddRef)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Pose, Release)
|
||||
|
|
|
@ -6991,11 +6991,11 @@ void nsContentUtils::FireMutationEventsForDirectParsing(
|
|||
}
|
||||
|
||||
/* static */
|
||||
Document* nsContentUtils::GetInProcessSubtreeRootDocument(Document* aDoc) {
|
||||
const Document* nsContentUtils::GetInProcessSubtreeRootDocument(const Document* aDoc) {
|
||||
if (!aDoc) {
|
||||
return nullptr;
|
||||
}
|
||||
Document* doc = aDoc;
|
||||
const Document* doc = aDoc;
|
||||
while (doc->GetInProcessParentDocument()) {
|
||||
doc = doc->GetInProcessParentDocument();
|
||||
}
|
||||
|
@ -7589,9 +7589,17 @@ nsresult nsContentUtils::IPCTransferableToTransferable(
|
|||
do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
const nsString& text = item.data().get_nsString();
|
||||
rv = dataWrapper->SetData(text);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (item.data().type() == IPCDataTransferData::TShmem) {
|
||||
Shmem itemData = item.data().get_Shmem();
|
||||
const nsDependentSubstring text(itemData.get<char16_t>(),
|
||||
itemData.Size<char16_t>());
|
||||
rv = dataWrapper->SetData(text);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else {
|
||||
const nsString& text = item.data().get_nsString();
|
||||
rv = dataWrapper->SetData(text);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
rv = aTransferable->SetTransferData(item.flavor().get(), dataWrapper);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
@ -7673,6 +7681,13 @@ nsresult nsContentUtils::IPCTransferableItemToVariant(
|
|||
});
|
||||
|
||||
if (aDataTransferItem.dataType() == TransferableDataType::String) {
|
||||
if (aDataTransferItem.data().type() == IPCDataTransferData::TShmem) {
|
||||
Shmem data = aDataTransferItem.data().get_Shmem();
|
||||
aVariant->SetAsAString(
|
||||
nsDependentSubstring(data.get<char16_t>(), data.Size<char16_t>()));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
const nsString& data = aDataTransferItem.data().get_nsString();
|
||||
aVariant->SetAsAString(data);
|
||||
return NS_OK;
|
||||
|
@ -7851,17 +7866,23 @@ bool nsContentUtils::IsFlavorImage(const nsACString& aFlavor) {
|
|||
aFlavor.EqualsLiteral(kGIFImageMime);
|
||||
}
|
||||
|
||||
static Shmem ConvertToShmem(mozilla::dom::ContentChild* aChild,
|
||||
mozilla::dom::ContentParent* aParent,
|
||||
const nsACString& aInput) {
|
||||
static bool AllocateShmem(mozilla::dom::ContentChild* aChild,
|
||||
mozilla::dom::ContentParent* aParent, size_t aSize,
|
||||
mozilla::ipc::Shmem* aShmem) {
|
||||
MOZ_ASSERT((aChild && !aParent) || (!aChild && aParent));
|
||||
MOZ_ASSERT(aShmem);
|
||||
|
||||
IShmemAllocator* allocator = aChild ? static_cast<IShmemAllocator*>(aChild)
|
||||
: static_cast<IShmemAllocator*>(aParent);
|
||||
|
||||
return allocator->AllocShmem(aSize, SharedMemory::TYPE_BASIC, aShmem);
|
||||
}
|
||||
|
||||
static Shmem ConvertToShmem(mozilla::dom::ContentChild* aChild,
|
||||
mozilla::dom::ContentParent* aParent,
|
||||
const nsACString& aInput) {
|
||||
Shmem result;
|
||||
if (!allocator->AllocShmem(aInput.Length(), SharedMemory::TYPE_BASIC,
|
||||
&result)) {
|
||||
if (!AllocateShmem(aChild, aParent, aInput.Length(), &result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -7870,6 +7891,20 @@ static Shmem ConvertToShmem(mozilla::dom::ContentChild* aChild,
|
|||
return result;
|
||||
}
|
||||
|
||||
static Shmem ConvertToShmem(mozilla::dom::ContentChild* aChild,
|
||||
mozilla::dom::ContentParent* aParent,
|
||||
const nsAString& aInput) {
|
||||
Shmem result;
|
||||
uint32_t size = aInput.Length() * sizeof(char16_t);
|
||||
if (!AllocateShmem(aChild, aParent, size, &result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
memcpy(result.get<char>(), aInput.BeginReading(), size);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void nsContentUtils::TransferableToIPCTransferable(
|
||||
nsITransferable* aTransferable, IPCDataTransfer* aIPCDataTransfer,
|
||||
bool aInSyncMessage, mozilla::dom::ContentChild* aChild,
|
||||
|
@ -7919,9 +7954,31 @@ void nsContentUtils::TransferableToIPCTransferable(
|
|||
if (nsCOMPtr<nsISupportsString> text = do_QueryInterface(data)) {
|
||||
nsAutoString dataAsString;
|
||||
text->GetData(dataAsString);
|
||||
|
||||
Maybe<Shmem> dataAsShmem;
|
||||
uint32_t size = dataAsString.Length() * sizeof(char16_t);
|
||||
// XXX IPCDataTransfer could contain multiple items, we give each item
|
||||
// same bucket size. The IPC message includes more than data payload, so
|
||||
// subtract 10 KB to make the total size within the bucket size. It
|
||||
// would be nice if we could have a smarter way to decide when to use
|
||||
// Shmem.
|
||||
uint32_t threshold =
|
||||
(IPC::Channel::kMaximumMessageSize / flavorList.Length()) -
|
||||
(10 * 1024);
|
||||
if (size > threshold) {
|
||||
dataAsShmem.emplace(ConvertToShmem(aChild, aParent, dataAsString));
|
||||
if (!dataAsShmem->IsReadable() || !dataAsShmem->Size<char16_t>()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
|
||||
item->flavor() = flavorStr;
|
||||
item->data() = dataAsString;
|
||||
if (dataAsShmem) {
|
||||
item->data() = dataAsShmem.value();
|
||||
} else {
|
||||
item->data() = dataAsString;
|
||||
}
|
||||
item->dataType() = TransferableDataType::String;
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -2459,7 +2459,11 @@ class nsContentUtils {
|
|||
* Returns the in-process subtree root document in a document hierarchy.
|
||||
* This could be a chrome document.
|
||||
*/
|
||||
static Document* GetInProcessSubtreeRootDocument(Document* aDoc);
|
||||
static Document* GetInProcessSubtreeRootDocument(Document* aDoc) {
|
||||
return const_cast<Document*>(
|
||||
GetInProcessSubtreeRootDocument(const_cast<const Document*>(aDoc)));
|
||||
}
|
||||
static const Document* GetInProcessSubtreeRootDocument(const Document* aDoc);
|
||||
|
||||
static void GetShiftText(nsAString& text);
|
||||
static void GetControlText(nsAString& text);
|
||||
|
|
|
@ -569,21 +569,12 @@ class IdleRequestExecutor final : public nsIRunnable,
|
|||
Maybe<int32_t> mDelayedExecutorHandle;
|
||||
};
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(IdleRequestExecutor)
|
||||
NS_IMPL_CYCLE_COLLECTION(IdleRequestExecutor, mWindow,
|
||||
mDelayedExecutorDispatcher)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequestExecutor)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequestExecutor)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IdleRequestExecutor)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDelayedExecutorDispatcher)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IdleRequestExecutor)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDelayedExecutorDispatcher)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutor)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIRunnable)
|
||||
NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable)
|
||||
|
|
|
@ -495,6 +495,7 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsWrapperCache, NS_WRAPPERCACHE_IID)
|
|||
class_, native_members_, js_members_) \
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(class_) \
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(class_) \
|
||||
using ::ImplCycleCollectionUnlink; \
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK( \
|
||||
MOZ_FOR_EACH_EXPAND_HELPER native_members_) \
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(MOZ_FOR_EACH_EXPAND_HELPER js_members_) \
|
||||
|
|
|
@ -145,9 +145,22 @@ bool OffscreenCanvasDisplayHelper::CommitFrameToCompositor(
|
|||
} else {
|
||||
surface = aContext->GetFrontBufferSnapshot(/* requireAlphaPremult */ false);
|
||||
if (surface) {
|
||||
auto surfaceImage = MakeRefPtr<layers::SourceSurfaceImage>(surface);
|
||||
surfaceImage->SetTextureFlags(flags);
|
||||
image = surfaceImage;
|
||||
bool usable = true;
|
||||
if (surface->GetType() == gfx::SurfaceType::WEBGL) {
|
||||
// Ensure we can map in the surface. If we get a SourceSurfaceWebgl
|
||||
// surface, then it may not be backed by raw pixels yet. We need to map
|
||||
// it on the owning thread rather than the ImageBridge thread.
|
||||
gfx::DataSourceSurface::ScopedMap map(
|
||||
static_cast<gfx::DataSourceSurface*>(surface.get()),
|
||||
gfx::DataSourceSurface::READ);
|
||||
usable = map.IsMapped();
|
||||
}
|
||||
|
||||
if (usable) {
|
||||
auto surfaceImage = MakeRefPtr<layers::SourceSurfaceImage>(surface);
|
||||
surfaceImage->SetTextureFlags(flags);
|
||||
image = surfaceImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -428,20 +428,6 @@ partial namespace ChromeUtils {
|
|||
[Throws]
|
||||
object import(UTF8String aResourceURI, optional object aTargetObj);
|
||||
|
||||
/**
|
||||
* Synchronously loads and evaluates the JS module source located at
|
||||
* 'aResourceURI'.
|
||||
*
|
||||
* @param aResourceURI A resource:// URI string to load the module from.
|
||||
* @returns the module's namespace object.
|
||||
*
|
||||
* The implementation maintains a hash of aResourceURI->global obj.
|
||||
* Subsequent invocations of import with 'aResourceURI' pointing to
|
||||
* the same file will not cause the module to be re-evaluated.
|
||||
*/
|
||||
[Throws]
|
||||
object importModule(DOMString aResourceURI);
|
||||
|
||||
/**
|
||||
* Defines a property on the given target which lazily imports a JavaScript
|
||||
* module when accessed.
|
||||
|
|
|
@ -97,22 +97,9 @@ EventListenerInfo::EventListenerInfo(
|
|||
|
||||
EventListenerInfo::~EventListenerInfo() { DropJSObjects(this); }
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(EventListenerInfo)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EventListenerInfo)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EventListenerInfo)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mListenerManager)
|
||||
tmp->mScriptedListener = nullptr;
|
||||
tmp->mScriptedListenerGlobal = nullptr;
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(EventListenerInfo)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScriptedListener)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScriptedListenerGlobal)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(EventListenerInfo, (mListenerManager),
|
||||
(mScriptedListener,
|
||||
mScriptedListenerGlobal))
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventListenerInfo)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIEventListenerInfo)
|
||||
|
|
|
@ -5540,12 +5540,6 @@ static Element* GetLabelTarget(nsIContent* aPossibleLabel) {
|
|||
return label->GetLabeledElement();
|
||||
}
|
||||
|
||||
/* static */
|
||||
void EventStateManager::SetFullscreenState(Element* aElement,
|
||||
bool aIsFullscreen) {
|
||||
DoStateChange(aElement, NS_EVENT_STATE_FULLSCREEN, aIsFullscreen);
|
||||
}
|
||||
|
||||
/* static */
|
||||
inline void EventStateManager::DoStateChange(Element* aElement,
|
||||
EventStates aState,
|
||||
|
|
|
@ -286,9 +286,6 @@ class EventStateManager : public nsSupportsWeakReference, public nsIObserver {
|
|||
static void SetActiveManager(EventStateManager* aNewESM,
|
||||
nsIContent* aContent);
|
||||
|
||||
// Sets the fullscreen event state on aElement to aIsFullscreen.
|
||||
static void SetFullscreenState(dom::Element* aElement, bool aIsFullscreen);
|
||||
|
||||
static bool IsRemoteTarget(nsIContent* target);
|
||||
|
||||
static bool IsTopLevelRemoteTarget(nsIContent* aTarget);
|
||||
|
|
|
@ -13,24 +13,9 @@
|
|||
|
||||
namespace mozilla::dom {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(GamepadTouch)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(GamepadTouch)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||
tmp->mPosition = nullptr;
|
||||
tmp->mSurfaceDimensions = nullptr;
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(GamepadTouch)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(GamepadTouch)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPosition)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mSurfaceDimensions)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(GamepadTouch, (mParent),
|
||||
(mPosition,
|
||||
mSurfaceDimensions))
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(GamepadTouch, AddRef)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(GamepadTouch, Release)
|
||||
|
|
|
@ -85,27 +85,19 @@ bool HTMLDialogElement::IsInTopLayer() const {
|
|||
}
|
||||
|
||||
void HTMLDialogElement::AddToTopLayerIfNeeded() {
|
||||
MOZ_ASSERT(IsInComposedDoc());
|
||||
if (IsInTopLayer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Document* doc = OwnerDoc();
|
||||
doc->TopLayerPush(this);
|
||||
doc->SetBlockedByModalDialog(*this);
|
||||
AddStates(NS_EVENT_STATE_MODAL_DIALOG);
|
||||
OwnerDoc()->AddModalDialog(*this);
|
||||
}
|
||||
|
||||
void HTMLDialogElement::RemoveFromTopLayerIfNeeded() {
|
||||
if (!IsInTopLayer()) {
|
||||
return;
|
||||
}
|
||||
auto predictFunc = [&](Element* element) { return element == this; };
|
||||
|
||||
Document* doc = OwnerDoc();
|
||||
DebugOnly<Element*> removedElement = doc->TopLayerPop(predictFunc);
|
||||
MOZ_ASSERT(removedElement == this);
|
||||
RemoveStates(NS_EVENT_STATE_MODAL_DIALOG);
|
||||
doc->UnsetBlockedByModalDialog(*this);
|
||||
OwnerDoc()->RemoveModalDialog(*this);
|
||||
}
|
||||
|
||||
void HTMLDialogElement::StorePreviouslyFocusedElement() {
|
||||
|
|
|
@ -532,7 +532,8 @@ RemoteDecoderManagerChild::LaunchUtilityProcessIfNeeded() {
|
|||
managerThread, __func__,
|
||||
[](ipc::PBackgroundChild::
|
||||
EnsureUtilityProcessAndCreateBridgePromise::
|
||||
ResolveOrRejectValue&& aResult) {
|
||||
ResolveOrRejectValue&& aResult)
|
||||
-> RefPtr<GenericNonExclusivePromise> {
|
||||
nsCOMPtr<nsISerialEventTarget> managerThread = GetManagerThread();
|
||||
if (!managerThread || aResult.IsReject()) {
|
||||
// The parent process died or we got shutdown
|
||||
|
|
|
@ -18,22 +18,12 @@
|
|||
|
||||
namespace mozilla::dom {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(MIDIMessageEvent)
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS(MIDIMessageEvent, Event, (),
|
||||
(mData))
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(MIDIMessageEvent, Event)
|
||||
NS_IMPL_RELEASE_INHERITED(MIDIMessageEvent, Event)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MIDIMessageEvent, Event)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(MIDIMessageEvent, Event)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MIDIMessageEvent, Event)
|
||||
tmp->mData = nullptr;
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MIDIMessageEvent)
|
||||
NS_INTERFACE_MAP_END_INHERITING(Event)
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "mozilla/HoldDropJSObjects.h"
|
||||
#include "mozilla/dom/PushUtil.h"
|
||||
#include "nsIGlobalObject.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
|
@ -30,19 +31,9 @@ PushSubscriptionOptions::~PushSubscriptionOptions() {
|
|||
mozilla::DropJSObjects(this);
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(PushSubscriptionOptions)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PushSubscriptionOptions)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||
tmp->mAppServerKey = nullptr;
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PushSubscriptionOptions)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(PushSubscriptionOptions)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAppServerKey)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(PushSubscriptionOptions,
|
||||
(mGlobal),
|
||||
(mAppServerKey))
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscriptionOptions)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscriptionOptions)
|
||||
|
|
|
@ -1291,9 +1291,11 @@ static nsresult CheckAllowFileProtocolScriptLoad(nsIChannel* aChannel) {
|
|||
|
||||
if (!nsContentUtils::IsJavascriptMIMEType(
|
||||
NS_ConvertUTF8toUTF16(contentType))) {
|
||||
Telemetry::Accumulate(Telemetry::SCRIPT_FILE_PROTOCOL_CORRECT_MIME, false);
|
||||
return NS_ERROR_CONTENT_BLOCKED;
|
||||
}
|
||||
|
||||
Telemetry::Accumulate(Telemetry::SCRIPT_FILE_PROTOCOL_CORRECT_MIME, true);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -40,11 +40,16 @@ support-files =
|
|||
file_framing_error_pages.sjs
|
||||
[browser_same_site_cookies_bug1748693.js]
|
||||
support-files =
|
||||
file_same_site_cookies_bug1748693.sjs
|
||||
file_same_site_cookies_bug1748693.sjs
|
||||
[browser_file_nonscript.js]
|
||||
support-files =
|
||||
file_loads_nonscript.html
|
||||
file_nonscript
|
||||
file_nonscript.xyz
|
||||
file_nonscript.html
|
||||
file_nonscript.txt
|
||||
file_nonscript.json
|
||||
file_script.js
|
||||
[browser_restrict_privileged_about_script.js]
|
||||
# This test intentionally asserts when in debug builds. Let's rely on opt builds when in CI.
|
||||
skip-if = debug
|
||||
|
|
|
@ -13,9 +13,22 @@ add_task(async function test_fileurl_nonscript_load() {
|
|||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
let ran = await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
|
||||
return content.window.wrappedJSObject.ran;
|
||||
let counter = await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
|
||||
Cu.exportFunction(Assert.equal.bind(Assert), content.window, {
|
||||
defineAs: "equal",
|
||||
});
|
||||
content.window.postMessage("run", "*");
|
||||
|
||||
await new Promise(resolve => {
|
||||
content.window.addEventListener("message", event => {
|
||||
if (event.data === "done") {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return content.window.wrappedJSObject.counter;
|
||||
});
|
||||
|
||||
is(ran, "error", "Script should not have run");
|
||||
is(counter, 1, "Only one script should have run");
|
||||
});
|
||||
|
|
|
@ -5,7 +5,45 @@
|
|||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.ran = 'before';
|
||||
/* global equal */
|
||||
|
||||
const files = ["file_nonscript",
|
||||
"file_nonscript.xyz",
|
||||
"file_nonscript.html",
|
||||
"file_nonscript.txt",
|
||||
"file_nonscript.json"];
|
||||
|
||||
async function run() {
|
||||
window.counter = 0;
|
||||
|
||||
for (let file of files) {
|
||||
let script = document.createElement("script");
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
script.addEventListener("error", resolve, {once: true});
|
||||
script.addEventListener("load", reject, {once: true});
|
||||
});
|
||||
script.src = file;
|
||||
document.body.append(script);
|
||||
|
||||
let event = await promise;
|
||||
equal(event.type, "error");
|
||||
equal(window.counter, 0);
|
||||
}
|
||||
|
||||
let script = document.createElement("script");
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
script.addEventListener("load", resolve, {once: true});
|
||||
script.addEventListener("error", reject, {once: true});
|
||||
});
|
||||
script.src = "file_script.js";
|
||||
document.body.append(script);
|
||||
|
||||
let event = await promise;
|
||||
equal(event.type, "load");
|
||||
equal(window.counter, 1);
|
||||
|
||||
window.postMessage("done", "*");
|
||||
}
|
||||
window.addEventListener("message", run, {once: true})
|
||||
</script>
|
||||
<script src="file_nonscript.xyz" onerror="window.ran = 'error'"></script>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
window.counter++;
|
|
@ -0,0 +1 @@
|
|||
window.counter++;
|
|
@ -0,0 +1 @@
|
|||
window.counter++;
|
|
@ -0,0 +1 @@
|
|||
window.counter++;
|
|
@ -1 +1 @@
|
|||
window.ran = 'ran';
|
||||
window.counter++;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
window.counter++;
|
|
@ -26,9 +26,6 @@ struct PipeToReadRequest;
|
|||
class WriteFinishedPromiseHandler;
|
||||
class ShutdownActionFinishedPromiseHandler;
|
||||
|
||||
// TODO: Bug 1756794
|
||||
using ::ImplCycleCollectionUnlink;
|
||||
|
||||
// https://streams.spec.whatwg.org/#readable-stream-pipe-to (Steps 14-15.)
|
||||
//
|
||||
// This class implements everything that is required to read all chunks from
|
||||
|
@ -649,16 +646,7 @@ struct PipeToReadRequest : public ReadRequest {
|
|||
virtual ~PipeToReadRequest() = default;
|
||||
};
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(PipeToReadRequest)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PipeToReadRequest, ReadRequest)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPipeToPump)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PipeToReadRequest,
|
||||
ReadRequest)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPipeToPump)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(PipeToReadRequest, ReadRequest, mPipeToPump)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(PipeToReadRequest, ReadRequest)
|
||||
NS_IMPL_RELEASE_INHERITED(PipeToReadRequest, ReadRequest)
|
||||
|
|
|
@ -14,9 +14,6 @@
|
|||
|
||||
namespace mozilla::dom {
|
||||
|
||||
// TODO: Bug 1756794
|
||||
using ::ImplCycleCollectionUnlink;
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(TeeState,
|
||||
(mStream, mReader, mBranch1, mBranch2,
|
||||
mCancelPromise),
|
||||
|
|
|
@ -14,5 +14,7 @@ prefs=
|
|||
[proper-realm-cancel.js]
|
||||
[proper-realm-pull.js]
|
||||
[large-pipeto.js]
|
||||
skip-if = os == "win"
|
||||
skip-if =
|
||||
os == "win"
|
||||
tsan # Causes claim expired errors; see Bug 1770170.
|
||||
[too-big-array-buffer.js]
|
||||
|
|
|
@ -124,22 +124,9 @@ JSObject* VRFieldOfView::WrapObject(JSContext* aCx,
|
|||
return VRFieldOfView_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(VREyeParameters)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(VREyeParameters)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent, mFOV)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||
tmp->mOffset = nullptr;
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(VREyeParameters)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent, mFOV)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(VREyeParameters)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mOffset)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(VREyeParameters,
|
||||
(mParent, mFOV),
|
||||
(mOffset))
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VREyeParameters, AddRef)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VREyeParameters, Release)
|
||||
|
|
|
@ -9,25 +9,14 @@
|
|||
#include "mozilla/dom/Pose.h"
|
||||
#include "mozilla/dom/DOMPointBinding.h"
|
||||
#include "mozilla/HoldDropJSObjects.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(XRRigidTransform)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(XRRigidTransform)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent, mPosition, mOrientation, mInverse)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||
tmp->mMatrixArray = nullptr;
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(XRRigidTransform)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent, mPosition, mOrientation, mInverse)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(XRRigidTransform)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMatrixArray)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(XRRigidTransform,
|
||||
(mParent, mPosition,
|
||||
mOrientation, mInverse),
|
||||
(mMatrixArray))
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(XRRigidTransform, AddRef)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(XRRigidTransform, Release)
|
||||
|
|
|
@ -9,25 +9,13 @@
|
|||
#include "mozilla/HoldDropJSObjects.h"
|
||||
#include "mozilla/dom/XRRigidTransform.h"
|
||||
#include "mozilla/dom/Pose.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(XRView)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(XRView)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent, mTransform)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||
tmp->mJSProjectionMatrix = nullptr;
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(XRView)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent, mTransform)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(XRView)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJSProjectionMatrix)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(XRView,
|
||||
(mParent, mTransform),
|
||||
(mJSProjectionMatrix))
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(XRView, AddRef)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(XRView, Release)
|
||||
|
|
|
@ -7,24 +7,12 @@
|
|||
#include "mozilla/dom/AuthenticatorResponse.h"
|
||||
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(AuthenticatorResponse)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AuthenticatorResponse)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||
tmp->mClientDataJSONCachedObj = nullptr;
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AuthenticatorResponse)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mClientDataJSONCachedObj)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AuthenticatorResponse)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(
|
||||
AuthenticatorResponse, (mParent), (mClientDataJSONCachedObj))
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(AuthenticatorResponse)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(AuthenticatorResponse)
|
||||
|
|
|
@ -242,10 +242,11 @@ bool GLXLibrary::SupportsVideoSync(Display* aDisplay) {
|
|||
}
|
||||
|
||||
static int (*sOldErrorHandler)(Display*, XErrorEvent*);
|
||||
ScopedXErrorHandler::ErrorEvent sErrorEvent;
|
||||
static XErrorEvent sErrorEvent = {};
|
||||
|
||||
static int GLXErrorHandler(Display* display, XErrorEvent* ev) {
|
||||
if (!sErrorEvent.mError.error_code) {
|
||||
sErrorEvent.mError = *ev;
|
||||
if (!sErrorEvent.error_code) {
|
||||
sErrorEvent = *ev;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -264,21 +265,23 @@ GLXLibrary::WrapperScope::~WrapperScope() {
|
|||
if (mDisplay) {
|
||||
FinishX(mDisplay);
|
||||
}
|
||||
if (sErrorEvent.mError.error_code) {
|
||||
if (sErrorEvent.error_code) {
|
||||
char buffer[100] = {};
|
||||
if (mDisplay) {
|
||||
XGetErrorText(mDisplay, sErrorEvent.mError.error_code, buffer,
|
||||
sizeof(buffer));
|
||||
XGetErrorText(mDisplay, sErrorEvent.error_code, buffer, sizeof(buffer));
|
||||
} else {
|
||||
SprintfLiteral(buffer, "%d", sErrorEvent.mError.error_code);
|
||||
SprintfLiteral(buffer, "%d", sErrorEvent.error_code);
|
||||
}
|
||||
printf_stderr("X ERROR after %s: %s (%i) - Request: %i.%i, Serial: %lu",
|
||||
mFuncName, buffer, sErrorEvent.mError.error_code,
|
||||
sErrorEvent.mError.request_code,
|
||||
sErrorEvent.mError.minor_code, sErrorEvent.mError.serial);
|
||||
mFuncName, buffer, sErrorEvent.error_code,
|
||||
sErrorEvent.request_code, sErrorEvent.minor_code,
|
||||
sErrorEvent.serial);
|
||||
MOZ_ASSERT_UNREACHABLE("AfterGLXCall sErrorEvent");
|
||||
}
|
||||
XSetErrorHandler(sOldErrorHandler);
|
||||
const auto was = XSetErrorHandler(sOldErrorHandler);
|
||||
if (was != GLXErrorHandler) {
|
||||
NS_WARNING("Concurrent XSetErrorHandlers");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -338,24 +341,17 @@ already_AddRefed<GLContextGLX> GLContextGLX::CreateGLContext(
|
|||
|
||||
const auto CreateWithAttribs =
|
||||
[&](const std::vector<int>& attribs) -> RefPtr<GLContextGLX> {
|
||||
OffMainThreadScopedXErrorHandler handler;
|
||||
|
||||
auto terminated = attribs;
|
||||
terminated.push_back(0);
|
||||
|
||||
// X Errors can happen even if this context creation returns non-null, and
|
||||
// we should not try to use such contexts. (Errors may come from the
|
||||
// distant server, or something)
|
||||
const auto glxContext = glx.fCreateContextAttribs(
|
||||
*display, cfg, nullptr, X11True, terminated.data());
|
||||
if (!glxContext) return nullptr;
|
||||
const RefPtr<GLContextGLX> ret =
|
||||
new GLContextGLX(desc, display, drawable, glxContext, deleteDrawable,
|
||||
isDoubleBuffered, pixmap);
|
||||
if (handler.SyncAndGetError(*display)) return nullptr;
|
||||
|
||||
if (!ret->Init()) return nullptr;
|
||||
if (handler.SyncAndGetError(*display)) return nullptr;
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
@ -439,13 +435,12 @@ GLContextGLX::~GLContextGLX() {
|
|||
}
|
||||
|
||||
// see bug 659842 comment 76
|
||||
#ifdef DEBUG
|
||||
bool success =
|
||||
#endif
|
||||
mGLX->fMakeCurrent(*mDisplay, X11None, nullptr);
|
||||
MOZ_ASSERT(success,
|
||||
"glXMakeCurrent failed to release GL context before we call "
|
||||
"glXDestroyContext!");
|
||||
bool success = mGLX->fMakeCurrent(*mDisplay, X11None, nullptr);
|
||||
if (!success) {
|
||||
NS_WARNING(
|
||||
"glXMakeCurrent failed to release GL context before we call "
|
||||
"glXDestroyContext!");
|
||||
}
|
||||
|
||||
mGLX->fDestroyContext(*mDisplay, mContext);
|
||||
|
||||
|
@ -476,7 +471,9 @@ bool GLContextGLX::MakeCurrentImpl() const {
|
|||
}
|
||||
|
||||
const bool succeeded = mGLX->fMakeCurrent(*mDisplay, mDrawable, mContext);
|
||||
NS_ASSERTION(succeeded, "Failed to make GL context current!");
|
||||
if (!succeeded) {
|
||||
NS_WARNING("Failed to make GL context current!");
|
||||
}
|
||||
|
||||
if (!IsOffscreen() && mGLX->SupportsSwapControl()) {
|
||||
// Many GLX implementations default to blocking until the next
|
||||
|
@ -868,14 +865,12 @@ static already_AddRefed<GLContextGLX> CreateOffscreenPixmapContext(
|
|||
int depth;
|
||||
FindVisualAndDepth(*display, visid, &visual, &depth);
|
||||
|
||||
OffMainThreadScopedXErrorHandler xErrorHandler;
|
||||
bool error = false;
|
||||
|
||||
gfx::IntSize dummySize(16, 16);
|
||||
RefPtr<gfxXlibSurface> surface = gfxXlibSurface::Create(
|
||||
display, DefaultScreenOfDisplay(display->get()), visual, dummySize);
|
||||
if (surface->CairoStatus() != 0) {
|
||||
mozilla::Unused << xErrorHandler.SyncAndGetError(*display);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -888,8 +883,7 @@ static already_AddRefed<GLContextGLX> CreateOffscreenPixmapContext(
|
|||
error = true;
|
||||
}
|
||||
|
||||
bool serverError = xErrorHandler.SyncAndGetError(*display);
|
||||
if (error || serverError) return nullptr;
|
||||
if (error) return nullptr;
|
||||
|
||||
auto fullDesc = GLContextDesc{desc};
|
||||
fullDesc.isOffscreen = true;
|
||||
|
|
|
@ -39,43 +39,4 @@ void FinishX(Display* aDisplay) {
|
|||
XSync(aDisplay, X11False);
|
||||
}
|
||||
|
||||
ScopedXErrorHandler::ErrorEvent* ScopedXErrorHandler::sXErrorPtr;
|
||||
|
||||
int ScopedXErrorHandler::ErrorHandler(Display*, XErrorEvent* ev) {
|
||||
// only record the error if no error was previously recorded.
|
||||
// this means that in case of multiple errors, it's the first error that we
|
||||
// report.
|
||||
if (!sXErrorPtr->mError.error_code) sXErrorPtr->mError = *ev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ScopedXErrorHandler::ScopedXErrorHandler(bool aAllowOffMainThread) {
|
||||
if (!aAllowOffMainThread) {
|
||||
// Off main thread usage is not safe in general, but OMTC GL layers uses
|
||||
// this with the main thread blocked, which makes it safe.
|
||||
NS_WARNING_ASSERTION(
|
||||
NS_IsMainThread(),
|
||||
"ScopedXErrorHandler being called off main thread, may cause issues");
|
||||
}
|
||||
// let sXErrorPtr point to this object's mXError object, but don't reset this
|
||||
// mXError object! think of the case of nested ScopedXErrorHandler's.
|
||||
mOldXErrorPtr = sXErrorPtr;
|
||||
sXErrorPtr = &mXError;
|
||||
mOldErrorHandler = XSetErrorHandler(ErrorHandler);
|
||||
}
|
||||
|
||||
ScopedXErrorHandler::~ScopedXErrorHandler() {
|
||||
sXErrorPtr = mOldXErrorPtr;
|
||||
XSetErrorHandler(mOldErrorHandler);
|
||||
}
|
||||
|
||||
bool ScopedXErrorHandler::SyncAndGetError(Display* dpy, XErrorEvent* ev) {
|
||||
FinishX(dpy);
|
||||
|
||||
bool retval = mXError.mError.error_code != 0;
|
||||
if (ev) *ev = mXError.mError;
|
||||
mXError = ErrorEvent(); // reset
|
||||
return retval;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -70,75 +70,6 @@ struct ScopedXFreePtrTraits {
|
|||
};
|
||||
SCOPED_TEMPLATE(ScopedXFree, ScopedXFreePtrTraits)
|
||||
|
||||
/**
|
||||
* On construction, set a graceful X error handler that doesn't crash the
|
||||
* application and records X errors. On destruction, restore the X error handler
|
||||
* to what it was before construction.
|
||||
*
|
||||
* The SyncAndGetError() method allows to know whether a X error occurred,
|
||||
* optionally allows to get the full XErrorEvent, and resets the recorded X
|
||||
* error state so that a single X error will be reported only once.
|
||||
*
|
||||
* Nesting is correctly handled: multiple nested ScopedXErrorHandler's don't
|
||||
* interfere with each other's state. However, if SyncAndGetError is not called
|
||||
* on the nested ScopedXErrorHandler, then any X errors caused by X calls made
|
||||
* while the nested ScopedXErrorHandler was in place may then be caught by the
|
||||
* other ScopedXErrorHandler. This is just a result of X being asynchronous and
|
||||
* us not doing any implicit syncing: the only method in this class what causes
|
||||
* syncing is SyncAndGetError().
|
||||
*
|
||||
* This class is not thread-safe at all. It is assumed that only one thread is
|
||||
* using any ScopedXErrorHandler's. Given that it's not used on Mac, it should
|
||||
* be easy to make it thread-safe by using thread-local storage with __thread.
|
||||
*/
|
||||
class ScopedXErrorHandler {
|
||||
public:
|
||||
// trivial wrapper around XErrorEvent, just adding ctor initializing by zero.
|
||||
struct ErrorEvent {
|
||||
XErrorEvent mError;
|
||||
|
||||
ErrorEvent() { memset(this, 0, sizeof(ErrorEvent)); }
|
||||
};
|
||||
|
||||
private:
|
||||
// this ScopedXErrorHandler's ErrorEvent object
|
||||
ErrorEvent mXError;
|
||||
|
||||
// static pointer for use by the error handler
|
||||
static ErrorEvent* sXErrorPtr;
|
||||
|
||||
// what to restore sXErrorPtr to on destruction
|
||||
ErrorEvent* mOldXErrorPtr;
|
||||
|
||||
// what to restore the error handler to on destruction
|
||||
int (*mOldErrorHandler)(Display*, XErrorEvent*);
|
||||
|
||||
public:
|
||||
static int ErrorHandler(Display*, XErrorEvent* ev);
|
||||
|
||||
/**
|
||||
* @param aAllowOffMainThread whether to warn if used off main thread
|
||||
*/
|
||||
explicit ScopedXErrorHandler(bool aAllowOffMainThread = false);
|
||||
|
||||
~ScopedXErrorHandler();
|
||||
|
||||
/** \returns true if a X error occurred since the last time this method was
|
||||
* called on this ScopedXErrorHandler object, or since the creation of this
|
||||
* ScopedXErrorHandler object if this method was never called on it.
|
||||
*
|
||||
* \param ev this optional parameter, if set, will be filled with the
|
||||
* XErrorEvent object. If multiple errors occurred, the first one will be
|
||||
* returned.
|
||||
*/
|
||||
bool SyncAndGetError(Display* dpy, XErrorEvent* ev = nullptr);
|
||||
};
|
||||
|
||||
class OffMainThreadScopedXErrorHandler : public ScopedXErrorHandler {
|
||||
public:
|
||||
OffMainThreadScopedXErrorHandler() : ScopedXErrorHandler(true) {}
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_X11Util_h
|
||||
|
|
|
@ -2,10 +2,17 @@ header = """/* This Source Code Form is subject to the terms of the Mozilla Publ
|
|||
* 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/. */"""
|
||||
autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen.
|
||||
* To generate this file:
|
||||
* 1. Get the latest cbindgen using `cargo install --force cbindgen`
|
||||
* a. Alternatively, you can clone `https://github.com/eqrion/cbindgen` and use a tagged release
|
||||
* 2. Run `rustup run nightly cbindgen toolkit/library/rust/ --lockfile Cargo.lock --crate wgpu_bindings -o dom/webgpu/ffi/wgpu_ffi_generated.h`
|
||||
*
|
||||
* This file is generated based on the configuration in
|
||||
* `gfx/wgpu_bindings/moz.build`, which directs the build system module
|
||||
* `build/RunCbindgen.py` to run the following command at the top of
|
||||
* the object file directory:
|
||||
*
|
||||
* $CBINDGEN $TOPSRCDIR --lockfile $TOPSRCDIR/Cargo.lock --crate wgpu_bindings --metadata config/cbindgen-metadata.json --cpp-compat > gfx/wgpu_bindings/wgpu_ffi_generated.h
|
||||
*
|
||||
* where:
|
||||
* - $TOPSRCDIR is the top of the Firefox source tree, and
|
||||
* - $CBINDGEN is the path to the cbindgen executable provided by mozbuild (the exact version often matters)
|
||||
*/
|
||||
|
||||
struct WGPUByteBuf;
|
||||
|
|
|
@ -2435,7 +2435,7 @@ impl BatchBuilder {
|
|||
let mut gpu_blocks = Vec::<GpuBlockData>::with_capacity(3 + max_tiles_per_header * 2);
|
||||
for chunk in image_instance.visible_tiles.chunks(max_tiles_per_header) {
|
||||
gpu_blocks.clear();
|
||||
gpu_blocks.push(PremultipliedColorF::WHITE.into()); //color
|
||||
gpu_blocks.push(image_data.color.premultiplied().into()); //color
|
||||
gpu_blocks.push(PremultipliedColorF::WHITE.into()); //bg color
|
||||
gpu_blocks.push([-1.0, 0.0, 0.0, 0.0].into()); //stretch size
|
||||
// negative first value makes the shader code ignore it and use the local size instead
|
||||
|
|
|
@ -72,7 +72,8 @@ void SetThisProcessName(const char* aProcessName) {
|
|||
displayNameKey = reinterpret_cast<CFStringRef>(*(CFStringRef*)displayNameKeyAddr);
|
||||
}
|
||||
|
||||
// Rename will fail without this
|
||||
// We need this to ensure we have a connection to the Process Manager, not
|
||||
// doing so will silently fail and process name wont be updated.
|
||||
ProcessSerialNumber psn;
|
||||
if (::GetCurrentProcess(&psn) != noErr) {
|
||||
return;
|
||||
|
|
|
@ -102,7 +102,6 @@ bool UtilityProcessChild::Init(base::ProcessId aParentPid,
|
|||
|
||||
mSandbox = (SandboxingKind)aSandboxingKind;
|
||||
|
||||
mozilla::ipc::SetThisProcessName("Utility Process");
|
||||
profiler_set_process_name(nsCString("Utility Process"));
|
||||
|
||||
// Notify the parent process that we have finished our init and that it can
|
||||
|
@ -121,6 +120,10 @@ void CGSShutdownServerConnections();
|
|||
mozilla::ipc::IPCResult UtilityProcessChild::RecvInit(
|
||||
const Maybe<FileDescriptor>& aBrokerFd,
|
||||
const bool& aCanRecordReleaseTelemetry) {
|
||||
// Do this now (before closing WindowServer on macOS) to avoid risking
|
||||
// blocking in GetCurrentProcess() called on that platform
|
||||
mozilla::ipc::SetThisProcessName("Utility Process");
|
||||
|
||||
#if defined(MOZ_SANDBOX)
|
||||
# if defined(XP_MACOSX)
|
||||
// Close all current connections to the WindowServer. This ensures that the
|
||||
|
|
|
@ -467,8 +467,6 @@ MSG_DEF(JSMSG_WASM_BAD_BUF_ARG, 0, JSEXN_TYPEERR, "first argument mus
|
|||
MSG_DEF(JSMSG_WASM_BAD_MOD_ARG, 0, JSEXN_TYPEERR, "first argument must be a WebAssembly.Module")
|
||||
MSG_DEF(JSMSG_WASM_BAD_BUF_MOD_ARG, 0, JSEXN_TYPEERR, "first argument must be a WebAssembly.Module, ArrayBuffer or typed array object")
|
||||
MSG_DEF(JSMSG_WASM_BAD_DESC_ARG, 1, JSEXN_TYPEERR, "first argument must be a {0} descriptor")
|
||||
MSG_DEF(JSMSG_WASM_BAD_ELEMENT, 0, JSEXN_TYPEERR, "\"element\" property of table descriptor must be \"funcref\"")
|
||||
MSG_DEF(JSMSG_WASM_BAD_ELEMENT_GENERALIZED, 0, JSEXN_TYPEERR, "\"element\" property of table descriptor must be \"funcref\" or \"externref\"")
|
||||
MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG, 0, JSEXN_TYPEERR, "second argument must be an object")
|
||||
MSG_DEF(JSMSG_WASM_BAD_IMPORT_FIELD, 1, JSEXN_TYPEERR, "import object field '{0}' is not an Object")
|
||||
MSG_DEF(JSMSG_WASM_BAD_REF_NONNULLABLE_VALUE, 0, JSEXN_TYPEERR, "cannot pass null to non-nullable WebAssembly reference")
|
||||
|
|
|
@ -94,19 +94,29 @@ assertErrorMessage(() => ins.exports.newfn(3),
|
|||
// RULE: WebAssembly.Global of type v128 is constructable from JS with a default
|
||||
// value.
|
||||
|
||||
var gi = new WebAssembly.Global({value: "v128"});
|
||||
var gm = new WebAssembly.Global({value: "v128", mutable:true});
|
||||
|
||||
// RULE: WebAssembly.Global constructor for type v128 does not accept any value
|
||||
// but throws TypeError.
|
||||
// RULE: WebAssembly.Global constructor for type v128 is not constructable with
|
||||
// or without a default value.
|
||||
|
||||
assertErrorMessage(() => new WebAssembly.Global({value: "v128"}, 37),
|
||||
TypeError,
|
||||
/cannot pass.*v128.*to or from JS/);
|
||||
assertErrorMessage(() => new WebAssembly.Global({value: "v128"}),
|
||||
TypeError,
|
||||
/cannot pass.*v128.*to or from JS/);
|
||||
assertErrorMessage(() => new WebAssembly.Global({value: "v128", mutable: true}),
|
||||
TypeError,
|
||||
/cannot pass.*v128.*to or from JS/);
|
||||
|
||||
// RULE: WebAssembly.Global of type v128 have getters and setters that throw
|
||||
// TypeError when called from JS.
|
||||
|
||||
let {gi, gm} = wasmEvalText(`
|
||||
(module
|
||||
(global (export "gi") v128 v128.const i64x2 0 0)
|
||||
(global (export "gm") (mut v128) v128.const i64x2 0 0)
|
||||
)`).exports;
|
||||
|
||||
assertErrorMessage(() => gi.value,
|
||||
TypeError,
|
||||
/cannot pass.*v128.*to or from JS/);
|
||||
|
|
|
@ -88,6 +88,7 @@ namespace jit {
|
|||
ABIFUNCTION_JS_CODEGEN_ARM_LIST(_) \
|
||||
ABIFUNCTION_WASM_CODEGEN_DEBUG_LIST(_) \
|
||||
_(js::ArgumentsObject::finishForIonPure) \
|
||||
_(js::ArgumentsObject::finishInlineForIonPure) \
|
||||
_(js::ArrayShiftMoveElements) \
|
||||
_(js::ecmaAtan2) \
|
||||
_(js::ecmaHypot) \
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче