mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-01 00:32:11 +00:00
Merge autoland to mozilla-central. a=merge
This commit is contained in:
commit
7f8c28a578
@ -24,6 +24,8 @@ const kESModuleList = new Set([
|
||||
/browser\/content\/browser\/firefoxview\.js$/,
|
||||
/browser\/content\/browser\/recently-closed-tabs\.js$/,
|
||||
/browser\/content\/browser\/tabs-pickup\.js$/,
|
||||
/browser\/content\/browser\/helpers\.js$/,
|
||||
/browser\/content\/browser\/tab-pickup-list\.js$/,
|
||||
/toolkit\/content\/global\/certviewer\/components\/.*\.js$/,
|
||||
/toolkit\/content\/global\/certviewer\/.*\.js$/,
|
||||
/chrome\/pdfjs\/content\/web\/.*\.js$/,
|
||||
|
@ -2046,22 +2046,17 @@ var Policies = {
|
||||
let manifest = {
|
||||
description: newEngine.Description,
|
||||
iconURL: newEngine.IconURL ? newEngine.IconURL.href : null,
|
||||
chrome_settings_overrides: {
|
||||
search_provider: {
|
||||
name: newEngine.Name,
|
||||
// If the encoding is not specified or is falsy, the
|
||||
// search service will fall back to the default encoding.
|
||||
encoding: newEngine.Encoding,
|
||||
search_url: encodeURI(newEngine.URLTemplate),
|
||||
keyword: newEngine.Alias,
|
||||
search_url_post_params:
|
||||
newEngine.Method == "POST"
|
||||
? newEngine.PostData
|
||||
: undefined,
|
||||
suggest_url: newEngine.SuggestURLTemplate,
|
||||
},
|
||||
},
|
||||
name: newEngine.Name,
|
||||
// If the encoding is not specified or is falsy, the
|
||||
// search service will fall back to the default encoding.
|
||||
encoding: newEngine.Encoding,
|
||||
search_url: encodeURI(newEngine.URLTemplate),
|
||||
keyword: newEngine.Alias,
|
||||
search_url_post_params:
|
||||
newEngine.Method == "POST" ? newEngine.PostData : undefined,
|
||||
suggest_url: newEngine.SuggestURLTemplate,
|
||||
};
|
||||
|
||||
let engine = Services.search.getEngineByName(newEngine.Name);
|
||||
if (engine) {
|
||||
try {
|
||||
|
@ -50,7 +50,10 @@ firefoxview-closed-tabs-collapse-button =
|
||||
firefoxview-closed-tabs-description = Reopen pages you’ve closed on this device.
|
||||
firefoxview-closed-tabs-placeholder = History is empty <br/> The next time you close a tab, you can reopen it here.
|
||||
|
||||
# refers to the last tab that was used
|
||||
firefoxview-pickup-tabs-badge = Last active
|
||||
|
||||
# Variables:
|
||||
# $targetURI (string) - URL that will be opened in the new tab
|
||||
firefoxview-closed-tabs-tab-button =
|
||||
firefoxview-tabs-list-tab-button =
|
||||
.title = Open { $targetURI } in a new tab
|
||||
|
@ -323,24 +323,160 @@ body > main > aside {
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.closed-tab-li-url, .closed-tab-li-time {
|
||||
.closed-tab-li-url,
|
||||
.closed-tab-li-time,
|
||||
.synced-tab-li-device,
|
||||
.synced-tab-li-url,
|
||||
.synced-tab-li-time,
|
||||
.synced-tab-li-url-device {
|
||||
color: var(--in-content-deemphasized-text);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.closed-tab-li-title, .closed-tab-li-url {
|
||||
.closed-tab-li-title,
|
||||
.closed-tab-li-url,
|
||||
.synced-tab-li:not(:first-child) > .synced-tab-li-title,
|
||||
.synced-tab-li-device,
|
||||
.synced-tab-li-url-device {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.synced-tabs-list {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: grid;
|
||||
grid-template-columns: 4fr 4fr;
|
||||
column-gap: 16px;
|
||||
row-gap: 8px;
|
||||
|
||||
grid-template-areas:
|
||||
"first second"
|
||||
"first third";
|
||||
}
|
||||
|
||||
.synced-tab-li {
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #CFCFD8;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
display: grid;
|
||||
grid-template-columns: min-content repeat(3, 1fr);
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-areas:
|
||||
"favicon title title title"
|
||||
"favicon url-device url-device time"
|
||||
}
|
||||
|
||||
.synced-tab-li:hover {
|
||||
box-shadow: 0px 2px 6px rgba(58, 57, 68, 0.2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.synced-tab-li:not(:first-child) {
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 60rem) {
|
||||
.synced-tab-li {
|
||||
grid-template-areas:
|
||||
"favicon title title title"
|
||||
"favicon url-device url-device url-device"
|
||||
}
|
||||
.synced-tab-li:not(:first-child) > .synced-tab-li-time {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.synced-tab-li:first-child {
|
||||
padding-top: 20px;
|
||||
grid-area: first;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-template-rows: auto;
|
||||
grid-template-areas:
|
||||
"favicon favicon badge badge"
|
||||
"title title title title"
|
||||
"domain domain domain time"
|
||||
"device device device time";
|
||||
}
|
||||
|
||||
.synced-tab-li:nth-child(2) {
|
||||
grid-area: second;
|
||||
}
|
||||
|
||||
.synced-tab-li:nth-child(3) {
|
||||
grid-area: third;
|
||||
}
|
||||
|
||||
.synced-tab-li-url,
|
||||
.synced-tab-li-device,
|
||||
.synced-tab-li-time,
|
||||
.synced-tab-li:not(:first-child) > .synced-tab-li-title {
|
||||
font-size: .85em;
|
||||
}
|
||||
|
||||
.synced-tab-li-url {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.synced-tab-li:first-child > .synced-tab-li-url {
|
||||
align-self: end;
|
||||
grid-area: domain;
|
||||
}
|
||||
|
||||
.synced-tab-li-title {
|
||||
grid-area: title;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.synced-tab-li:first-child > .synced-tab-li-title {
|
||||
color: inherit;
|
||||
padding-top: 5px;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
height: 2.5em;
|
||||
}
|
||||
|
||||
.synced-tab-li-url-device {
|
||||
grid-area: url-device;
|
||||
padding-top: 4px
|
||||
}
|
||||
|
||||
.synced-tab-li:first-child > .synced-tab-li-device {
|
||||
grid-area: device;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.synced-tab-li-time {
|
||||
grid-area: time;
|
||||
justify-self: end;
|
||||
color: var(--in-content-deemphasized-text);
|
||||
align-self: end;
|
||||
}
|
||||
|
||||
.synced-tab-li:first-child > .synced-tab-li-time {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.synced-tab-li .favicon {
|
||||
grid-area: favicon;
|
||||
margin-inline-end: 10px;
|
||||
}
|
||||
|
||||
.synced-tab-li .icon {
|
||||
vertical-align: bottom;
|
||||
margin-inline-end: 5px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
display: inline-block;
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.icon.arrow-down {
|
||||
@ -355,12 +491,50 @@ body > main > aside {
|
||||
background-image: url('chrome://browser/skin/history.svg');
|
||||
}
|
||||
|
||||
.icon.phone {
|
||||
background-image: url('chrome://browser/skin/device-phone.svg');
|
||||
}
|
||||
|
||||
.icon.desktop {
|
||||
background-image: url('chrome://browser/skin/device-desktop.svg');
|
||||
}
|
||||
|
||||
.icon.tablet {
|
||||
background-image: url('chrome://browser/skin/device-tablet.svg');
|
||||
}
|
||||
|
||||
.favicon {
|
||||
background-size: cover;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.favicon, .icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.last-active-badge {
|
||||
height: 1.25em;
|
||||
width: 6em;
|
||||
background-color: #E3FFF3;
|
||||
grid-area: badge;
|
||||
border-radius: 2em;
|
||||
justify-self: end;
|
||||
text-align: center;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
background-color: #2AC3A2;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.badge-text {
|
||||
font-weight: 400;
|
||||
font-size: .75em;
|
||||
letter-spacing: 0.02em;
|
||||
margin-inline-start: 4px;
|
||||
color: #000000;
|
||||
}
|
||||
|
@ -17,6 +17,8 @@
|
||||
<script type="module" src="chrome://browser/content/tabs-pickup.js"></script>
|
||||
<script type="module" src="chrome://browser/content/firefoxview.js"></script>
|
||||
<script type="module" src="chrome://browser/content/recently-closed-tabs.js"></script>
|
||||
<script type="module" src="chrome://browser/content/tab-pickup-list.js"></script>
|
||||
<script type="module" src="chrome://browser/content/helpers.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -83,9 +85,9 @@
|
||||
</template>
|
||||
<template id="synced-tabs-template">
|
||||
<div class="synced-tabs-container" data-prefix="id:" hidden>
|
||||
<div class="card">
|
||||
<h2 data-l10n-id="firefoxview-tabpickup-recenttabs-description"></h2>
|
||||
</div>
|
||||
<tab-pickup-list>
|
||||
<ol hidden="true" class="synced-tabs-list"></ol>
|
||||
</tab-pickup-list>
|
||||
<p class="loading-content" data-l10n-id="firefoxview-tabpickup-syncing"></p>
|
||||
</div>
|
||||
</template>
|
||||
|
63
browser/components/firefoxview/helpers.js
Normal file
63
browser/components/firefoxview/helpers.js
Normal file
@ -0,0 +1,63 @@
|
||||
/* 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/. */
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
XPCOMUtils.defineLazyModuleGetters(globalThis, {
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm",
|
||||
});
|
||||
|
||||
export function formatURIForDisplay(uriString) {
|
||||
// TODO: Bug 1764816: Make sure we handle file:///, jar:, blob, IP4/IP6 etc. addresses
|
||||
let uri;
|
||||
try {
|
||||
uri = Services.io.newURI(uriString);
|
||||
} catch (ex) {
|
||||
return uriString;
|
||||
}
|
||||
if (!uri.asciiHost) {
|
||||
return uriString;
|
||||
}
|
||||
let displayHost;
|
||||
try {
|
||||
// This might fail if it's an IP address or doesn't have more than 1 part
|
||||
displayHost = Services.eTLD.getBaseDomain(uri);
|
||||
} catch (ex) {
|
||||
return uri.displayHostPort;
|
||||
}
|
||||
return displayHost.length ? displayHost : uriString;
|
||||
}
|
||||
|
||||
export function convertTimestamp(timestamp, fluentStrings) {
|
||||
const relativeTimeFormat = new Services.intl.RelativeTimeFormat(
|
||||
undefined,
|
||||
{}
|
||||
);
|
||||
const elapsed = Date.now() - timestamp;
|
||||
// Cutoff of 1.5 minutes + 1 second to determine what text string to display
|
||||
const nowThresholdMs = 91000;
|
||||
let formattedTime;
|
||||
if (elapsed <= nowThresholdMs) {
|
||||
// Use a different string for very recent timestamps
|
||||
formattedTime = fluentStrings.formatValueSync(
|
||||
"firefoxview-just-now-timestamp"
|
||||
);
|
||||
} else {
|
||||
formattedTime = relativeTimeFormat.formatBestUnit(new Date(timestamp));
|
||||
}
|
||||
return formattedTime;
|
||||
}
|
||||
|
||||
export function createFaviconElement(image) {
|
||||
const imageUrl = image
|
||||
? PlacesUIUtils.getImageURL(image)
|
||||
: "chrome://global/skin/icons/defaultFavicon.svg";
|
||||
let favicon = document.createElement("div");
|
||||
|
||||
favicon.style.backgroundImage = `url('${imageUrl}')`;
|
||||
favicon.classList.add("favicon");
|
||||
return favicon;
|
||||
}
|
@ -6,6 +6,8 @@ browser.jar:
|
||||
content/browser/firefoxview.html
|
||||
content/browser/firefoxview.js
|
||||
content/browser/firefoxview.css
|
||||
content/browser/helpers.js
|
||||
content/browser/tabs-pickup.js
|
||||
content/browser/tab-pickup-list.js
|
||||
content/browser/recently-closed-tabs.js
|
||||
content/browser/colorway-background.svg (content/colorway-background.svg)
|
||||
|
@ -10,10 +10,14 @@ const { XPCOMUtils } = ChromeUtils.import(
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetters(globalThis, {
|
||||
SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
|
||||
PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm",
|
||||
});
|
||||
|
||||
const relativeTimeFormat = new Services.intl.RelativeTimeFormat(undefined, {});
|
||||
import {
|
||||
formatURIForDisplay,
|
||||
convertTimestamp,
|
||||
createFaviconElement,
|
||||
} from "./helpers.js";
|
||||
|
||||
const SS_NOTIFY_CLOSED_OBJECTS_CHANGED = "sessionstore-closed-objects-changed";
|
||||
|
||||
function getWindow() {
|
||||
@ -52,42 +56,6 @@ class RecentlyClosedTabsList extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
convertTimestamp(timestamp) {
|
||||
const elapsed = Date.now() - timestamp;
|
||||
const nowThresholdMs = 91000;
|
||||
let formattedTime;
|
||||
if (elapsed <= nowThresholdMs) {
|
||||
// Use a different string for very recent timestamps
|
||||
formattedTime = this.fluentStrings.formatValueSync(
|
||||
"firefoxview-just-now-timestamp"
|
||||
);
|
||||
} else {
|
||||
formattedTime = relativeTimeFormat.formatBestUnit(new Date(timestamp));
|
||||
}
|
||||
return formattedTime;
|
||||
}
|
||||
|
||||
formatURIForDisplay(uriString) {
|
||||
// TODO: Bug 1764816: Make sure we handle file:///, jar:, blob, IP4/IP6 etc. addresses
|
||||
let uri;
|
||||
try {
|
||||
uri = Services.io.newURI(uriString);
|
||||
} catch (ex) {
|
||||
return uriString;
|
||||
}
|
||||
if (!uri.asciiHost) {
|
||||
return uriString;
|
||||
}
|
||||
let displayHost;
|
||||
try {
|
||||
// This might fail if it's an IP address or doesn't have more than 1 part
|
||||
displayHost = Services.eTLD.getBaseDomain(uri);
|
||||
} catch (ex) {
|
||||
return uri.displayHostPort;
|
||||
}
|
||||
return displayHost.length ? displayHost : uriString;
|
||||
}
|
||||
|
||||
getTabStateValue(tab, key) {
|
||||
let value = "";
|
||||
const tabEntries = tab.state.entries;
|
||||
@ -173,18 +141,6 @@ class RecentlyClosedTabsList extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
@ -195,24 +151,25 @@ class RecentlyClosedTabsList extends HTMLElement {
|
||||
title.textContent = `${tab.title}`;
|
||||
title.classList.add("closed-tab-li-title");
|
||||
|
||||
const favicon = this.setFavicon(tab);
|
||||
const favicon = createFaviconElement(tab.image);
|
||||
li.append(favicon);
|
||||
|
||||
const targetURI = this.getTabStateValue(tab, "url");
|
||||
li.dataset.targetURI = targetURI;
|
||||
document.l10n.setAttributes(li, "firefoxview-closed-tabs-tab-button", {
|
||||
document.l10n.setAttributes(li, "firefoxview-tabs-list-tab-button", {
|
||||
targetURI,
|
||||
});
|
||||
|
||||
const url = document.createElement("span");
|
||||
|
||||
if (targetURI) {
|
||||
url.textContent = this.formatURIForDisplay(targetURI);
|
||||
url.textContent = formatURIForDisplay(targetURI);
|
||||
url.classList.add("closed-tab-li-url");
|
||||
}
|
||||
|
||||
const time = document.createElement("span");
|
||||
time.textContent = this.convertTimestamp(tab.closedAt);
|
||||
time.textContent = convertTimestamp(tab.closedAt, this.fluentStrings);
|
||||
|
||||
time.classList.add("closed-tab-li-time");
|
||||
|
||||
li.append(title, url, time);
|
||||
|
152
browser/components/firefoxview/tab-pickup-list.js
Normal file
152
browser/components/firefoxview/tab-pickup-list.js
Normal file
@ -0,0 +1,152 @@
|
||||
/* 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";
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
XPCOMUtils.defineLazyModuleGetters(globalThis, {
|
||||
SyncedTabs: "resource://services-sync/SyncedTabs.jsm",
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
});
|
||||
|
||||
import {
|
||||
formatURIForDisplay,
|
||||
convertTimestamp,
|
||||
createFaviconElement,
|
||||
} from "./helpers.js";
|
||||
|
||||
class TabPickupList extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.maxTabsLength = 3;
|
||||
}
|
||||
|
||||
get tabsList() {
|
||||
return this.querySelector("ol");
|
||||
}
|
||||
|
||||
get fluentStrings() {
|
||||
if (!this._fluentStrings) {
|
||||
this._fluentStrings = new Localization(["preview/firefoxView.ftl"], true);
|
||||
}
|
||||
return this._fluentStrings;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.addEventListener("click", this);
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
if (event.type == "click") {
|
||||
this.openTab(event);
|
||||
}
|
||||
}
|
||||
|
||||
openTab(event) {
|
||||
event.preventDefault();
|
||||
const item = event.target.closest(".synced-tab-li");
|
||||
window.open(item.dataset.targetURI, "_blank");
|
||||
}
|
||||
|
||||
async getSyncedTabData() {
|
||||
let tabs = [];
|
||||
let clients = await SyncedTabs.getTabClients();
|
||||
|
||||
for (let client of clients) {
|
||||
for (let tab of client.tabs) {
|
||||
tab.device = client.name;
|
||||
tab.deviceType = client.clientType;
|
||||
}
|
||||
tabs = [...tabs, ...client.tabs.reverse()];
|
||||
}
|
||||
tabs = tabs
|
||||
.sort((a, b) => b.lastUsed - a.lastUsed)
|
||||
.slice(0, this.maxTabsLength);
|
||||
|
||||
if (tabs.length) {
|
||||
this.initiateTabsList(tabs);
|
||||
} else {
|
||||
// TODO show empty state placeholder
|
||||
}
|
||||
}
|
||||
|
||||
initiateTabsList(syncedTabs) {
|
||||
for (let i = 0; i < syncedTabs.length; i++) {
|
||||
const li = this.generateListItem(syncedTabs[i], i);
|
||||
this.tabsList.append(li);
|
||||
}
|
||||
this.tabsList.hidden = false;
|
||||
}
|
||||
|
||||
generateListItem(tab, index) {
|
||||
const li = document.createElement("li");
|
||||
li.classList.add("synced-tab-li");
|
||||
li.setAttribute("tabindex", 0);
|
||||
li.setAttribute("role", "button");
|
||||
|
||||
const title = document.createElement("span");
|
||||
title.textContent = tab.title;
|
||||
title.classList.add("synced-tab-li-title");
|
||||
|
||||
const favicon = createFaviconElement(tab.icon);
|
||||
const targetURI = tab.url;
|
||||
|
||||
li.dataset.targetURI = targetURI;
|
||||
document.l10n.setAttributes(li, "firefoxview-tabs-list-tab-button", {
|
||||
targetURI,
|
||||
});
|
||||
|
||||
const time = document.createElement("span");
|
||||
time.textContent = convertTimestamp(
|
||||
tab.lastUsed * 1000,
|
||||
this.fluentStrings
|
||||
);
|
||||
time.classList.add("synced-tab-li-time");
|
||||
|
||||
const url = document.createElement("span");
|
||||
const device = document.createElement("span");
|
||||
const deviceIcon = document.createElement("div");
|
||||
deviceIcon.classList.add("icon", tab.deviceType);
|
||||
deviceIcon.setAttribute("role", "presentation");
|
||||
|
||||
const deviceText = tab.device;
|
||||
device.textContent = deviceText;
|
||||
device.prepend(deviceIcon);
|
||||
device.title = deviceText;
|
||||
|
||||
url.textContent = formatURIForDisplay(tab.url);
|
||||
url.classList.add("synced-tab-li-url");
|
||||
device.classList.add("synced-tab-li-device");
|
||||
|
||||
// the first list item is diffent from second and third
|
||||
if (index == 0) {
|
||||
const badge = this.createBadge();
|
||||
li.append(favicon, badge, title, url, device, time);
|
||||
} else {
|
||||
const urlWithDevice = document.createElement("span");
|
||||
urlWithDevice.append(url, " • ", device);
|
||||
urlWithDevice.classList.add("synced-tab-li-url-device");
|
||||
li.append(favicon, title, urlWithDevice, time);
|
||||
}
|
||||
|
||||
return li;
|
||||
}
|
||||
|
||||
createBadge() {
|
||||
const badge = document.createElement("div");
|
||||
const dot = document.createElement("span");
|
||||
const badgeText = document.createElement("span");
|
||||
|
||||
badgeText.setAttribute("data-l10n-id", "firefoxview-pickup-tabs-badge");
|
||||
badgeText.classList.add("badge-text");
|
||||
badge.classList.add("last-active-badge");
|
||||
dot.classList.add("dot");
|
||||
badge.append(dot, badgeText);
|
||||
return badge;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("tab-pickup-list", TabPickupList);
|
@ -261,19 +261,28 @@ class TabsPickupContainer extends HTMLElement {
|
||||
}
|
||||
setupElem.hidden = false;
|
||||
setupElem.selectedViewName = `sync-setup-view${stateIndex}`;
|
||||
} else {
|
||||
if (!tabsElem) {
|
||||
this.appendTemplatedElement(
|
||||
"synced-tabs-template",
|
||||
"tabpickup-tabs-container"
|
||||
);
|
||||
tabsElem = this.tabsContainerElem;
|
||||
}
|
||||
if (setupElem) {
|
||||
setupElem.hidden = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tabsElem) {
|
||||
this.appendTemplatedElement(
|
||||
"synced-tabs-template",
|
||||
"tabpickup-tabs-container"
|
||||
);
|
||||
tabsElem = this.tabsContainerElem;
|
||||
tabsElem.classList.toggle("loading", stateIndex == 3);
|
||||
tabsElem.hidden = false;
|
||||
}
|
||||
if (setupElem) {
|
||||
setupElem.hidden = true;
|
||||
}
|
||||
tabsElem.hidden = false;
|
||||
|
||||
const tabPickupList = document.querySelector("tab-pickup-list");
|
||||
if (stateIndex == 4 && !tabPickupList.tabsList.hasChildNodes()) {
|
||||
if (tabsElem) {
|
||||
tabsElem.classList.toggle("loading", false);
|
||||
}
|
||||
tabPickupList.getSyncedTabData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1254,3 +1254,12 @@ These record the impression and click pings for the Sponsored TopSites.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Glean "newtab" ping
|
||||
|
||||
Unlike the other data collections, this is a
|
||||
[Glean Ping](https://mozilla.github.io/glean/book/user/pings/index.html)
|
||||
that batches events and metadata about newtab sessions.
|
||||
|
||||
You can find full documentation about this ping and its contents in
|
||||
[its Glean Dictionary entry](https://dictionary.telemetry.mozilla.org/apps/firefox_desktop/pings/newtab).
|
||||
|
@ -8,12 +8,17 @@ const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
const { actionCreators: ac, actionTypes: at } = ChromeUtils.import(
|
||||
"resource://activity-stream/common/Actions.jsm"
|
||||
);
|
||||
const {
|
||||
actionCreators: ac,
|
||||
actionTypes: at,
|
||||
actionUtils: au,
|
||||
} = ChromeUtils.import("resource://activity-stream/common/Actions.jsm");
|
||||
const { shortURL } = ChromeUtils.import(
|
||||
"resource://activity-stream/lib/ShortURL.jsm"
|
||||
);
|
||||
const { AboutNewTab } = ChromeUtils.import(
|
||||
"resource:///modules/AboutNewTab.jsm"
|
||||
);
|
||||
|
||||
const lazy = {};
|
||||
|
||||
@ -443,17 +448,21 @@ class PlacesFeed {
|
||||
];
|
||||
}
|
||||
|
||||
handoffSearchToAwesomebar({ _target, data, meta }) {
|
||||
handoffSearchToAwesomebar(action) {
|
||||
const { _target, data, meta } = action;
|
||||
const searchEngine = this._getDefaultSearchEngine(
|
||||
lazy.PrivateBrowsingUtils.isBrowserPrivate(_target.browser)
|
||||
);
|
||||
const urlBar = _target.browser.ownerGlobal.gURLBar;
|
||||
let isFirstChange = true;
|
||||
|
||||
const newtabSession = AboutNewTab.activityStream.store.feeds
|
||||
.get("feeds.telemetry")
|
||||
?.sessions.get(au.getPortIdOfSender(action));
|
||||
if (!data || !data.text) {
|
||||
urlBar.setHiddenFocus();
|
||||
} else {
|
||||
urlBar.handoff(data.text, searchEngine);
|
||||
urlBar.handoff(data.text, searchEngine, newtabSession?.session_id);
|
||||
isFirstChange = false;
|
||||
}
|
||||
|
||||
@ -464,7 +473,7 @@ class PlacesFeed {
|
||||
if (isFirstChange) {
|
||||
isFirstChange = false;
|
||||
urlBar.removeHiddenFocus(true);
|
||||
urlBar.handoff("", searchEngine);
|
||||
urlBar.handoff("", searchEngine, newtabSession?.session_id);
|
||||
this.store.dispatch(
|
||||
ac.OnlyToOneContent({ type: at.DISABLE_SEARCH }, meta.fromTarget)
|
||||
);
|
||||
|
@ -67,6 +67,7 @@ ChromeUtils.defineModuleGetter(
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(lazy, {
|
||||
ExperimentAPI: "resource://nimbus/ExperimentAPI.jsm",
|
||||
NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
|
||||
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
|
||||
TelemetrySession: "resource://gre/modules/TelemetrySession.jsm",
|
||||
});
|
||||
@ -188,6 +189,7 @@ class TelemetryFeed {
|
||||
this._impressionId
|
||||
);
|
||||
Services.telemetry.scalarSet("deletion.request.context_id", lazy.contextId);
|
||||
Glean.newtab.locale.set(Services.locale.appLocaleAsBCP47);
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
@ -423,6 +425,14 @@ class TelemetryFeed {
|
||||
this.sendDiscoveryStreamLoadedContent(portID, session);
|
||||
this.sendDiscoveryStreamImpressions(portID, session);
|
||||
|
||||
Glean.newtab.closed.record({ newtab_visit_id: session.session_id });
|
||||
if (
|
||||
this.telemetryEnabled &&
|
||||
(lazy.NimbusFeatures.glean.getVariable("newtabPingEnabled") ?? true)
|
||||
) {
|
||||
GleanPings.newtab.submit("newtab_session_end");
|
||||
}
|
||||
|
||||
if (session.perf.visibility_event_rcvd_ts) {
|
||||
let absNow = this.processStartTs + Cu.now();
|
||||
session.session_duration = Math.round(
|
||||
@ -845,9 +855,10 @@ class TelemetryFeed {
|
||||
|
||||
handleTopSitesImpressionStats(action) {
|
||||
const { data } = action;
|
||||
const { type, position, source } = data;
|
||||
const { type, position, source, advertiser } = data;
|
||||
let pingType;
|
||||
|
||||
const session = this.sessions.get(au.getPortIdOfSender(action));
|
||||
if (type === "impression") {
|
||||
pingType = "topsites-impression";
|
||||
Services.telemetry.keyedScalarAdd(
|
||||
@ -855,6 +866,12 @@ class TelemetryFeed {
|
||||
`${source}_${position}`,
|
||||
1
|
||||
);
|
||||
if (session) {
|
||||
Glean.topsites.impression.record({
|
||||
newtab_visit_id: session.session_id,
|
||||
is_sponsored: !!advertiser,
|
||||
});
|
||||
}
|
||||
} else if (type === "click") {
|
||||
pingType = "topsites-click";
|
||||
Services.telemetry.keyedScalarAdd(
|
||||
@ -862,6 +879,12 @@ class TelemetryFeed {
|
||||
`${source}_${position}`,
|
||||
1
|
||||
);
|
||||
if (session) {
|
||||
Glean.topsites.click.record({
|
||||
newtab_visit_id: session.session_id,
|
||||
is_sponsored: !!advertiser,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Cu.reportError("Unknown ping type for TopSites impression");
|
||||
return;
|
||||
@ -1146,6 +1169,17 @@ class TelemetryFeed {
|
||||
}
|
||||
|
||||
Object.assign(session.perf, data);
|
||||
|
||||
if (data.visibility_event_rcvd_ts && !session.newtabOpened) {
|
||||
session.newtabOpened = true;
|
||||
const source = ONBOARDING_ALLOWED_PAGE_VALUES.includes(session.page)
|
||||
? session.page
|
||||
: "other";
|
||||
Glean.newtab.opened.record({
|
||||
newtab_visit_id: session.session_id,
|
||||
source,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
uninit() {
|
||||
|
147
browser/components/newtab/metrics.yaml
Normal file
147
browser/components/newtab/metrics.yaml
Normal file
@ -0,0 +1,147 @@
|
||||
# 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/.
|
||||
|
||||
# Adding a new metric? We have docs for that!
|
||||
# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html
|
||||
|
||||
---
|
||||
$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
|
||||
$tags:
|
||||
- 'Firefox :: New Tab Page'
|
||||
|
||||
newtab:
|
||||
locale:
|
||||
type: string
|
||||
description: >
|
||||
The application's locale as of when newtab's TelemetryFeed was init.
|
||||
Comes from `Services.local.appLocaleAsBCP47`.
|
||||
Looks like `en-US`.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_sensitivity:
|
||||
- interaction
|
||||
notification_emails:
|
||||
- anicholson@mozilla.com
|
||||
- chutten@mozilla.com
|
||||
- mmccorquodale@mozilla.com
|
||||
- najiang@mozilla.com
|
||||
expires: 107
|
||||
send_in_pings:
|
||||
- newtab
|
||||
lifetime: application
|
||||
|
||||
opened:
|
||||
type: event
|
||||
description: >
|
||||
Recorded when newtab UI is opened via `about:newtab` or `about:home` or
|
||||
`about:welcome` and has been made visible (see `visibility_event_rcvd_ts`
|
||||
in
|
||||
[detect-user-session-start.js](https://searchfox.org/mozilla-central/source/browser/components/newtab/content-src/lib/detect-user-session-start.js)).
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_sensitivity:
|
||||
- interaction
|
||||
notification_emails:
|
||||
- anicholson@mozilla.com
|
||||
- chutten@mozilla.com
|
||||
- mmccorquodale@mozilla.com
|
||||
- najiang@mozilla.com
|
||||
expires: 107
|
||||
extra_keys:
|
||||
newtab_visit_id: &newtab_visit_id
|
||||
description: >
|
||||
The id of this newtab visit.
|
||||
Allows you to separate multiple simultaneous newtabs and
|
||||
build an event timeline of actions taken from this newtab.
|
||||
type: string
|
||||
source:
|
||||
description: >
|
||||
The source that opened this newtab.
|
||||
One of
|
||||
* `about:newtab`
|
||||
* `about:home`
|
||||
* `about:welcome`
|
||||
* `other`
|
||||
(See `ONBOARDING_ALLOWED_PAGE_VALUES`).
|
||||
type: string
|
||||
send_in_pings:
|
||||
- newtab
|
||||
|
||||
closed:
|
||||
type: event
|
||||
description: >
|
||||
Recorded when newtab UI is closed by
|
||||
* navigation
|
||||
* closing the tab
|
||||
|
||||
Doesn't mean that the newtab was ever visible to a user.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_sensitivity:
|
||||
- interaction
|
||||
notification_emails:
|
||||
- anicholson@mozilla.com
|
||||
- chutten@mozilla.com
|
||||
- mmccorquodale@mozilla.com
|
||||
- najiang@mozilla.com
|
||||
expires: 107
|
||||
extra_keys:
|
||||
newtab_visit_id: *newtab_visit_id
|
||||
send_in_pings:
|
||||
- newtab
|
||||
|
||||
topsites:
|
||||
impression:
|
||||
type: event
|
||||
description: >
|
||||
Recorded when topsite tiles are loaded.
|
||||
Presently only happens for sponsored tiles.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_sensitivity:
|
||||
- interaction
|
||||
notification_emails:
|
||||
- anicholson@mozilla.com
|
||||
- chutten@mozilla.com
|
||||
- mmccorquodale@mozilla.com
|
||||
- najiang@mozilla.com
|
||||
expires: 107
|
||||
extra_keys:
|
||||
newtab_visit_id: *newtab_visit_id
|
||||
is_sponsored: &is_sponsored
|
||||
description: Whether the topsite tile was sponsored.
|
||||
type: boolean
|
||||
send_in_pings:
|
||||
- newtab
|
||||
|
||||
click:
|
||||
type: event
|
||||
description: >
|
||||
Recorded when a topsite tile is clicked.
|
||||
Presently only happens for sponsored tiles.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_sensitivity:
|
||||
- interaction
|
||||
notification_emails:
|
||||
- anicholson@mozilla.com
|
||||
- chutten@mozilla.com
|
||||
- mmccorquodale@mozilla.com
|
||||
- najiang@mozilla.com
|
||||
expires: 107
|
||||
extra_keys:
|
||||
newtab_visit_id: *newtab_visit_id
|
||||
is_sponsored: *is_sponsored
|
||||
send_in_pings:
|
||||
- newtab
|
26
browser/components/newtab/pings.yaml
Normal file
26
browser/components/newtab/pings.yaml
Normal file
@ -0,0 +1,26 @@
|
||||
# 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/.
|
||||
|
||||
---
|
||||
$schema: moz://mozilla.org/schemas/glean/pings/2-0-0
|
||||
|
||||
newtab:
|
||||
description: |
|
||||
Newtab-related instrumentation.
|
||||
Can be disabled via the `newtabPingEnabled` variable of the `glean` Nimbus
|
||||
feature, or the `browser.newtabpage.ping.enabled` pref.
|
||||
reasons:
|
||||
newtab_session_end: |
|
||||
The newtab session ended.
|
||||
Could be by navigation, being closed, etc.
|
||||
include_client_id: true
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
notification_emails:
|
||||
- chutten@mozilla.com
|
||||
- mmccorquodale@mozilla.com
|
||||
- anicholson@mozilla.com
|
||||
- najiang@mozilla.com
|
@ -55,6 +55,7 @@ support-files=
|
||||
[browser_newtab_overrides.js]
|
||||
[browser_newtab_header.js]
|
||||
[browser_newtab_last_LinkMenu.js]
|
||||
[browser_newtab_ping.js]
|
||||
[browser_newtab_trigger.js]
|
||||
[browser_open_tab_focus.js]
|
||||
skip-if = (os == "linux") # Test setup only implemented for OSX and Windows
|
||||
|
172
browser/components/newtab/test/browser/browser_newtab_ping.js
Normal file
172
browser/components/newtab/test/browser/browser_newtab_ping.js
Normal file
@ -0,0 +1,172 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { AboutNewTab } = ChromeUtils.import(
|
||||
"resource:///modules/AboutNewTab.jsm"
|
||||
);
|
||||
const { ASRouter } = ChromeUtils.import(
|
||||
"resource://activity-stream/lib/ASRouter.jsm"
|
||||
);
|
||||
|
||||
const { ExperimentFakes } = ChromeUtils.import(
|
||||
"resource://testing-common/NimbusTestUtils.jsm"
|
||||
);
|
||||
|
||||
let sendTriggerMessageSpy;
|
||||
|
||||
add_setup(function() {
|
||||
let sandbox = sinon.createSandbox();
|
||||
sendTriggerMessageSpy = sandbox.spy(ASRouter, "sendTriggerMessage");
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_newtab_tab_close_sends_ping() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.newtabpage.activity-stream.telemetry", true]],
|
||||
});
|
||||
|
||||
Services.fog.testResetFOG();
|
||||
sendTriggerMessageSpy.resetHistory();
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"about:newtab",
|
||||
false // waitForLoad; about:newtab is cached so this would never resolve
|
||||
);
|
||||
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => sendTriggerMessageSpy.called,
|
||||
"After about:newtab finishes loading"
|
||||
);
|
||||
sendTriggerMessageSpy.resetHistory();
|
||||
|
||||
let record = Glean.newtab.opened.testGetValue("newtab");
|
||||
Assert.equal(record.length, 1, "Should only be one open");
|
||||
const sessionId = record[0].extra.newtab_visit_id;
|
||||
Assert.ok(!!sessionId, "newtab_visit_id must be present");
|
||||
|
||||
let pingSubmitted = false;
|
||||
GleanPings.newtab.testBeforeNextSubmit(reason => {
|
||||
pingSubmitted = true;
|
||||
Assert.equal(reason, "newtab_session_end");
|
||||
record = Glean.newtab.closed.testGetValue("newtab");
|
||||
Assert.equal(record.length, 1, "Should only have one close");
|
||||
Assert.equal(
|
||||
record[0].extra.newtab_visit_id,
|
||||
sessionId,
|
||||
"Should've closed the session we opened"
|
||||
);
|
||||
});
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => pingSubmitted,
|
||||
"We expect the ping to have submitted."
|
||||
);
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
add_task(async function test_newtab_tab_nav_sends_ping() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.newtabpage.activity-stream.telemetry", true]],
|
||||
});
|
||||
|
||||
Services.fog.testResetFOG();
|
||||
sendTriggerMessageSpy.resetHistory();
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"about:newtab",
|
||||
false // waitForLoad; about:newtab is cached so this would never resolve
|
||||
);
|
||||
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => sendTriggerMessageSpy.called,
|
||||
"After about:newtab finishes loading"
|
||||
);
|
||||
sendTriggerMessageSpy.resetHistory();
|
||||
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => !!Glean.newtab.opened.testGetValue("newtab"),
|
||||
"We expect the newtab open to be recorded"
|
||||
);
|
||||
let record = Glean.newtab.opened.testGetValue("newtab");
|
||||
Assert.equal(record.length, 1, "Should only be one open");
|
||||
const sessionId = record[0].extra.newtab_visit_id;
|
||||
Assert.ok(!!sessionId, "newtab_visit_id must be present");
|
||||
|
||||
let pingSubmitted = false;
|
||||
GleanPings.newtab.testBeforeNextSubmit(reason => {
|
||||
pingSubmitted = true;
|
||||
Assert.equal(reason, "newtab_session_end");
|
||||
record = Glean.newtab.closed.testGetValue("newtab");
|
||||
Assert.equal(record.length, 1, "Should only have one close");
|
||||
Assert.equal(
|
||||
record[0].extra.newtab_visit_id,
|
||||
sessionId,
|
||||
"Should've closed the session we opened"
|
||||
);
|
||||
});
|
||||
|
||||
BrowserTestUtils.loadURI(tab.linkedBrowser, "about:mozilla");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => pingSubmitted,
|
||||
"We expect the ping to have submitted."
|
||||
);
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
add_task(async function test_newtab_doesnt_send_nimbus() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.newtabpage.activity-stream.telemetry", true]],
|
||||
});
|
||||
|
||||
let doEnrollmentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
|
||||
featureId: "glean",
|
||||
value: { newtabPingEnabled: false },
|
||||
});
|
||||
Services.fog.testResetFOG();
|
||||
sendTriggerMessageSpy.resetHistory();
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"about:newtab",
|
||||
false // waitForLoad; about:newtab is cached so this would never resolve
|
||||
);
|
||||
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => sendTriggerMessageSpy.called,
|
||||
"After about:newtab finishes loading"
|
||||
);
|
||||
sendTriggerMessageSpy.resetHistory();
|
||||
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => !!Glean.newtab.opened.testGetValue("newtab"),
|
||||
"We expect the newtab open to be recorded"
|
||||
);
|
||||
let record = Glean.newtab.opened.testGetValue("newtab");
|
||||
Assert.equal(record.length, 1, "Should only be one open");
|
||||
const sessionId = record[0].extra.newtab_visit_id;
|
||||
Assert.ok(!!sessionId, "newtab_visit_id must be present");
|
||||
|
||||
GleanPings.newtab.testBeforeNextSubmit(() => {
|
||||
Assert.ok(false, "Must not submit ping!");
|
||||
});
|
||||
BrowserTestUtils.loadURI(tab.linkedBrowser, "about:mozilla");
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
let { sessions } = AboutNewTab.activityStream.store.feeds.get(
|
||||
"feeds.telemetry"
|
||||
);
|
||||
return !Array.from(sessions.entries()).filter(
|
||||
([k, v]) => v.session_id === sessionId
|
||||
).length;
|
||||
}, "Waiting for sessions to clean up.");
|
||||
// Session ended without a ping being sent. Success!
|
||||
await doEnrollmentCleanup();
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
@ -106,6 +106,9 @@ describe("PlacesFeed", () => {
|
||||
PlacesObserver = PlacesFeed.PlacesObserver;
|
||||
feed = new PlacesFeed();
|
||||
feed.store = { dispatch: sinon.spy() };
|
||||
globals.set("AboutNewTab", {
|
||||
activityStream: { store: { feeds: { get() {} } } },
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
globals.restore();
|
||||
@ -735,16 +738,25 @@ describe("PlacesFeed", () => {
|
||||
});
|
||||
});
|
||||
it("should properly handle handoff with text data passed in", () => {
|
||||
const sessionId = "decafc0ffee";
|
||||
sandbox.stub(AboutNewTab.activityStream.store.feeds, "get").returns({
|
||||
sessions: {
|
||||
get: () => {
|
||||
return { session_id: sessionId };
|
||||
},
|
||||
},
|
||||
});
|
||||
feed.handoffSearchToAwesomebar({
|
||||
_target: { browser: { ownerGlobal: { gURLBar: fakeUrlBar } } },
|
||||
data: { text: "foo" },
|
||||
meta: { fromTarget: {} },
|
||||
});
|
||||
assert.calledOnce(fakeUrlBar.handoff);
|
||||
assert.calledWith(
|
||||
assert.calledWithExactly(
|
||||
fakeUrlBar.handoff,
|
||||
"foo",
|
||||
global.Services.search.defaultEngine
|
||||
global.Services.search.defaultEngine,
|
||||
sessionId
|
||||
);
|
||||
assert.notCalled(fakeUrlBar.focus);
|
||||
assert.notCalled(fakeUrlBar.setHiddenFocus);
|
||||
@ -770,10 +782,11 @@ describe("PlacesFeed", () => {
|
||||
meta: { fromTarget: {} },
|
||||
});
|
||||
assert.calledOnce(fakeUrlBar.handoff);
|
||||
assert.calledWith(
|
||||
assert.calledWithExactly(
|
||||
fakeUrlBar.handoff,
|
||||
"foo",
|
||||
global.Services.search.defaultPrivateEngine
|
||||
global.Services.search.defaultPrivateEngine,
|
||||
undefined
|
||||
);
|
||||
assert.notCalled(fakeUrlBar.focus);
|
||||
assert.notCalled(fakeUrlBar.setHiddenFocus);
|
||||
@ -802,7 +815,8 @@ describe("PlacesFeed", () => {
|
||||
assert.calledWithExactly(
|
||||
fakeUrlBar.handoff,
|
||||
"foo",
|
||||
global.Services.search.defaultEngine
|
||||
global.Services.search.defaultEngine,
|
||||
undefined
|
||||
);
|
||||
assert.notCalled(fakeUrlBar.focus);
|
||||
|
||||
@ -819,6 +833,45 @@ describe("PlacesFeed", () => {
|
||||
type: "SHOW_SEARCH",
|
||||
});
|
||||
});
|
||||
it("should properly handoff a newtab session id with no text passed in", () => {
|
||||
const sessionId = "decafc0ffee";
|
||||
sandbox.stub(AboutNewTab.activityStream.store.feeds, "get").returns({
|
||||
sessions: {
|
||||
get: () => {
|
||||
return { session_id: sessionId };
|
||||
},
|
||||
},
|
||||
});
|
||||
feed.handoffSearchToAwesomebar({
|
||||
_target: { browser: { ownerGlobal: { gURLBar: fakeUrlBar } } },
|
||||
data: {},
|
||||
meta: { fromTarget: {} },
|
||||
});
|
||||
assert.calledOnce(fakeUrlBar.setHiddenFocus);
|
||||
assert.notCalled(fakeUrlBar.handoff);
|
||||
assert.notCalled(feed.store.dispatch);
|
||||
|
||||
// Now type a character.
|
||||
listeners.keydown({ key: "f" });
|
||||
assert.calledOnce(fakeUrlBar.handoff);
|
||||
assert.calledWithExactly(
|
||||
fakeUrlBar.handoff,
|
||||
"",
|
||||
global.Services.search.defaultEngine,
|
||||
sessionId
|
||||
);
|
||||
assert.calledOnce(fakeUrlBar.removeHiddenFocus);
|
||||
assert.calledOnce(feed.store.dispatch);
|
||||
assert.calledWith(feed.store.dispatch, {
|
||||
meta: {
|
||||
from: "ActivityStream:Main",
|
||||
skipMain: true,
|
||||
to: "ActivityStream:Content",
|
||||
toTarget: {},
|
||||
},
|
||||
type: "DISABLE_SEARCH",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#observe", () => {
|
||||
|
@ -1303,6 +1303,22 @@ describe("TelemetryFeed", () => {
|
||||
assert.calledOnce(spy);
|
||||
assert.calledWith(spy, topsites_first_painted_ts);
|
||||
});
|
||||
it("should record a Glean newtab.opened event with the correct visit_id when visibility event received", () => {
|
||||
const session_id = "decafc0ffee";
|
||||
const page = "about:newtab";
|
||||
const session = { page, perf: {}, session_id };
|
||||
const data = { visibility_event_rcvd_ts: 444455 };
|
||||
sandbox.stub(instance.sessions, "get").returns(session);
|
||||
|
||||
sandbox.spy(Glean.newtab.opened, "record");
|
||||
instance.saveSessionPerfData("port123", data);
|
||||
|
||||
assert.calledOnce(Glean.newtab.opened.record);
|
||||
assert.deepEqual(Glean.newtab.opened.record.firstCall.args[0], {
|
||||
newtab_visit_id: session_id,
|
||||
source: page,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("#uninit", () => {
|
||||
it("should call .pingCentre.uninit", () => {
|
||||
@ -1956,6 +1972,51 @@ describe("TelemetryFeed", () => {
|
||||
// version
|
||||
assert.equal(args[3], "1");
|
||||
});
|
||||
it("should record a Glean topsites.impression event on an impression event", async () => {
|
||||
const data = {
|
||||
type: "impression",
|
||||
tile_id: 42,
|
||||
source: "newtab",
|
||||
position: 1,
|
||||
reporting_url: "https://test.reporting.net/",
|
||||
advertiser: "adnoid ads",
|
||||
};
|
||||
instance = new TelemetryFeed();
|
||||
const session_id = "decafc0ffee";
|
||||
sandbox.stub(instance.sessions, "get").returns({ session_id });
|
||||
sandbox.spy(Glean.topsites.impression, "record");
|
||||
|
||||
await instance.handleTopSitesImpressionStats({ data });
|
||||
|
||||
// Event should be recorded
|
||||
assert.calledOnce(Glean.topsites.impression.record);
|
||||
assert.calledWith(Glean.topsites.impression.record, {
|
||||
newtab_visit_id: session_id,
|
||||
is_sponsored: true,
|
||||
});
|
||||
});
|
||||
it("should record a Glean topsites.click event on a click event", async () => {
|
||||
const data = {
|
||||
type: "click",
|
||||
tile_id: 42,
|
||||
source: "newtab",
|
||||
position: 1,
|
||||
reporting_url: "https://test.reporting.net/",
|
||||
};
|
||||
instance = new TelemetryFeed();
|
||||
const session_id = "decafc0ffee";
|
||||
sandbox.stub(instance.sessions, "get").returns({ session_id });
|
||||
sandbox.spy(Glean.topsites.click, "record");
|
||||
|
||||
await instance.handleTopSitesImpressionStats({ data });
|
||||
|
||||
// Event should be recorded
|
||||
assert.calledOnce(Glean.topsites.click.record);
|
||||
assert.calledWith(Glean.topsites.click.record, {
|
||||
newtab_visit_id: session_id,
|
||||
is_sponsored: false,
|
||||
});
|
||||
});
|
||||
it("should reportError on unknown pingTypes", async () => {
|
||||
const data = { type: "unknown_type" };
|
||||
instance = new TelemetryFeed();
|
||||
|
@ -528,6 +528,9 @@ const TEST_GLOBAL = {
|
||||
off: () => {},
|
||||
},
|
||||
NimbusFeatures: {
|
||||
glean: {
|
||||
getVariable() {},
|
||||
},
|
||||
newtab: {
|
||||
isEnabled() {},
|
||||
getVariable() {},
|
||||
@ -580,6 +583,33 @@ const TEST_GLOBAL = {
|
||||
},
|
||||
Logger,
|
||||
getFxAccountsSingleton() {},
|
||||
AboutNewTab: {},
|
||||
Glean: {
|
||||
newtab: {
|
||||
opened: {
|
||||
record() {},
|
||||
},
|
||||
closed: {
|
||||
record() {},
|
||||
},
|
||||
locale: {
|
||||
set() {},
|
||||
},
|
||||
},
|
||||
topsites: {
|
||||
impression: {
|
||||
record() {},
|
||||
},
|
||||
click: {
|
||||
record() {},
|
||||
},
|
||||
},
|
||||
},
|
||||
GleanPings: {
|
||||
newtab: {
|
||||
submit() {},
|
||||
},
|
||||
},
|
||||
};
|
||||
overrider.set(TEST_GLOBAL);
|
||||
|
||||
|
@ -1561,6 +1561,23 @@ var PlacesUIUtils = {
|
||||
|
||||
PlacesUIUtils.lastContextMenuTriggerNode = menupopup.triggerNode;
|
||||
|
||||
if (Services.prefs.getBoolPref("browser.tabs.loadBookmarksInTabs", false)) {
|
||||
menupopup.ownerDocument
|
||||
.getElementById("placesContext_open")
|
||||
.removeAttribute("default");
|
||||
menupopup.ownerDocument
|
||||
.getElementById("placesContext_open:newtab")
|
||||
.setAttribute("default", "true");
|
||||
// else clause ensures correct behavior if pref is repeatedly toggled
|
||||
} else {
|
||||
menupopup.ownerDocument
|
||||
.getElementById("placesContext_open:newtab")
|
||||
.removeAttribute("default");
|
||||
menupopup.ownerDocument
|
||||
.getElementById("placesContext_open")
|
||||
.setAttribute("default", "true");
|
||||
}
|
||||
|
||||
let isManaged = !!menupopup.triggerNode.closest("#managed-bookmarks");
|
||||
if (isManaged) {
|
||||
this.managedPlacesContextShowing(event);
|
||||
@ -1816,8 +1833,8 @@ var PlacesUIUtils = {
|
||||
}
|
||||
},
|
||||
|
||||
getImageURL(aItem) {
|
||||
let iconURL = aItem.image;
|
||||
getImageURL(icon) {
|
||||
let iconURL = icon;
|
||||
// don't initiate a connection just to fetch a favicon (see bug 467828)
|
||||
if (/^https?:/.test(iconURL)) {
|
||||
iconURL = "moz-anno:favicon:" + iconURL;
|
||||
|
@ -90,14 +90,63 @@ let checkContextMenu = async (cbfunc, optionItems, doc = document) => {
|
||||
children: bookmarksInfo,
|
||||
});
|
||||
|
||||
let contextMenu = await cbfunc(bookmark);
|
||||
// Open and check the context menu twice, once with
|
||||
// `browser.tabs.loadBookmarksInTabs` set to true and again with it set to
|
||||
// false.
|
||||
for (let loadBookmarksInNewTab of [true, false]) {
|
||||
info(
|
||||
`Running checkContextMenu: ` + JSON.stringify({ loadBookmarksInNewTab })
|
||||
);
|
||||
|
||||
for (let item of optionItems) {
|
||||
OptionItemExists(item, doc);
|
||||
Services.prefs.setBoolPref(
|
||||
"browser.tabs.loadBookmarksInTabs",
|
||||
loadBookmarksInNewTab
|
||||
);
|
||||
|
||||
// When `loadBookmarksInTabs` is true, the usual placesContext_open:newtab
|
||||
// item is hidden and placesContext_open is shown. The tasks in this test
|
||||
// assume that `loadBookmarksInTabs` is false, so when a caller expects
|
||||
// placesContext_open:newtab to appear but not placesContext_open, add it to
|
||||
// the list of expected items when the pref is set.
|
||||
let expectedOptionItems = [...optionItems];
|
||||
if (
|
||||
loadBookmarksInNewTab &&
|
||||
optionItems.includes("placesContext_open:newtab") &&
|
||||
!optionItems.includes("placesContext_open")
|
||||
) {
|
||||
expectedOptionItems.push("placesContext_open");
|
||||
}
|
||||
|
||||
// The caller is responsible for opening the menu, via `cbfunc()`.
|
||||
let contextMenu = await cbfunc(bookmark);
|
||||
|
||||
for (let item of expectedOptionItems) {
|
||||
OptionItemExists(item, doc);
|
||||
}
|
||||
|
||||
OptionsMatchExpected(contextMenu, expectedOptionItems);
|
||||
|
||||
// Check the "default" attributes on placesContext_open and
|
||||
// placesContext_open:newtab.
|
||||
if (expectedOptionItems.includes("placesContext_open")) {
|
||||
Assert.equal(
|
||||
doc.getElementById("placesContext_open").getAttribute("default"),
|
||||
loadBookmarksInNewTab ? "" : "true",
|
||||
`placesContext_open has the correct "default" attribute when loadBookmarksInTabs = ${loadBookmarksInNewTab}`
|
||||
);
|
||||
}
|
||||
if (expectedOptionItems.includes("placesContext_open:newtab")) {
|
||||
Assert.equal(
|
||||
doc.getElementById("placesContext_open:newtab").getAttribute("default"),
|
||||
loadBookmarksInNewTab ? "true" : "",
|
||||
`placesContext_open:newtab has the correct "default" attribute when loadBookmarksInTabs = ${loadBookmarksInNewTab}`
|
||||
);
|
||||
}
|
||||
|
||||
contextMenu.hidePopup();
|
||||
}
|
||||
|
||||
OptionsMatchExpected(contextMenu, optionItems);
|
||||
contextMenu.hidePopup();
|
||||
Services.prefs.clearUserPref("browser.tabs.loadBookmarksInTabs");
|
||||
await PlacesUtils.bookmarks.eraseEverything();
|
||||
};
|
||||
|
||||
@ -149,7 +198,7 @@ add_task(async function test_bookmark_contextmenu_contents() {
|
||||
return contextMenu;
|
||||
}, optionItems);
|
||||
|
||||
let tab;
|
||||
let tabs = [];
|
||||
let contextMenuOnContent;
|
||||
|
||||
await checkContextMenu(async function() {
|
||||
@ -161,7 +210,11 @@ add_task(async function test_bookmark_contextmenu_contents() {
|
||||
});
|
||||
|
||||
info("Open context menu on about:config");
|
||||
tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:config");
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"about:config"
|
||||
);
|
||||
tabs.push(tab);
|
||||
contextMenuOnContent = document.getElementById("contentAreaContextMenu");
|
||||
const popupShownPromiseOnContent = BrowserTestUtils.waitForEvent(
|
||||
contextMenuOnContent,
|
||||
@ -172,6 +225,7 @@ add_task(async function test_bookmark_contextmenu_contents() {
|
||||
type: "contextmenu",
|
||||
});
|
||||
await popupShownPromiseOnContent;
|
||||
contextMenuOnContent.hidePopup();
|
||||
|
||||
info("Check context menu on bookmark");
|
||||
const toolbarNode = getToolbarNodeForItemGuid(toolbarBookmark.guid);
|
||||
@ -192,10 +246,11 @@ add_task(async function test_bookmark_contextmenu_contents() {
|
||||
|
||||
// We need to do a thorough cleanup to avoid leaking the window of
|
||||
// 'about:config'.
|
||||
const tabClosed = BrowserTestUtils.waitForTabClosing(tab);
|
||||
contextMenuOnContent.hidePopup();
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
await tabClosed;
|
||||
for (let tab of tabs) {
|
||||
const tabClosed = BrowserTestUtils.waitForTabClosing(tab);
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
await tabClosed;
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_empty_contextmenu_contents() {
|
||||
|
@ -174,6 +174,8 @@ class BrowserSearchTelemetryHandler {
|
||||
* true if this event was generated by a form history result.
|
||||
* @param {string} [details.alias=null]
|
||||
* The search engine alias used in the search, if any.
|
||||
* @param {string} [details.newtabSessionId=undefined]
|
||||
* The newtab session that prompted this search, if any.
|
||||
* @throws if source is not in the known sources list.
|
||||
*/
|
||||
recordSearch(browser, engine, source, details = {}) {
|
||||
@ -218,6 +220,17 @@ class BrowserSearchTelemetryHandler {
|
||||
this._recordSearch(browser, engine, details.url, source);
|
||||
break;
|
||||
}
|
||||
if (["urlbar-handoff", "abouthome", "newtab"].includes(source)) {
|
||||
Glean.newtabSearch.issued.record({
|
||||
newtab_visit_id: details.newtabSessionId,
|
||||
search_access_point: KNOWN_SEARCH_SOURCES.get(source),
|
||||
telemetry_id: engine.telemetryId,
|
||||
});
|
||||
lazy.SearchSERPTelemetry.recordBrowserNewtabSession(
|
||||
browser,
|
||||
details.newtabSessionId
|
||||
);
|
||||
}
|
||||
} catch (ex) {
|
||||
// Catch any errors here, so that search actions are not broken if
|
||||
// telemetry is broken for some reason.
|
||||
|
@ -79,6 +79,10 @@ class TelemetryHandler {
|
||||
// browser - one of the KNOWN_SEARCH_SOURCES in BrowserSearchTelemetry.
|
||||
_browserSourceMap = new WeakMap();
|
||||
|
||||
// _browserNewtabSessionMap is a map of the newtab session id for particular
|
||||
// browsers.
|
||||
_browserNewtabSessionMap = new WeakMap();
|
||||
|
||||
constructor() {
|
||||
this._contentHandler = new ContentHandler({
|
||||
browserInfoByURL: this._browserInfoByURL,
|
||||
@ -152,6 +156,19 @@ class TelemetryHandler {
|
||||
this._browserSourceMap.set(browser, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the newtab source for particular browsers, in case it needs
|
||||
* to be associated with a SERP.
|
||||
*
|
||||
* @param {browser} browser
|
||||
* The browser where the search originated.
|
||||
* @param {string} newtabSessionId
|
||||
* The sessionId of the newtab session the search originated from.
|
||||
*/
|
||||
recordBrowserNewtabSession(browser, newtabSessionId) {
|
||||
this._browserNewtabSessionMap.set(browser, newtabSessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the TabClose event received from the listeners.
|
||||
*
|
||||
@ -163,6 +180,7 @@ class TelemetryHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
this._browserNewtabSessionMap.delete(event.target.linkedBrowser);
|
||||
this.stopTrackingBrowser(event.target.linkedBrowser);
|
||||
}
|
||||
|
||||
@ -227,6 +245,7 @@ class TelemetryHandler {
|
||||
}
|
||||
let info = this._checkURLForSerpMatch(url);
|
||||
if (!info) {
|
||||
this._browserNewtabSessionMap.delete(browser);
|
||||
this.stopTrackingBrowser(browser);
|
||||
return;
|
||||
}
|
||||
@ -241,6 +260,13 @@ class TelemetryHandler {
|
||||
this._browserSourceMap.delete(browser);
|
||||
}
|
||||
|
||||
let newtabSessionId;
|
||||
if (this._browserNewtabSessionMap.has(browser)) {
|
||||
newtabSessionId = this._browserNewtabSessionMap.get(browser);
|
||||
// We leave the newtabSessionId in the map for this browser
|
||||
// until we stop loading SERP pages or the tab is closed.
|
||||
}
|
||||
|
||||
this._reportSerpPage(info, source, url);
|
||||
|
||||
let item = this._browserInfoByURL.get(url);
|
||||
@ -249,12 +275,14 @@ class TelemetryHandler {
|
||||
item.browsers.set(browser, "no ads reported");
|
||||
item.count++;
|
||||
item.source = source;
|
||||
item.newtabSessionId = newtabSessionId;
|
||||
} else {
|
||||
item = this._browserInfoByURL.set(url, {
|
||||
browsers: new WeakMap().set(browser, "no ads reported"),
|
||||
info,
|
||||
count: 1,
|
||||
source,
|
||||
newtabSessionId,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -770,6 +798,15 @@ class ContentHandler {
|
||||
1
|
||||
);
|
||||
wrappedChannel._adClickRecorded = true;
|
||||
if (item.newtabSessionId) {
|
||||
Glean.newtabSearchAd.click.record({
|
||||
newtab_visit_id: item.newtabSessionId,
|
||||
search_access_point: item.source,
|
||||
is_follow_on: item.info.type.endsWith("follow-on"),
|
||||
is_tagged: item.info.type.startsWith("tagged"),
|
||||
telemetry_id: item.info.provider,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
@ -827,6 +864,16 @@ class ContentHandler {
|
||||
);
|
||||
|
||||
item.browsers.set(browser, "ad reported");
|
||||
|
||||
if (item.newtabSessionId) {
|
||||
Glean.newtabSearchAd.impression.record({
|
||||
newtab_visit_id: item.newtabSessionId,
|
||||
search_access_point: item.source,
|
||||
is_follow_on: item.info.type.endsWith("follow-on"),
|
||||
is_tagged: item.info.type.startsWith("tagged"),
|
||||
telemetry_id: item.info.provider,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 5.3 KiB |
@ -2,7 +2,7 @@
|
||||
"name": "Ecosia",
|
||||
"description": "Search Ecosia",
|
||||
"manifest_version": 2,
|
||||
"version": "1.0",
|
||||
"version": "1.1",
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "ecosia@search.mozilla.org"
|
||||
|
117
browser/components/search/metrics.yaml
Normal file
117
browser/components/search/metrics.yaml
Normal file
@ -0,0 +1,117 @@
|
||||
# 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/.
|
||||
|
||||
# Adding a new metric? We have docs for that!
|
||||
# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html
|
||||
|
||||
---
|
||||
$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
|
||||
$tags:
|
||||
- 'Firefox :: Search'
|
||||
|
||||
newtab.search:
|
||||
issued:
|
||||
type: event
|
||||
description: >
|
||||
When Firefox was asked to issue a search from a Search Access Point (SAP)
|
||||
on a newtab page.
|
||||
Doesn't record searches in Private Browsing Mode unless
|
||||
`browser.engagement.search_counts.pbm` is set to `true`.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_sensitivity:
|
||||
- interaction
|
||||
notification_emails:
|
||||
- anicholson@mozilla.com
|
||||
- chutten@mozilla.com
|
||||
- mmccorquodale@mozilla.com
|
||||
- najiang@mozilla.com
|
||||
expires: 107
|
||||
extra_keys:
|
||||
newtab_visit_id: &newtab_visit_id
|
||||
description: >
|
||||
The id of the newtab visit that originated the search.
|
||||
Should always be present for handoff searches.
|
||||
TODO(bug 1774597): for searches done without handoff (e.g. with
|
||||
`browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar`
|
||||
set to `false`), the active newtab visit id is unknown.
|
||||
type: string
|
||||
search_access_point: &search_access_point
|
||||
description: >
|
||||
One of the search access points available on the new tab like
|
||||
* `urlbar_handoff`
|
||||
* `about_home`
|
||||
* `about_newtab`
|
||||
type: string
|
||||
telemetry_id: &telemetry_id
|
||||
description: >
|
||||
The search engine's `telemetryId`, like `google-b-d`.
|
||||
This is set to be a telemetry-specific id for app-provided engines,
|
||||
and is `other-<name>` for others (where `<name>` is the engine's
|
||||
WebExtension name).
|
||||
type: string
|
||||
send_in_pings:
|
||||
- newtab
|
||||
|
||||
newtab.search.ad:
|
||||
impression:
|
||||
type: event
|
||||
description: >
|
||||
Recorded when a newtab visit resulted in a search that
|
||||
loaded a Search Engine Result Page (SERP) that contains an ad link.
|
||||
And the SERP is visible.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_sensitivity:
|
||||
- interaction
|
||||
notification_emails:
|
||||
- anicholson@mozilla.com
|
||||
- chutten@mozilla.com
|
||||
- mmccorquodale@mozilla.com
|
||||
- najiang@mozilla.com
|
||||
expires: 107
|
||||
extra_keys:
|
||||
newtab_visit_id: *newtab_visit_id
|
||||
search_access_point: *search_access_point
|
||||
is_follow_on: &is_follow_on
|
||||
description: >
|
||||
Whether the preceding search happened on a search results page.
|
||||
type: boolean
|
||||
is_tagged: &is_tagged
|
||||
description: >
|
||||
Whether the preceding search was tagged with a partner code.
|
||||
type: boolean
|
||||
telemetry_id: *telemetry_id
|
||||
send_in_pings:
|
||||
- newtab
|
||||
|
||||
click:
|
||||
type: event
|
||||
description: >
|
||||
Recorded when an ad link is clicked on a Search Engine Result Page (SERP)
|
||||
which was loaded by a seach that began on a newtab page.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1766887
|
||||
data_sensitivity:
|
||||
- interaction
|
||||
notification_emails:
|
||||
- anicholson@mozilla.com
|
||||
- chutten@mozilla.com
|
||||
- mmccorquodale@mozilla.com
|
||||
- najiang@mozilla.com
|
||||
expires: 107
|
||||
extra_keys:
|
||||
newtab_visit_id: *newtab_visit_id
|
||||
search_access_point: *search_access_point
|
||||
is_follow_on: *is_follow_on
|
||||
is_tagged: *is_tagged
|
||||
telemetry_id: *telemetry_id
|
||||
send_in_pings:
|
||||
- newtab
|
@ -59,6 +59,7 @@ add_task(async function test_abouthome_activitystream_simpleQuery() {
|
||||
// Let's reset the counts.
|
||||
Services.telemetry.clearScalars();
|
||||
Services.telemetry.clearEvents();
|
||||
Services.fog.testResetFOG();
|
||||
let search_hist = TelemetryTestUtils.getAndClearKeyedHistogram(
|
||||
"SEARCH_COUNTS"
|
||||
);
|
||||
@ -122,5 +123,18 @@ add_task(async function test_abouthome_activitystream_simpleQuery() {
|
||||
{ category: "navigation", method: "search" }
|
||||
);
|
||||
|
||||
// Also also check Glean events.
|
||||
const record = Glean.newtabSearch.issued.testGetValue();
|
||||
Assert.ok(!!record, "Must have recorded a search issuance");
|
||||
Assert.equal(record.length, 1, "One search, one event");
|
||||
Assert.deepEqual(
|
||||
{
|
||||
search_access_point: "about_home",
|
||||
telemetry_id: "other-MozSearch",
|
||||
},
|
||||
record[0].extra,
|
||||
"Must have recorded the expected information."
|
||||
);
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
@ -135,6 +135,7 @@ add_task(async function test_about_newtab() {
|
||||
// Let's reset the counts.
|
||||
Services.telemetry.clearScalars();
|
||||
Services.telemetry.clearEvents();
|
||||
Services.fog.testResetFOG();
|
||||
let search_hist = TelemetryTestUtils.getAndClearKeyedHistogram(
|
||||
"SEARCH_COUNTS"
|
||||
);
|
||||
@ -191,6 +192,19 @@ add_task(async function test_about_newtab() {
|
||||
{ category: "navigation", method: "search" }
|
||||
);
|
||||
|
||||
// Also also check Glean events.
|
||||
const record = Glean.newtabSearch.issued.testGetValue();
|
||||
Assert.ok(!!record, "Must have recorded a search issuance");
|
||||
Assert.equal(record.length, 1, "One search, one event");
|
||||
Assert.deepEqual(
|
||||
{
|
||||
search_access_point: "about_newtab",
|
||||
telemetry_id: "other-MozSearch",
|
||||
},
|
||||
record[0].extra,
|
||||
"Must have recorded the expected information."
|
||||
);
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
@ -204,6 +204,7 @@ add_task(async function test_source_urlbar_handoff() {
|
||||
"urlbar-handoff",
|
||||
"urlbar_handoff",
|
||||
async () => {
|
||||
Services.fog.testResetFOG();
|
||||
tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
BrowserTestUtils.loadURI(tab.linkedBrowser, "about:newtab");
|
||||
await BrowserTestUtils.browserStopped(tab.linkedBrowser, "about:newtab");
|
||||
@ -237,6 +238,47 @@ add_task(async function test_source_urlbar_handoff() {
|
||||
return tab;
|
||||
},
|
||||
async () => {
|
||||
const issueRecords = Glean.newtabSearch.issued.testGetValue();
|
||||
Assert.ok(!!issueRecords, "Must have recorded a search issuance");
|
||||
Assert.equal(issueRecords.length, 1, "One search, one event");
|
||||
const newtabVisitId = issueRecords[0].extra.newtab_visit_id;
|
||||
Assert.ok(!!newtabVisitId, "Must have a visit id");
|
||||
Assert.deepEqual(
|
||||
{
|
||||
// Yes, this is tautological. But I want to use deepEqual.
|
||||
newtab_visit_id: newtabVisitId,
|
||||
search_access_point: "urlbar_handoff",
|
||||
telemetry_id: "other-Example",
|
||||
},
|
||||
issueRecords[0].extra,
|
||||
"Must have recorded the expected information."
|
||||
);
|
||||
const impRecords = Glean.newtabSearchAd.impression.testGetValue();
|
||||
Assert.equal(impRecords.length, 1, "One impression, one event.");
|
||||
Assert.deepEqual(
|
||||
{
|
||||
newtab_visit_id: newtabVisitId,
|
||||
search_access_point: "urlbar_handoff",
|
||||
telemetry_id: "example",
|
||||
is_tagged: "true",
|
||||
is_follow_on: "false",
|
||||
},
|
||||
impRecords[0].extra,
|
||||
"Must have recorded the expected information."
|
||||
);
|
||||
const clickRecords = Glean.newtabSearchAd.click.testGetValue();
|
||||
Assert.equal(clickRecords.length, 1, "One click, one event.");
|
||||
Assert.deepEqual(
|
||||
{
|
||||
newtab_visit_id: newtabVisitId,
|
||||
search_access_point: "urlbar_handoff",
|
||||
telemetry_id: "example",
|
||||
is_tagged: "true",
|
||||
is_follow_on: "false",
|
||||
},
|
||||
clickRecords[0].extra,
|
||||
"Must have recorded the expected information."
|
||||
);
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
);
|
||||
|
@ -194,7 +194,7 @@ function createEntry(
|
||||
|
||||
element.setAttribute("label", aMenuLabel);
|
||||
if (aClosedTab.image) {
|
||||
const iconURL = lazy.PlacesUIUtils.getImageURL(aClosedTab);
|
||||
const iconURL = lazy.PlacesUIUtils.getImageURL(aClosedTab.image);
|
||||
element.setAttribute("image", iconURL);
|
||||
}
|
||||
if (!aIsWindowsFragment) {
|
||||
|
@ -775,6 +775,7 @@ class TelemetryEvent {
|
||||
};
|
||||
|
||||
let { queryContext } = this._controller._lastQueryContextWrapper || {};
|
||||
|
||||
this._controller.manager.notifyEngagementChange(
|
||||
this._isPrivate,
|
||||
"start",
|
||||
@ -905,6 +906,13 @@ class TelemetryEvent {
|
||||
);
|
||||
|
||||
let { queryContext } = this._controller._lastQueryContextWrapper || {};
|
||||
|
||||
if (method === "engagement" && queryContext.results?.[0].autofill) {
|
||||
// Record autofill impressions upon engagement.
|
||||
const type = UrlbarUtils.telemetryTypeFromResult(queryContext.results[0]);
|
||||
Services.telemetry.scalarAdd(`urlbar.impression.${type}`, 1);
|
||||
}
|
||||
|
||||
this._controller.manager.notifyEngagementChange(
|
||||
this._isPrivate,
|
||||
method,
|
||||
|
@ -723,10 +723,12 @@ class UrlbarInput {
|
||||
* @param {nsISearchEngine} [searchEngine]
|
||||
* Optional. If included and the right prefs are set, we will enter search
|
||||
* mode when handing `searchString` from the fake input to the Urlbar.
|
||||
* @param {string} newtabSessionId
|
||||
* Optional. The id of the newtab session that handed off this search.
|
||||
*
|
||||
*/
|
||||
handoff(searchString, searchEngine) {
|
||||
this._isHandoffSession = true;
|
||||
handoff(searchString, searchEngine, newtabSessionId) {
|
||||
this._handoffSession = newtabSessionId;
|
||||
if (UrlbarPrefs.get("shouldHandOffToSearchMode") && searchEngine) {
|
||||
this.search(searchString, {
|
||||
searchEngine,
|
||||
@ -2350,7 +2352,7 @@ class UrlbarInput {
|
||||
const isOneOff = this.view.oneOffSearchButtons.eventTargetIsAOneOff(event);
|
||||
|
||||
let source = "urlbar";
|
||||
if (this._isHandoffSession) {
|
||||
if (this._handoffSession) {
|
||||
source = "urlbar-handoff";
|
||||
} else if (this.searchMode && !isOneOff) {
|
||||
// Without checking !isOneOff, we might record the string
|
||||
@ -2365,7 +2367,11 @@ class UrlbarInput {
|
||||
this.window.gBrowser.selectedBrowser,
|
||||
engine,
|
||||
source,
|
||||
{ ...searchActionDetails, isOneOff }
|
||||
{
|
||||
...searchActionDetails,
|
||||
isOneOff,
|
||||
newtabSessionId: this._handoffSession,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -2877,7 +2883,7 @@ class UrlbarInput {
|
||||
|
||||
_on_blur(event) {
|
||||
this.focusedViaMousedown = false;
|
||||
this._isHandoffSession = false;
|
||||
this._handoffSession = undefined;
|
||||
|
||||
// We cannot count every blur events after a missed engagement as abandoment
|
||||
// because the user may have clicked on some view element that executes
|
||||
|
@ -65,6 +65,27 @@ urlbar.engagement
|
||||
example by picking a result in the urlbar panel or typing a search term or URL
|
||||
in the urlbar and pressing the enter key.
|
||||
|
||||
urlbar.impression.*
|
||||
A uint recording the number of impression that was displaying when user picks
|
||||
any result.
|
||||
|
||||
- ``autofill_about``
|
||||
For about-page type autofill.
|
||||
- ``autofill_adaptive``
|
||||
For adaptive history type autofill.
|
||||
- ``autofill_origin``
|
||||
For origin type autofill.
|
||||
- ``autofill_other``
|
||||
Counts how many times some other type of autofill result that does not have
|
||||
a specific scalar was shown. This is a fallback that is used when the code is
|
||||
not properly setting a specific autofill type, and it should not normally be
|
||||
used. If it appears in the data, it means we need to investigate and fix the
|
||||
code that is not properly setting a specific autofill type.
|
||||
- ``autofill_preloaded``
|
||||
For preloaded site type autofill.
|
||||
- ``autofill_url``
|
||||
For url type autofill.
|
||||
|
||||
urlbar.tips
|
||||
This is a keyed scalar whose values are uints and are incremented each time a
|
||||
tip result is shown, a tip is picked, and a tip's help button is picked. The
|
||||
@ -215,10 +236,21 @@ urlbar.picked.*
|
||||
- ``autofill``
|
||||
An origin or a URL completed the user typed text inline. This was deprecated
|
||||
in Firefox 102.
|
||||
- ``autofill_about``
|
||||
An about-page completed the user typed text inline.
|
||||
- ``autofill_adaptive``
|
||||
An adaptive history completed the user typed text inline.
|
||||
- ``autofill_origin``
|
||||
An origin completed the user typed text inline.
|
||||
- ``autofill_other``
|
||||
Counts how many times some other type of autofill result that does not have
|
||||
a specific keyed scalar was picked at a given index. This is a fallback that
|
||||
is used when the code is not properly setting a specific autofill type, and
|
||||
it should not normally be used. If it appears in the data, it means we need
|
||||
to investigate and fix the code that is not properly setting a specific
|
||||
autofill type.
|
||||
- ``autofill_preloaded``
|
||||
A preloaded site completed the user typed text inline.
|
||||
- ``autofill_url``
|
||||
A URL completed the user typed text inline.
|
||||
- ``bookmark``
|
||||
|
@ -73,31 +73,90 @@ function assertTelemetryResults(histograms, type, index, method) {
|
||||
* @param {string} searchString
|
||||
* @param {string} autofilledValue
|
||||
* The input's expected value after autofill occurs.
|
||||
* @param {string} unpickResult
|
||||
* Optional: If true, do not pick any result. Default value is false.
|
||||
* @param {string} urlToSelect
|
||||
* Optional: If want to select result except autofill, pass the URL.
|
||||
*/
|
||||
async function triggerAutofillAndPickResult(searchString, autofilledValue) {
|
||||
async function triggerAutofillAndPickResult(
|
||||
searchString,
|
||||
autofilledValue,
|
||||
unpickResult = false,
|
||||
urlToSelect = null
|
||||
) {
|
||||
await BrowserTestUtils.withNewTab("about:blank", async () => {
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
value: searchString,
|
||||
fireInputEvent: true,
|
||||
});
|
||||
|
||||
let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
|
||||
Assert.ok(details.autofill, "Result is autofill");
|
||||
Assert.equal(gURLBar.value, autofilledValue, "gURLBar.value");
|
||||
Assert.equal(gURLBar.selectionStart, searchString.length, "selectionStart");
|
||||
Assert.equal(gURLBar.selectionEnd, autofilledValue.length, "selectionEnd");
|
||||
|
||||
if (urlToSelect) {
|
||||
for (let row = 0; row < UrlbarTestUtils.getResultCount(window); row++) {
|
||||
const result = await UrlbarTestUtils.getDetailsOfResultAt(window, row);
|
||||
if (result.url === urlToSelect) {
|
||||
UrlbarTestUtils.setSelectedRowIndex(window, row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unpickResult) {
|
||||
// Close popup without any action.
|
||||
await UrlbarTestUtils.promisePopupClose(window);
|
||||
return;
|
||||
}
|
||||
|
||||
let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
await loadPromise;
|
||||
|
||||
let url = autofilledValue.includes(":")
|
||||
? autofilledValue
|
||||
: "http://" + autofilledValue;
|
||||
let url;
|
||||
if (urlToSelect) {
|
||||
url = urlToSelect;
|
||||
} else {
|
||||
url = autofilledValue.includes(":")
|
||||
? autofilledValue
|
||||
: "http://" + autofilledValue;
|
||||
}
|
||||
Assert.equal(gBrowser.currentURI.spec, url, "Loaded URL is correct");
|
||||
});
|
||||
}
|
||||
|
||||
function createOtherAutofillProvider(searchString, autofilledValue) {
|
||||
return new UrlbarTestUtils.TestProvider({
|
||||
priority: Infinity,
|
||||
type: UrlbarUtils.PROVIDER_TYPE.HEURISTIC,
|
||||
results: [
|
||||
Object.assign(
|
||||
new UrlbarResult(
|
||||
UrlbarUtils.RESULT_TYPE.URL,
|
||||
UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
|
||||
{
|
||||
title: "Test",
|
||||
url: "http://example.com/",
|
||||
}
|
||||
),
|
||||
{
|
||||
heuristic: true,
|
||||
autofill: {
|
||||
value: autofilledValue,
|
||||
selectionStart: searchString.length,
|
||||
selectionEnd: autofilledValue.length,
|
||||
// Leave out `type` to trigger "other"
|
||||
},
|
||||
}
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// Allow more time for Mac machines so they don't time out in verify mode.
|
||||
if (AppConstants.platform == "macosx") {
|
||||
requestLongerTimeout(3);
|
||||
@ -106,8 +165,15 @@ if (AppConstants.platform == "macosx") {
|
||||
add_setup(async function() {
|
||||
await PlacesUtils.history.clear();
|
||||
await PlacesUtils.bookmarks.eraseEverything();
|
||||
await PlacesTestUtils.clearInputHistory();
|
||||
|
||||
// Enable local telemetry recording for the duration of the tests.
|
||||
const originalCanRecord = Services.telemetry.canRecordExtended;
|
||||
Services.telemetry.canRecordExtended = true;
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
Services.telemetry.canRecordExtended = originalCanRecord;
|
||||
await PlacesTestUtils.clearInputHistory();
|
||||
await PlacesUtils.history.clear();
|
||||
});
|
||||
});
|
||||
@ -116,7 +182,7 @@ add_setup(async function() {
|
||||
add_task(async function history() {
|
||||
const testData = [
|
||||
{
|
||||
adaptiveHistoryPref: true,
|
||||
useAdaptiveHistory: true,
|
||||
visitHistory: ["http://example.com/test"],
|
||||
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
|
||||
userInput: "ex",
|
||||
@ -124,7 +190,7 @@ add_task(async function history() {
|
||||
expected: "autofill_origin",
|
||||
},
|
||||
{
|
||||
adaptiveHistoryPref: true,
|
||||
useAdaptiveHistory: true,
|
||||
visitHistory: ["http://example.com/test"],
|
||||
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
|
||||
userInput: "exa",
|
||||
@ -132,7 +198,7 @@ add_task(async function history() {
|
||||
expected: "autofill_adaptive",
|
||||
},
|
||||
{
|
||||
adaptiveHistoryPref: true,
|
||||
useAdaptiveHistory: true,
|
||||
visitHistory: ["http://example.com/test"],
|
||||
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
|
||||
userInput: "exam",
|
||||
@ -140,7 +206,7 @@ add_task(async function history() {
|
||||
expected: "autofill_adaptive",
|
||||
},
|
||||
{
|
||||
adaptiveHistoryPref: true,
|
||||
useAdaptiveHistory: true,
|
||||
visitHistory: ["http://example.com/test"],
|
||||
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
|
||||
userInput: "example.com",
|
||||
@ -148,7 +214,7 @@ add_task(async function history() {
|
||||
expected: "autofill_adaptive",
|
||||
},
|
||||
{
|
||||
adaptiveHistoryPref: true,
|
||||
useAdaptiveHistory: true,
|
||||
visitHistory: ["http://example.com/test"],
|
||||
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
|
||||
userInput: "example.com/",
|
||||
@ -156,7 +222,7 @@ add_task(async function history() {
|
||||
expected: "autofill_adaptive",
|
||||
},
|
||||
{
|
||||
adaptiveHistoryPref: true,
|
||||
useAdaptiveHistory: true,
|
||||
visitHistory: ["http://example.com/test"],
|
||||
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
|
||||
userInput: "example.com/test",
|
||||
@ -164,7 +230,7 @@ add_task(async function history() {
|
||||
expected: "autofill_adaptive",
|
||||
},
|
||||
{
|
||||
adaptiveHistoryPref: true,
|
||||
useAdaptiveHistory: true,
|
||||
visitHistory: ["http://example.com/test", "http://example.org/test"],
|
||||
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
|
||||
userInput: "example.org",
|
||||
@ -172,7 +238,7 @@ add_task(async function history() {
|
||||
expected: "autofill_origin",
|
||||
},
|
||||
{
|
||||
adaptiveHistoryPref: true,
|
||||
useAdaptiveHistory: true,
|
||||
visitHistory: ["http://example.com/test", "http://example.com/test/url"],
|
||||
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
|
||||
userInput: "example.com/test/",
|
||||
@ -180,7 +246,7 @@ add_task(async function history() {
|
||||
expected: "autofill_url",
|
||||
},
|
||||
{
|
||||
adaptiveHistoryPref: true,
|
||||
useAdaptiveHistory: true,
|
||||
visitHistory: [{ uri: "http://example.com/test" }],
|
||||
inputHistory: [
|
||||
{ uri: "http://example.com/test", input: "http://example.com/test" },
|
||||
@ -190,7 +256,7 @@ add_task(async function history() {
|
||||
expected: "autofill_adaptive",
|
||||
},
|
||||
{
|
||||
adaptiveHistoryPref: false,
|
||||
useAdaptiveHistory: false,
|
||||
visitHistory: [{ uri: "http://example.com/test" }],
|
||||
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
|
||||
userInput: "example",
|
||||
@ -198,7 +264,7 @@ add_task(async function history() {
|
||||
expected: "autofill_origin",
|
||||
},
|
||||
{
|
||||
adaptiveHistoryPref: false,
|
||||
useAdaptiveHistory: false,
|
||||
visitHistory: [{ uri: "http://example.com/test" }],
|
||||
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
|
||||
userInput: "example.com/te",
|
||||
@ -208,7 +274,7 @@ add_task(async function history() {
|
||||
];
|
||||
|
||||
for (const {
|
||||
adaptiveHistoryPref,
|
||||
useAdaptiveHistory,
|
||||
visitHistory,
|
||||
inputHistory,
|
||||
userInput,
|
||||
@ -222,7 +288,7 @@ add_task(async function history() {
|
||||
await UrlbarUtils.addToInputHistory(uri, input);
|
||||
}
|
||||
|
||||
UrlbarPrefs.set("autoFill.adaptiveHistory.enabled", adaptiveHistoryPref);
|
||||
UrlbarPrefs.set("autoFill.adaptiveHistory.enabled", useAdaptiveHistory);
|
||||
|
||||
await triggerAutofillAndPickResult(userInput, autofilled);
|
||||
|
||||
@ -235,6 +301,7 @@ add_task(async function history() {
|
||||
);
|
||||
|
||||
UrlbarPrefs.clear("autoFill.adaptiveHistory.enabled");
|
||||
await PlacesTestUtils.clearInputHistory();
|
||||
await PlacesUtils.history.clear();
|
||||
}
|
||||
});
|
||||
@ -283,32 +350,7 @@ add_task(async function preloaded() {
|
||||
add_task(async function other() {
|
||||
let searchString = "exam";
|
||||
let autofilledValue = "example.com/";
|
||||
|
||||
let provider = new UrlbarTestUtils.TestProvider({
|
||||
priority: Infinity,
|
||||
type: UrlbarUtils.PROVIDER_TYPE.HEURISTIC,
|
||||
results: [
|
||||
Object.assign(
|
||||
new UrlbarResult(
|
||||
UrlbarUtils.RESULT_TYPE.URL,
|
||||
UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
|
||||
{
|
||||
title: "Test",
|
||||
url: "http://example.com/",
|
||||
}
|
||||
),
|
||||
{
|
||||
heuristic: true,
|
||||
autofill: {
|
||||
value: autofilledValue,
|
||||
selectionStart: searchString.length,
|
||||
selectionEnd: autofilledValue.length,
|
||||
// Leave out `type` to trigger "other"
|
||||
},
|
||||
}
|
||||
),
|
||||
],
|
||||
});
|
||||
let provider = createOtherAutofillProvider(searchString, autofilledValue);
|
||||
UrlbarProvidersManager.registerProvider(provider);
|
||||
|
||||
let histograms = snapshotHistograms();
|
||||
@ -325,3 +367,214 @@ add_task(async function other() {
|
||||
await PlacesUtils.history.clear();
|
||||
UrlbarProvidersManager.unregisterProvider(provider);
|
||||
});
|
||||
|
||||
// Checks impression telemetry.
|
||||
add_task(async function impression() {
|
||||
const testData = [
|
||||
{
|
||||
description: "Adaptive history autofill and pick it",
|
||||
useAdaptiveHistory: true,
|
||||
visitHistory: ["http://example.com/first", "http://example.com/second"],
|
||||
inputHistory: [{ uri: "http://example.com/first", input: "exa" }],
|
||||
userInput: "exa",
|
||||
autofilled: "example.com/first",
|
||||
expected: "autofill_adaptive",
|
||||
},
|
||||
{
|
||||
description: "Adaptive history autofill but pick another result",
|
||||
useAdaptiveHistory: true,
|
||||
visitHistory: ["http://example.com/first", "http://example.com/second"],
|
||||
inputHistory: [{ uri: "http://example.com/first", input: "exa" }],
|
||||
userInput: "exa",
|
||||
urlToSelect: "http://example.com/second",
|
||||
autofilled: "example.com/first",
|
||||
expected: "autofill_adaptive",
|
||||
},
|
||||
{
|
||||
description: "Adaptive history autofill but not pick any result",
|
||||
unpickResult: true,
|
||||
useAdaptiveHistory: true,
|
||||
visitHistory: ["http://example.com/first", "http://example.com/second"],
|
||||
inputHistory: [{ uri: "http://example.com/first", input: "exa" }],
|
||||
userInput: "exa",
|
||||
autofilled: "example.com/first",
|
||||
},
|
||||
{
|
||||
description: "Origin autofill and pick it",
|
||||
visitHistory: ["http://example.com/first", "http://example.com/second"],
|
||||
userInput: "exa",
|
||||
autofilled: "example.com/",
|
||||
expected: "autofill_origin",
|
||||
},
|
||||
{
|
||||
description: "Origin autofill but pick another result",
|
||||
visitHistory: ["http://example.com/first", "http://example.com/second"],
|
||||
userInput: "exa",
|
||||
urlToSelect: "http://example.com/second",
|
||||
autofilled: "example.com/",
|
||||
expected: "autofill_origin",
|
||||
},
|
||||
{
|
||||
description: "Origin autofill but not pick any result",
|
||||
unpickResult: true,
|
||||
visitHistory: ["http://example.com/first", "http://example.com/second"],
|
||||
userInput: "exa",
|
||||
autofilled: "example.com/",
|
||||
},
|
||||
{
|
||||
description: "URL autofill and pick it",
|
||||
visitHistory: ["http://example.com/first", "http://example.com/second"],
|
||||
userInput: "example.com/",
|
||||
autofilled: "example.com/",
|
||||
expected: "autofill_url",
|
||||
},
|
||||
{
|
||||
description: "URL autofill but pick another result",
|
||||
visitHistory: ["http://example.com/first", "http://example.com/second"],
|
||||
userInput: "example.com/",
|
||||
urlToSelect: "http://example.com/second",
|
||||
autofilled: "example.com/",
|
||||
expected: "autofill_url",
|
||||
},
|
||||
{
|
||||
description: "URL autofill but not pick any result",
|
||||
unpickResult: true,
|
||||
visitHistory: ["http://example.com/first", "http://example.com/second"],
|
||||
userInput: "example.com/",
|
||||
autofilled: "example.com/",
|
||||
},
|
||||
{
|
||||
description: "about page autofill and pick it",
|
||||
userInput: "about:a",
|
||||
autofilled: "about:about",
|
||||
expected: "autofill_about",
|
||||
},
|
||||
{
|
||||
description: "about page autofill but pick another result",
|
||||
userInput: "about:a",
|
||||
urlToSelect: "about:addons",
|
||||
autofilled: "about:about",
|
||||
expected: "autofill_about",
|
||||
},
|
||||
{
|
||||
description: "about page autofill but not pick any result",
|
||||
unpickResult: true,
|
||||
userInput: "about:a",
|
||||
autofilled: "about:about",
|
||||
},
|
||||
{
|
||||
description: "Preloaded site autofill and pick it",
|
||||
usePreloadedSite: true,
|
||||
preloadedSites: [["http://example.com/", "Example"]],
|
||||
userInput: "exa",
|
||||
autofilled: "example.com/",
|
||||
expected: "autofill_preloaded",
|
||||
},
|
||||
{
|
||||
description: "Preloaded site autofill but not pick any result",
|
||||
unpickResult: true,
|
||||
usePreloadedSite: true,
|
||||
preloadedSites: [["http://example.com/", "Example"]],
|
||||
userInput: "exa",
|
||||
autofilled: "example.com/",
|
||||
},
|
||||
{
|
||||
description: "Other provider's autofill and pick it",
|
||||
useOtherProvider: true,
|
||||
userInput: "example",
|
||||
autofilled: "example.com/",
|
||||
expected: "autofill_other",
|
||||
},
|
||||
{
|
||||
description: "Other provider's autofill but not pick any result",
|
||||
unpickResult: true,
|
||||
useOtherProvider: true,
|
||||
userInput: "example",
|
||||
autofilled: "example.com/",
|
||||
},
|
||||
];
|
||||
|
||||
for (const {
|
||||
description,
|
||||
useAdaptiveHistory = false,
|
||||
usePreloadedSite = false,
|
||||
useOtherProvider = false,
|
||||
unpickResult = false,
|
||||
visitHistory,
|
||||
inputHistory,
|
||||
preloadedSites,
|
||||
userInput,
|
||||
select,
|
||||
autofilled,
|
||||
expected,
|
||||
} of testData) {
|
||||
info(description);
|
||||
|
||||
UrlbarPrefs.set("autoFill.adaptiveHistory.enabled", useAdaptiveHistory);
|
||||
if (usePreloadedSite) {
|
||||
UrlbarPrefs.set("usepreloadedtopurls.enabled", true);
|
||||
UrlbarPrefs.set("usepreloadedtopurls.expire_days", 100);
|
||||
}
|
||||
let otherProvider;
|
||||
if (useOtherProvider) {
|
||||
otherProvider = createOtherAutofillProvider(userInput, autofilled);
|
||||
UrlbarProvidersManager.registerProvider(otherProvider);
|
||||
}
|
||||
|
||||
if (visitHistory) {
|
||||
await PlacesTestUtils.addVisits(visitHistory);
|
||||
}
|
||||
if (inputHistory) {
|
||||
for (const { uri, input } of inputHistory) {
|
||||
await UrlbarUtils.addToInputHistory(uri, input);
|
||||
}
|
||||
}
|
||||
if (preloadedSites) {
|
||||
UrlbarProviderPreloadedSites.populatePreloadedSiteStorage(preloadedSites);
|
||||
}
|
||||
|
||||
await triggerAutofillAndPickResult(
|
||||
userInput,
|
||||
autofilled,
|
||||
unpickResult,
|
||||
select
|
||||
);
|
||||
|
||||
const scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
|
||||
if (unpickResult) {
|
||||
TelemetryTestUtils.assertScalarUnset(
|
||||
scalars,
|
||||
"urlbar.impression.autofill_adaptive"
|
||||
);
|
||||
TelemetryTestUtils.assertScalarUnset(
|
||||
scalars,
|
||||
"urlbar.impression.autofill_origin"
|
||||
);
|
||||
TelemetryTestUtils.assertScalarUnset(
|
||||
scalars,
|
||||
"urlbar.impression.autofill_url"
|
||||
);
|
||||
TelemetryTestUtils.assertScalarUnset(
|
||||
scalars,
|
||||
"urlbar.impression.autofill_about"
|
||||
);
|
||||
} else {
|
||||
TelemetryTestUtils.assertScalar(
|
||||
scalars,
|
||||
`urlbar.impression.${expected}`,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
UrlbarPrefs.clear("autoFill.adaptiveHistory.enabled");
|
||||
UrlbarPrefs.clear("usepreloadedtopurls.enabled");
|
||||
UrlbarPrefs.clear("usepreloadedtopurls.expire_days");
|
||||
|
||||
if (otherProvider) {
|
||||
UrlbarProvidersManager.unregisterProvider(otherProvider);
|
||||
}
|
||||
|
||||
await PlacesTestUtils.clearInputHistory();
|
||||
await PlacesUtils.history.clear();
|
||||
}
|
||||
});
|
||||
|
@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "Web Compatibility Interventions",
|
||||
"description": "Urgent post-release fixes for web compatibility.",
|
||||
"version": "102.14.0",
|
||||
"version": "102.15.0",
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "webcompat@mozilla.org",
|
||||
|
@ -71,3 +71,7 @@ if (window.gapi?._pl === undefined) {
|
||||
zoomableimage: stub,
|
||||
};
|
||||
}
|
||||
|
||||
for (const e of document.querySelectorAll("ins.adsbygoogle")) {
|
||||
e.style.maxWidth = "0px";
|
||||
}
|
||||
|
@ -9,44 +9,93 @@ const TEST_URI = `
|
||||
<style type='text/css'>
|
||||
div {
|
||||
color: red;
|
||||
font-family: "courier";
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
<div>test</div>
|
||||
`;
|
||||
|
||||
/*
|
||||
This array will be iterated over in order and the `value` property will be used to
|
||||
update the value of the first CSS declaration.
|
||||
This object contains iteration steps to modify various CSS properties of the
|
||||
test element, keyed by property name,.
|
||||
Each value is an array which will be iterated over in order and the `value`
|
||||
property will be used to update the value of the property.
|
||||
The `add` and `remove` objects hold the expected values of the tracked declarations
|
||||
shown in the Changes panel. If `add` or `remove` are null, it means we don't expect
|
||||
any corresponding tracked declaration to show up in the Changes panel.
|
||||
*/
|
||||
const VALUE_CHANGE_ITERATIONS = [
|
||||
// No changes should be tracked if the value did not actually change.
|
||||
{
|
||||
value: "red",
|
||||
add: null,
|
||||
remove: null,
|
||||
},
|
||||
// Changing the priority flag "!important" should be tracked.
|
||||
{
|
||||
value: "red !important",
|
||||
add: { value: "red !important" },
|
||||
remove: { value: "red" },
|
||||
},
|
||||
// Repeated changes should still show the original value as the one removed.
|
||||
{
|
||||
value: "blue",
|
||||
add: { value: "blue" },
|
||||
remove: { value: "red" },
|
||||
},
|
||||
// Restoring the original value should clear tracked changes.
|
||||
{
|
||||
value: "red",
|
||||
add: null,
|
||||
remove: null,
|
||||
},
|
||||
];
|
||||
const ITERATIONS = {
|
||||
color: [
|
||||
// No changes should be tracked if the value did not actually change.
|
||||
{
|
||||
value: "red",
|
||||
add: null,
|
||||
remove: null,
|
||||
},
|
||||
// Changing the priority flag "!important" should be tracked.
|
||||
{
|
||||
value: "red !important",
|
||||
add: { value: "red !important" },
|
||||
remove: { value: "red" },
|
||||
},
|
||||
// Repeated changes should still show the original value as the one removed.
|
||||
{
|
||||
value: "blue",
|
||||
add: { value: "blue" },
|
||||
remove: { value: "red" },
|
||||
},
|
||||
// Restoring the original value should clear tracked changes.
|
||||
{
|
||||
value: "red",
|
||||
add: null,
|
||||
remove: null,
|
||||
},
|
||||
],
|
||||
"font-family": [
|
||||
// Set a value with an opening quote, missing the closing one.
|
||||
// The closing quote should still appear in the "add" value.
|
||||
{
|
||||
value: '"ar',
|
||||
add: { value: '"ar"' },
|
||||
remove: { value: '"courier"' },
|
||||
// For some reason we need an additional flush the first time we set a
|
||||
// value with a quote. Since the ruleview is manually flushed when opened
|
||||
// openRuleView, we need to pass this information all the way down to the
|
||||
// setProperty helper.
|
||||
needsExtraFlush: true,
|
||||
},
|
||||
// Add an escaped character
|
||||
{
|
||||
value: '"ar\\i',
|
||||
add: { value: '"ar\\i"' },
|
||||
remove: { value: '"courier"' },
|
||||
},
|
||||
// Add some more text
|
||||
{
|
||||
value: '"ar\\ia',
|
||||
add: { value: '"ar\\ia"' },
|
||||
remove: { value: '"courier"' },
|
||||
},
|
||||
// Remove the backslash
|
||||
{
|
||||
value: '"aria',
|
||||
add: { value: '"aria"' },
|
||||
remove: { value: '"courier"' },
|
||||
},
|
||||
// Add the rest of the text, still no closing quote
|
||||
{
|
||||
value: '"arial',
|
||||
add: { value: '"arial"' },
|
||||
remove: { value: '"courier"' },
|
||||
},
|
||||
// Restoring the original value should clear tracked changes.
|
||||
{
|
||||
value: '"courier"',
|
||||
add: null,
|
||||
remove: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
add_task(async function() {
|
||||
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
@ -54,15 +103,31 @@ add_task(async function() {
|
||||
const { document: doc, store } = selectChangesView(inspector);
|
||||
|
||||
await selectNode("div", inspector);
|
||||
const prop = getTextProperty(ruleView, 1, { color: "red" });
|
||||
|
||||
const colorProp = getTextProperty(ruleView, 1, { color: "red" });
|
||||
await assertEditValue(ruleView, doc, store, colorProp, ITERATIONS.color);
|
||||
|
||||
const fontFamilyProp = getTextProperty(ruleView, 1, {
|
||||
"font-family": '"courier"',
|
||||
});
|
||||
await assertEditValue(
|
||||
ruleView,
|
||||
doc,
|
||||
store,
|
||||
fontFamilyProp,
|
||||
ITERATIONS["font-family"]
|
||||
);
|
||||
});
|
||||
|
||||
async function assertEditValue(ruleView, doc, store, prop, iterations) {
|
||||
let onTrackChange;
|
||||
|
||||
for (const { value, add, remove } of VALUE_CHANGE_ITERATIONS) {
|
||||
for (const { value, add, needsExtraFlush, remove } of iterations) {
|
||||
onTrackChange = waitForDispatch(store, "TRACK_CHANGE");
|
||||
|
||||
info(`Change the CSS declaration value to ${value}`);
|
||||
await setProperty(ruleView, prop, value);
|
||||
await setProperty(ruleView, prop, value, {
|
||||
flushCount: needsExtraFlush ? 2 : 1,
|
||||
});
|
||||
info("Wait for the change to be tracked");
|
||||
await onTrackChange;
|
||||
|
||||
@ -102,4 +167,4 @@ add_task(async function() {
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ add_task(async function() {
|
||||
let prop = rule.textProps[0];
|
||||
|
||||
info("Clearing the property value");
|
||||
await setProperty(view, prop, null, false);
|
||||
await setProperty(view, prop, null, { blurNewProperty: false });
|
||||
|
||||
let newValue = await getRulePropertyValue(0, 0, "background-color");
|
||||
is(newValue, "", "background-color should have been unset.");
|
||||
@ -44,7 +44,7 @@ add_task(async function() {
|
||||
view.styleDocument.activeElement.blur();
|
||||
|
||||
info("Clearing the property value");
|
||||
await setProperty(view, prop, null, false);
|
||||
await setProperty(view, prop, null, { blurNewProperty: false });
|
||||
|
||||
newValue = await getRulePropertyValue(0, 0, "background-color");
|
||||
is(newValue, "", "color should have been unset.");
|
||||
|
@ -353,17 +353,23 @@ var addProperty = async function(
|
||||
* @param {String} value
|
||||
* The new value to be used. If null is passed, then the value will be
|
||||
* deleted
|
||||
* @param {Boolean} blurNewProperty
|
||||
* @param {Object} options
|
||||
* @param {Boolean} options.blurNewProperty
|
||||
* After the value has been changed, a new property would have been
|
||||
* focused. This parameter is true by default, and that causes the new
|
||||
* property to be blurred. Set to false if you don't want this.
|
||||
* @param {number} options.flushCount
|
||||
* The ruleview uses a manual flush for tests only, and some properties are
|
||||
* only updated after several flush. Allow tests to trigger several flushes
|
||||
* if necessary. Defaults to 1.
|
||||
*/
|
||||
var setProperty = async function(
|
||||
view,
|
||||
textProp,
|
||||
value,
|
||||
blurNewProperty = true
|
||||
{ blurNewProperty = true, flushCount = 1 } = {}
|
||||
) {
|
||||
info("Set property to: " + value);
|
||||
await focusEditableField(view, textProp.editor.valueSpan);
|
||||
|
||||
const onPreview = view.once("ruleview-changed");
|
||||
@ -374,14 +380,29 @@ var setProperty = async function(
|
||||
} else {
|
||||
EventUtils.sendString(value, view.styleWindow);
|
||||
}
|
||||
|
||||
info("Waiting for ruleview-changed after updating property");
|
||||
view.debounce.flush();
|
||||
flushCount--;
|
||||
|
||||
while (flushCount > 0) {
|
||||
// Wait for some time before triggering a new flush to let new debounced
|
||||
// functions queue in-between.
|
||||
await wait(100);
|
||||
view.debounce.flush();
|
||||
flushCount--;
|
||||
}
|
||||
|
||||
await onPreview;
|
||||
|
||||
const onValueDone = view.once("ruleview-changed");
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
|
||||
|
||||
info("Waiting for another ruleview-changed after setting property");
|
||||
await onValueDone;
|
||||
|
||||
if (blurNewProperty) {
|
||||
info("Force blur on the active element");
|
||||
view.styleDocument.activeElement.blur();
|
||||
}
|
||||
};
|
||||
|
@ -142,6 +142,13 @@ TableWidget.prototype = {
|
||||
editBookmark: null,
|
||||
scrollIntoViewOnUpdate: null,
|
||||
|
||||
/**
|
||||
* Return true if the table body has a scrollbar.
|
||||
*/
|
||||
get hasScrollbar() {
|
||||
return this.tbody.scrollHeight > this.tbody.clientHeight;
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter for the headers context menu popup id.
|
||||
*/
|
||||
|
@ -24,19 +24,50 @@ add_task(async function() {
|
||||
await openStoragePanel();
|
||||
|
||||
info("Run the tests with tall DevTools");
|
||||
await runTests();
|
||||
await runTests(true);
|
||||
});
|
||||
|
||||
async function runTests() {
|
||||
async function runTests(tall) {
|
||||
if (tall) {
|
||||
// We need to zoom out and a tall storage panel in order to fit more than 50
|
||||
// items in the table. We do this to ensure that we load enough content to
|
||||
// show a scrollbar so that we can still use infinite scrolling.
|
||||
zoom(0.5);
|
||||
}
|
||||
|
||||
gUI.tree.expandAll();
|
||||
await selectTreeItem(["localStorage", "https://test1.example.org"]);
|
||||
checkCellLength(ITEMS_PER_PAGE);
|
||||
|
||||
await scroll();
|
||||
checkCellLength(ITEMS_PER_PAGE * 2);
|
||||
if (tall) {
|
||||
if (getCellLength() === ITEMS_PER_PAGE) {
|
||||
await scrollToAddItems();
|
||||
await waitForStorageData("item-100", "value-100");
|
||||
}
|
||||
|
||||
await scroll();
|
||||
checkCellLength(ITEMS_PER_PAGE * 3);
|
||||
if (getCellLength() === ITEMS_PER_PAGE * 2) {
|
||||
await scrollToAddItems();
|
||||
await waitForStorageData("item-150", "value-150");
|
||||
}
|
||||
|
||||
if (getCellLength() === ITEMS_PER_PAGE * 3) {
|
||||
await scrollToAddItems();
|
||||
await waitForStorageData("item-151", "value-151");
|
||||
}
|
||||
} else {
|
||||
checkCellLength(ITEMS_PER_PAGE);
|
||||
await scrollToAddItems();
|
||||
await waitForStorageData("item-100", "value-100");
|
||||
|
||||
checkCellLength(ITEMS_PER_PAGE * 2);
|
||||
await scrollToAddItems();
|
||||
await waitForStorageData("item-150", "value-150");
|
||||
|
||||
checkCellLength(ITEMS_PER_PAGE * 3);
|
||||
await scrollToAddItems();
|
||||
await waitForStorageData("item-151", "value-151");
|
||||
}
|
||||
|
||||
is(getCellLength(), 151, "Storage table contains 151 items");
|
||||
|
||||
// Check that the columns are sorted in a human readable way (ascending).
|
||||
checkCellValues("ASC");
|
||||
@ -46,6 +77,10 @@ async function runTests() {
|
||||
|
||||
// Check that the columns are sorted in a human readable way (descending).
|
||||
checkCellValues("DEC");
|
||||
|
||||
if (tall) {
|
||||
zoom(1);
|
||||
}
|
||||
}
|
||||
|
||||
function checkCellValues(order) {
|
||||
@ -54,6 +89,16 @@ function checkCellValues(order) {
|
||||
];
|
||||
cells.forEach(function(cell, index, arr) {
|
||||
const i = order === "ASC" ? index + 1 : arr.length - index;
|
||||
is(cell.value, `item-${i}`, `Cell value is correct (${order}).`);
|
||||
is(cell.value, `item-${i}`, `Cell value is "item-${i}" (${order}).`);
|
||||
});
|
||||
}
|
||||
|
||||
async function scrollToAddItems() {
|
||||
info(`Scrolling to add ${ITEMS_PER_PAGE} items`);
|
||||
await scroll();
|
||||
}
|
||||
|
||||
function zoom(zoomValue) {
|
||||
const bc = BrowsingContext.getFromWindow(gPanelWindow);
|
||||
bc.fullZoom = zoomValue;
|
||||
}
|
||||
|
@ -1070,7 +1070,7 @@ function getCellLength() {
|
||||
}
|
||||
|
||||
function checkCellLength(len) {
|
||||
is(getCellLength(), len, `Table should initially display ${len} items`);
|
||||
is(getCellLength(), len, `Table should contain ${len} items`);
|
||||
}
|
||||
|
||||
async function scroll() {
|
||||
|
@ -11,7 +11,7 @@ Bug 1171903 - Storage Inspector endless scrolling
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
for (let i = 1; i < 151; i++) {
|
||||
for (let i = 1; i < 152; i++) {
|
||||
localStorage.setItem(`item-${i}`, `value-${i}`);
|
||||
}
|
||||
</script>
|
||||
|
@ -29,6 +29,11 @@ loader.lazyRequireGetter(
|
||||
"devtools/client/shared/widgets/TableWidget",
|
||||
true
|
||||
);
|
||||
loader.lazyImporter(
|
||||
this,
|
||||
"DeferredTask",
|
||||
"resource://gre/modules/DeferredTask.jsm"
|
||||
);
|
||||
loader.lazyImporter(
|
||||
this,
|
||||
"VariablesView",
|
||||
@ -42,6 +47,9 @@ const REASON = {
|
||||
UPDATE: "update",
|
||||
};
|
||||
|
||||
// How long we wait to debounce resize events
|
||||
const LAZY_RESIZE_INTERVAL_MS = 200;
|
||||
|
||||
// Maximum length of item name to show in context menu label - will be
|
||||
// trimmed with ellipsis if it's longer.
|
||||
const ITEM_NAME_MAX_LENGTH = 32;
|
||||
@ -143,7 +151,7 @@ class StorageUI {
|
||||
this.updateObjectSidebar = this.updateObjectSidebar.bind(this);
|
||||
this.table.on(TableWidget.EVENTS.ROW_SELECTED, this.updateObjectSidebar);
|
||||
|
||||
this.handleScrollEnd = this.handleScrollEnd.bind(this);
|
||||
this.handleScrollEnd = this.loadMoreItems.bind(this);
|
||||
this.table.on(TableWidget.EVENTS.SCROLL_END, this.handleScrollEnd);
|
||||
|
||||
this.editItem = this.editItem.bind(this);
|
||||
@ -189,6 +197,7 @@ class StorageUI {
|
||||
this.onRefreshTable = this.onRefreshTable.bind(this);
|
||||
this.onAddItem = this.onAddItem.bind(this);
|
||||
this.onCopyItem = this.onCopyItem.bind(this);
|
||||
this.onPanelWindowResize = this.#onPanelWindowResize.bind(this);
|
||||
this.onRemoveItem = this.onRemoveItem.bind(this);
|
||||
this.onRemoveAllFrom = this.onRemoveAllFrom.bind(this);
|
||||
this.onRemoveAll = this.onRemoveAll.bind(this);
|
||||
@ -201,6 +210,8 @@ class StorageUI {
|
||||
this._addButton = this._panelDoc.getElementById("add-button");
|
||||
this._addButton.addEventListener("click", this.onAddItem);
|
||||
|
||||
this._window.addEventListener("resize", this.onPanelWindowResize, true);
|
||||
|
||||
this._variableViewPopupCopy = this._panelDoc.getElementById(
|
||||
"variable-view-popup-copy"
|
||||
);
|
||||
@ -413,6 +424,11 @@ class StorageUI {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
this._destroyed = true;
|
||||
|
||||
const { resourceCommand } = this._toolbox;
|
||||
resourceCommand.unwatchResources(
|
||||
[
|
||||
@ -429,7 +445,7 @@ class StorageUI {
|
||||
);
|
||||
|
||||
this.table.off(TableWidget.EVENTS.ROW_SELECTED, this.updateObjectSidebar);
|
||||
this.table.off(TableWidget.EVENTS.SCROLL_END, this.handleScrollEnd);
|
||||
this.table.off(TableWidget.EVENTS.SCROLL_END, this.loadMoreItems);
|
||||
this.table.off(TableWidget.EVENTS.CELL_EDIT, this.editItem);
|
||||
this.table.destroy();
|
||||
|
||||
@ -443,6 +459,8 @@ class StorageUI {
|
||||
);
|
||||
this.sidebarToggleBtn = null;
|
||||
|
||||
this._window.removeEventListener("resize", this.#onPanelWindowResize, true);
|
||||
|
||||
this._treePopup.removeEventListener(
|
||||
"popupshowing",
|
||||
this.onTreePopupShowing
|
||||
@ -673,6 +691,40 @@ class StorageUI {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Debounce the window resize event by calling _onLazyPanelResize() if
|
||||
* the required amount of time has passed.
|
||||
*/
|
||||
#onPanelWindowResize() {
|
||||
if (this._toolbox.currentToolId !== "storage") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._lazyResizeHandler) {
|
||||
this._lazyResizeHandler = new DeferredTask(
|
||||
this.#onLazyPanelResize.bind(this),
|
||||
LAZY_RESIZE_INTERVAL_MS,
|
||||
0
|
||||
);
|
||||
}
|
||||
this._lazyResizeHandler.arm();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the panel is resized we need to check if we should load the next batch of
|
||||
* storage items.
|
||||
*/
|
||||
async #onLazyPanelResize() {
|
||||
// We can be called on a closed window or destroyed toolbox because of the
|
||||
// deferred task.
|
||||
if (this._window.closed || this._destroyed || this.table.hasScrollbar) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.loadMoreItems();
|
||||
this.emit("storage-resize");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string for a column name automatically choosing whether or not the
|
||||
* string should be localized.
|
||||
@ -899,9 +951,13 @@ class StorageUI {
|
||||
await this.resetColumns(type, host, subType);
|
||||
}
|
||||
|
||||
const { data } = await storage.getStoreObjects(host, names, fetchOpts);
|
||||
const { data, total } = await storage.getStoreObjects(
|
||||
host,
|
||||
names,
|
||||
fetchOpts
|
||||
);
|
||||
if (data.length) {
|
||||
await this.populateTable(data, reason);
|
||||
await this.populateTable(data, reason, total);
|
||||
} else if (reason === REASON.POPULATE) {
|
||||
await this.clearHeaders();
|
||||
}
|
||||
@ -1235,8 +1291,9 @@ class StorageUI {
|
||||
if (item.length > 2) {
|
||||
names = [JSON.stringify(item.slice(2))];
|
||||
}
|
||||
await this.fetchStorageObjects(type, host, names, REASON.POPULATE);
|
||||
|
||||
this.itemOffset = 0;
|
||||
await this.fetchStorageObjects(type, host, names, REASON.POPULATE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1312,8 +1369,10 @@ class StorageUI {
|
||||
* Array of objects to be populated in the storage table
|
||||
* @param {Constant} reason
|
||||
* See REASON constant at top of file.
|
||||
* @param {number} totalAvailable
|
||||
* The total number of items available in the current storage type.
|
||||
*/
|
||||
async populateTable(data, reason) {
|
||||
async populateTable(data, reason, totalAvailable) {
|
||||
for (const item of data) {
|
||||
if (item.value) {
|
||||
item.valueActor = item.value;
|
||||
@ -1351,6 +1410,14 @@ class StorageUI {
|
||||
|
||||
this.shouldLoadMoreItems = true;
|
||||
}
|
||||
|
||||
if (
|
||||
(reason === REASON.POPULATE || reason === REASON.NEXT_50_ITEMS) &&
|
||||
this.table.items.size < totalAvailable &&
|
||||
!this.table.hasScrollbar
|
||||
) {
|
||||
await this.loadMoreItems();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1390,9 +1457,9 @@ class StorageUI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles endless scrolling for the table
|
||||
* Load the next batch of 50 items
|
||||
*/
|
||||
async handleScrollEnd() {
|
||||
async loadMoreItems() {
|
||||
if (!this.shouldLoadMoreItems) {
|
||||
return;
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ const protocol = require("devtools/shared/protocol");
|
||||
const { getCSSLexer } = require("devtools/shared/css/lexer");
|
||||
const InspectorUtils = require("InspectorUtils");
|
||||
const TrackChangeEmitter = require("devtools/server/actors/utils/track-change-emitter");
|
||||
|
||||
const {
|
||||
getRuleText,
|
||||
getTextAtLineColumn,
|
||||
@ -93,6 +92,8 @@ const StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
|
||||
// this.form().declarations on demand because that would cause needless re-parsing.
|
||||
this._declarations = [];
|
||||
|
||||
this._pendingDeclarationChanges = [];
|
||||
|
||||
if (CSSRule.isInstance(item)) {
|
||||
this.type = item.type;
|
||||
this.rawRule = item;
|
||||
@ -459,6 +460,16 @@ const StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
|
||||
return decl;
|
||||
});
|
||||
|
||||
// We have computed the new `declarations` array, before forgetting about
|
||||
// the old declarations compute the CSS changes for pending modifications
|
||||
// applied by the user. Comparing the old and new declarations arrays
|
||||
// ensures we only rely on values understood by the engine and not authored
|
||||
// values. See Bug 1590031.
|
||||
this._pendingDeclarationChanges.forEach(change =>
|
||||
this.logDeclarationChange(change, declarations, this._declarations)
|
||||
);
|
||||
this._pendingDeclarationChanges = [];
|
||||
|
||||
// Cache parsed declarations so we don't needlessly re-parse authoredText every time
|
||||
// we need to check previous property names and values when tracking changes.
|
||||
this._declarations = declarations;
|
||||
@ -711,9 +722,6 @@ const StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
|
||||
throw new Error("invalid call to setRuleText");
|
||||
}
|
||||
|
||||
// Log the changes before applying them so we have access to the previous values.
|
||||
modifications.map(mod => this.logDeclarationChange(mod));
|
||||
|
||||
if (this.type === ELEMENT_STYLE) {
|
||||
// For element style rules, set the node's style attribute.
|
||||
this.rawNode.setAttributeDevtools("style", newText);
|
||||
@ -751,6 +759,11 @@ const StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
|
||||
this.authoredText = newText;
|
||||
this.pageStyle.refreshObservedRules();
|
||||
|
||||
// Add processed modifications to the _pendingDeclarationChanges array,
|
||||
// they will be emitted as CSS_CHANGE resources once `declarations` have
|
||||
// been re-computed in `form`.
|
||||
this._pendingDeclarationChanges.push(...modifications);
|
||||
|
||||
// Returning this updated actor over the protocol will update its corresponding front
|
||||
// and any references to it.
|
||||
return this;
|
||||
@ -794,7 +807,6 @@ const StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
|
||||
const tempElement = document.createElementNS(XHTML_NS, "div");
|
||||
|
||||
for (const mod of modifications) {
|
||||
this.logDeclarationChange(mod);
|
||||
if (mod.type === "set") {
|
||||
tempElement.style.setProperty(mod.name, mod.value, mod.priority || "");
|
||||
this.rawStyle.setProperty(
|
||||
@ -809,6 +821,11 @@ const StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
|
||||
|
||||
this.pageStyle.refreshObservedRules();
|
||||
|
||||
// Add processed modifications to the _pendingDeclarationChanges array,
|
||||
// they will be emitted as CSS_CHANGE resources once `declarations` have
|
||||
// been re-computed in `form`.
|
||||
this._pendingDeclarationChanges.push(...modifications);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
@ -912,8 +929,12 @@ const StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
|
||||
*
|
||||
* @param {Object} change
|
||||
* Data about a modification to a declaration. @see |modifyProperties()|
|
||||
* @param {Object} newDeclarations
|
||||
* The current declarations array to get the latest values, names...
|
||||
* @param {Object} oldDeclarations
|
||||
* The previous declarations array to use to fetch old values, names...
|
||||
*/
|
||||
logDeclarationChange(change) {
|
||||
logDeclarationChange(change, newDeclarations, oldDeclarations) {
|
||||
// Position of the declaration within its rule.
|
||||
const index = change.index;
|
||||
// Destructure properties from the previous CSS declaration at this index, if any,
|
||||
@ -923,7 +944,9 @@ const StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
|
||||
name: prevName,
|
||||
priority: prevPriority,
|
||||
commentOffsets,
|
||||
} = this._declarations[index] || {};
|
||||
} = oldDeclarations[index] || {};
|
||||
|
||||
const { value: currentValue } = newDeclarations[index] || {};
|
||||
// A declaration is disabled if it has a `commentOffsets` array.
|
||||
// Here we type coerce the value to a boolean with double-bang (!!)
|
||||
const prevDisabled = !!commentOffsets;
|
||||
@ -941,9 +964,12 @@ const StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
|
||||
// declaration is being updated. In that case, use the provided `change.name`.
|
||||
const name = change.newName ? change.newName : change.name;
|
||||
// Append the "!important" string if defined in the incoming priority flag.
|
||||
|
||||
const changeValue = currentValue || change.value;
|
||||
const newValue = change.priority
|
||||
? `${change.value} !important`
|
||||
: change.value;
|
||||
? `${changeValue} !important`
|
||||
: changeValue;
|
||||
|
||||
// Reuse the previous value string, when the property is renamed.
|
||||
// Otherwise, use the incoming value string.
|
||||
const value = change.newName ? prevValue : newValue;
|
||||
|
@ -66,30 +66,18 @@ async function addTestEngines() {
|
||||
// WebExtensions need is only defined for browser/
|
||||
await Services.search.addPolicyEngine({
|
||||
description: "urifixup search engine",
|
||||
chrome_settings_overrides: {
|
||||
search_provider: {
|
||||
name: kSearchEngineID,
|
||||
search_url: kSearchEngineURL,
|
||||
},
|
||||
},
|
||||
name: kSearchEngineID,
|
||||
search_url: kSearchEngineURL,
|
||||
});
|
||||
await Services.search.addPolicyEngine({
|
||||
description: "urifixup private search engine",
|
||||
chrome_settings_overrides: {
|
||||
search_provider: {
|
||||
name: kPrivateSearchEngineID,
|
||||
search_url: kPrivateSearchEngineURL,
|
||||
},
|
||||
},
|
||||
name: kPrivateSearchEngineID,
|
||||
search_url: kPrivateSearchEngineURL,
|
||||
});
|
||||
await Services.search.addPolicyEngine({
|
||||
description: "urifixup POST search engine",
|
||||
chrome_settings_overrides: {
|
||||
search_provider: {
|
||||
name: kPostSearchEngineID,
|
||||
search_url: kPostSearchEngineURL,
|
||||
search_url_post_params: kPostSearchEngineData,
|
||||
},
|
||||
},
|
||||
name: kPostSearchEngineID,
|
||||
search_url: kPostSearchEngineURL,
|
||||
search_url_post_params: kPostSearchEngineData,
|
||||
});
|
||||
}
|
||||
|
@ -14778,8 +14778,7 @@ void Document::GetWireframeWithoutFlushing(bool aIncludeNodes,
|
||||
}
|
||||
bool drawImage = false;
|
||||
bool drawColor = false;
|
||||
ComputedStyle* bgStyle = nullptr;
|
||||
if (nsCSSRendering::FindBackground(frame, &bgStyle)) {
|
||||
if (const auto* bgStyle = nsCSSRendering::FindBackground(frame)) {
|
||||
const nscolor color = nsCSSRendering::DetermineBackgroundColor(
|
||||
pc, bgStyle, frame, drawImage, drawColor);
|
||||
if (drawImage &&
|
||||
|
@ -183,6 +183,8 @@
|
||||
#include "mozilla/dom/XRPermissionRequest.h"
|
||||
#include "mozilla/dom/cache/CacheStorage.h"
|
||||
#include "mozilla/dom/cache/Types.h"
|
||||
#include "mozilla/glean/bindings/Glean.h"
|
||||
#include "mozilla/glean/bindings/GleanPings.h"
|
||||
#include "mozilla/extensions/WebExtensionPolicy.h"
|
||||
#include "mozilla/fallible.h"
|
||||
#include "mozilla/gfx/BasePoint.h"
|
||||
|
@ -44,8 +44,6 @@
|
||||
#include "mozilla/dom/EventTarget.h"
|
||||
#include "mozilla/dom/WindowBinding.h"
|
||||
#include "mozilla/dom/WindowProxyHolder.h"
|
||||
#include "mozilla/glean/bindings/Glean.h"
|
||||
#include "mozilla/glean/bindings/GleanPings.h"
|
||||
#include "Units.h"
|
||||
#include "nsCheapSets.h"
|
||||
#include "mozilla/dom/ImageBitmapBinding.h"
|
||||
@ -91,6 +89,11 @@ namespace mozilla {
|
||||
class AbstractThread;
|
||||
class ErrorResult;
|
||||
|
||||
namespace glean {
|
||||
class Glean;
|
||||
class GleanPings;
|
||||
} // namespace glean
|
||||
|
||||
namespace hal {
|
||||
enum class ScreenOrientation : uint32_t;
|
||||
}
|
||||
|
@ -3139,7 +3139,7 @@ void BrowserChild::ReinitRendering() {
|
||||
|
||||
// In some cases, like when we create a windowless browser,
|
||||
// RemoteLayerTreeOwner/BrowserChild is not connected to a compositor.
|
||||
if (mLayersConnected.isNothing() || !*mLayersConnected) {
|
||||
if (mLayersConnected.isNothing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -138,6 +138,7 @@ PerformanceTimingData::PerformanceTimingData(nsITimedChannel* aChannel,
|
||||
if (aChannel) {
|
||||
aChannel->GetAsyncOpen(&mAsyncOpen);
|
||||
aChannel->GetAllRedirectsSameOrigin(&mAllRedirectsSameOrigin);
|
||||
aChannel->GetAllRedirectsPassTimingAllowCheck(&mAllRedirectsPassTAO);
|
||||
aChannel->GetRedirectCount(&mRedirectCount);
|
||||
aChannel->GetRedirectStart(&mRedirectStart);
|
||||
aChannel->GetRedirectEnd(&mRedirectEnd);
|
||||
|
@ -8,6 +8,12 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
const { PushRecord } = ChromeUtils.import(
|
||||
"resource://gre/modules/PushRecord.jsm"
|
||||
);
|
||||
const { Preferences } = ChromeUtils.import(
|
||||
"resource://gre/modules/Preferences.jsm"
|
||||
);
|
||||
|
||||
const lazy = {};
|
||||
|
||||
@ -16,11 +22,6 @@ ChromeUtils.defineModuleGetter(
|
||||
"PushDB",
|
||||
"resource://gre/modules/PushDB.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
lazy,
|
||||
"PushRecord",
|
||||
"resource://gre/modules/PushRecord.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
lazy,
|
||||
"PushCrypto",
|
||||
@ -31,11 +32,6 @@ ChromeUtils.defineModuleGetter(
|
||||
"EventDispatcher",
|
||||
"resource://gre/modules/Messaging.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
lazy,
|
||||
"Preferences",
|
||||
"resource://gre/modules/Preferences.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(lazy, "Log", () => {
|
||||
return ChromeUtils.import(
|
||||
@ -60,7 +56,7 @@ const kPUSHANDROIDGCMDB_STORE_NAME = "pushAndroidGCM";
|
||||
|
||||
const FXA_PUSH_SCOPE = "chrome://fxa-push";
|
||||
|
||||
const prefs = new lazy.Preferences("dom.push.");
|
||||
const prefs = new Preferences("dom.push.");
|
||||
|
||||
/**
|
||||
* The implementation of WebPush push backed by Android's GCM
|
||||
@ -311,11 +307,11 @@ var PushServiceAndroidGCM = {
|
||||
};
|
||||
|
||||
function PushRecordAndroidGCM(record) {
|
||||
lazy.PushRecord.call(this, record);
|
||||
PushRecord.call(this, record);
|
||||
this.channelID = record.channelID;
|
||||
}
|
||||
|
||||
PushRecordAndroidGCM.prototype = Object.create(lazy.PushRecord.prototype, {
|
||||
PushRecordAndroidGCM.prototype = Object.create(PushRecord.prototype, {
|
||||
keyID: {
|
||||
get() {
|
||||
return this.channelID;
|
||||
|
@ -1562,6 +1562,46 @@ nsresult nsContentSecurityManager::CheckChannel(nsIChannel* aChannel) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#serializing-a-request-origin
|
||||
void nsContentSecurityManager::GetSerializedOrigin(
|
||||
nsIPrincipal* aOrigin, nsIPrincipal* aResourceOrigin,
|
||||
nsACString& aSerializedOrigin, nsILoadInfo* aLoadInfo) {
|
||||
// The following for loop performs the
|
||||
// https://fetch.spec.whatwg.org/#ref-for-concept-request-tainted-origin
|
||||
nsCOMPtr<nsIPrincipal> lastOrigin;
|
||||
for (nsIRedirectHistoryEntry* entry : aLoadInfo->RedirectChain()) {
|
||||
if (!lastOrigin) {
|
||||
entry->GetPrincipal(getter_AddRefs(lastOrigin));
|
||||
continue;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> currentOrigin;
|
||||
entry->GetPrincipal(getter_AddRefs(currentOrigin));
|
||||
|
||||
if (!currentOrigin->Equals(lastOrigin) && !lastOrigin->Equals(aOrigin)) {
|
||||
return;
|
||||
}
|
||||
lastOrigin = currentOrigin;
|
||||
}
|
||||
|
||||
// When the redirectChain is empty, it means this is the first redirect.
|
||||
// So according to the #serializing-a-request-origin spec, we don't
|
||||
// have a redirect-tainted origin, so we return the origin of the request
|
||||
// here.
|
||||
if (!lastOrigin) {
|
||||
aOrigin->GetAsciiOrigin(aSerializedOrigin);
|
||||
return;
|
||||
}
|
||||
|
||||
// Same as above, redirectChain doesn't contain the current redirect,
|
||||
// so we have to do the check one last time here.
|
||||
if (lastOrigin->Equals(aResourceOrigin) && !lastOrigin->Equals(aOrigin)) {
|
||||
return;
|
||||
}
|
||||
|
||||
aOrigin->GetAsciiOrigin(aSerializedOrigin);
|
||||
}
|
||||
|
||||
// ==== nsIContentSecurityManager implementation =====
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -71,6 +71,10 @@ class nsContentSecurityManager : public nsIContentSecurityManager,
|
||||
static nsSecurityFlags ComputeSecurityFlags(
|
||||
mozilla::CORSMode aCORSMode, CORSSecurityMapping aCORSSecurityMapping);
|
||||
|
||||
static void GetSerializedOrigin(nsIPrincipal* aOrigin,
|
||||
nsIPrincipal* aResourceOrigin,
|
||||
nsACString& aResult, nsILoadInfo* aLoadInfo);
|
||||
|
||||
private:
|
||||
static nsresult CheckChannel(nsIChannel* aChannel);
|
||||
static nsresult CheckFTPSubresourceLoad(nsIChannel* aChannel);
|
||||
|
@ -747,6 +747,56 @@ static EditorDOMPoint GetPointAfterFollowingLineBreakOrAtFollowingBlock(
|
||||
return point;
|
||||
}
|
||||
|
||||
void AutoRangeArray::ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
|
||||
EditSubAction aEditSubAction, const Element& aEditingHost) {
|
||||
// FYI: This is originated in
|
||||
// https://searchfox.org/mozilla-central/rev/1739f1301d658c9bff544a0a095ab11fca2e549d/editor/libeditor/HTMLEditSubActionHandler.cpp#6712
|
||||
|
||||
bool removeSomeRanges = false;
|
||||
for (OwningNonNull<nsRange>& range : mRanges) {
|
||||
// Remove non-positioned ranges.
|
||||
if (MOZ_UNLIKELY(!range->IsPositioned())) {
|
||||
removeSomeRanges = true;
|
||||
continue;
|
||||
}
|
||||
// If the range is native anonymous subtrees, we must meet a bug of
|
||||
// `Selection` so that we need to hack here.
|
||||
if (MOZ_UNLIKELY(range->GetStartContainer()->IsInNativeAnonymousSubtree() ||
|
||||
range->GetEndContainer()->IsInNativeAnonymousSubtree())) {
|
||||
EditorRawDOMRange rawRange(range);
|
||||
if (!rawRange.EnsureNotInNativeAnonymousSubtree()) {
|
||||
range->Reset();
|
||||
removeSomeRanges = true;
|
||||
continue;
|
||||
}
|
||||
if (NS_FAILED(
|
||||
range->SetStartAndEnd(rawRange.StartRef().ToRawRangeBoundary(),
|
||||
rawRange.EndRef().ToRawRangeBoundary())) ||
|
||||
MOZ_UNLIKELY(!range->IsPositioned())) {
|
||||
range->Reset();
|
||||
removeSomeRanges = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Finally, extend the range.
|
||||
if (NS_FAILED(ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
|
||||
range, aEditSubAction, aEditingHost))) {
|
||||
// If we failed to extend the range, we should use the original range
|
||||
// as-is unless the range is broken at setting the range.
|
||||
if (NS_WARN_IF(!range->IsPositioned())) {
|
||||
removeSomeRanges = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removeSomeRanges) {
|
||||
for (size_t i : Reversed(IntegerRange(mRanges.Length()))) {
|
||||
if (!mRanges[i]->IsPositioned()) {
|
||||
mRanges.RemoveElementAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
nsresult AutoRangeArray::ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
|
||||
nsRange& aRange, EditSubAction aEditSubAction,
|
||||
|
@ -345,6 +345,13 @@ class MOZ_STACK_CLASS AutoRangeArray final {
|
||||
*/
|
||||
void EnsureRangesInTextNode(const dom::Text& aTextNode);
|
||||
|
||||
/**
|
||||
* Extend ranges to wrap lines to handle block level edit actions such as
|
||||
* updating the block parent or indent/outdent around the selection.
|
||||
*/
|
||||
void ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
|
||||
EditSubAction aEditSubAction, const dom::Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* Check whether the range is in aEditingHost and both containers of start and
|
||||
* end boundaries of the range are editable.
|
||||
|
@ -57,8 +57,13 @@ nsresult HTMLEditor::SetSelectionToAbsoluteOrStaticAsAction(
|
||||
return rv;
|
||||
}
|
||||
|
||||
const RefPtr<Element> editingHost = ComputeEditingHost();
|
||||
if (!editingHost) {
|
||||
return NS_SUCCESS_DOM_NO_OPERATION;
|
||||
}
|
||||
|
||||
if (aEnabled) {
|
||||
EditActionResult result = SetSelectionToAbsoluteAsSubAction();
|
||||
EditActionResult result = SetSelectionToAbsoluteAsSubAction(*editingHost);
|
||||
NS_WARNING_ASSERTION(
|
||||
result.Succeeded(),
|
||||
"HTMLEditor::SetSelectionToAbsoluteAsSubAction() failed");
|
||||
|
@ -1481,7 +1481,8 @@ nsresult HTMLEditor::InsertLineBreakAsSubAction() {
|
||||
return rv;
|
||||
}
|
||||
|
||||
EditActionResult HTMLEditor::InsertParagraphSeparatorAsSubAction() {
|
||||
EditActionResult HTMLEditor::InsertParagraphSeparatorAsSubAction(
|
||||
const Element& aEditingHost) {
|
||||
if (NS_WARN_IF(!mInitSucceeded)) {
|
||||
return EditActionIgnored(NS_ERROR_NOT_INITIALIZED);
|
||||
}
|
||||
@ -1550,11 +1551,6 @@ EditActionResult HTMLEditor::InsertParagraphSeparatorAsSubAction() {
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<Element> editingHost = ComputeEditingHost();
|
||||
if (NS_WARN_IF(!editingHost)) {
|
||||
return EditActionIgnored(NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
if (IsMailEditor()) {
|
||||
const auto pointToSplit = GetFirstSelectionStartPoint<EditorDOMPoint>();
|
||||
if (NS_WARN_IF(!pointToSplit.IsInContentNode())) {
|
||||
@ -1567,7 +1563,7 @@ EditActionResult HTMLEditor::InsertParagraphSeparatorAsSubAction() {
|
||||
// table cell boundaries?
|
||||
Result<EditorDOMPoint, nsresult> atNewBRElementOrError =
|
||||
HandleInsertParagraphInMailCiteElement(*mailCiteElement, pointToSplit,
|
||||
*editingHost);
|
||||
aEditingHost);
|
||||
if (MOZ_UNLIKELY(atNewBRElementOrError.isErr())) {
|
||||
NS_WARNING(
|
||||
"HTMLEditor::HandleInsertParagraphInMailCiteElement() failed");
|
||||
@ -1612,11 +1608,11 @@ EditActionResult HTMLEditor::InsertParagraphSeparatorAsSubAction() {
|
||||
// insert new paragraph nor <br> element.
|
||||
// XXX Currently, we don't support editing outside <body> element, but Blink
|
||||
// does it.
|
||||
if (editingHost->GetParentElement() &&
|
||||
HTMLEditUtils::IsSimplyEditableNode(*editingHost->GetParentElement()) &&
|
||||
if (aEditingHost.GetParentElement() &&
|
||||
HTMLEditUtils::IsSimplyEditableNode(*aEditingHost.GetParentElement()) &&
|
||||
(!atStartOfSelection.IsInContentNode() ||
|
||||
!nsContentUtils::ContentIsFlattenedTreeDescendantOf(
|
||||
atStartOfSelection.ContainerAsContent(), editingHost))) {
|
||||
atStartOfSelection.ContainerAsContent(), &aEditingHost))) {
|
||||
return EditActionHandled(NS_ERROR_EDITOR_NO_EDITABLE_RANGE);
|
||||
}
|
||||
|
||||
@ -1644,9 +1640,9 @@ EditActionResult HTMLEditor::InsertParagraphSeparatorAsSubAction() {
|
||||
else if (!HTMLEditUtils::IsSplittableNode(*editableBlockElement)) {
|
||||
insertLineBreak =
|
||||
separator == ParagraphSeparator::br ||
|
||||
!HTMLEditUtils::CanElementContainParagraph(*editingHost) ||
|
||||
!HTMLEditUtils::CanElementContainParagraph(aEditingHost) ||
|
||||
HTMLEditUtils::ShouldInsertLinefeedCharacter(atStartOfSelection,
|
||||
*editingHost);
|
||||
aEditingHost);
|
||||
}
|
||||
// If the nearest block parent is a single-line container declared in
|
||||
// the execCommand spec and not the editing host, we should separate the
|
||||
@ -1675,8 +1671,8 @@ EditActionResult HTMLEditor::InsertParagraphSeparatorAsSubAction() {
|
||||
// paragraph separator is set to "br" which is Gecko-specific mode.
|
||||
if (separator != ParagraphSeparator::br &&
|
||||
HTMLEditUtils::ShouldInsertLinefeedCharacter(atStartOfSelection,
|
||||
*editingHost)) {
|
||||
nsresult rv = HandleInsertLinefeed(atStartOfSelection, *editingHost);
|
||||
aEditingHost)) {
|
||||
nsresult rv = HandleInsertLinefeed(atStartOfSelection, aEditingHost);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("HTMLEditor::HandleInsertLinefeed() failed");
|
||||
return EditActionResult(rv);
|
||||
@ -1685,7 +1681,7 @@ EditActionResult HTMLEditor::InsertParagraphSeparatorAsSubAction() {
|
||||
}
|
||||
|
||||
CreateElementResult insertBRElementResult =
|
||||
HandleInsertBRElement(atStartOfSelection, *editingHost);
|
||||
HandleInsertBRElement(atStartOfSelection, aEditingHost);
|
||||
if (insertBRElementResult.isErr()) {
|
||||
NS_WARNING("HTMLEditor::HandleInsertBRElement() failed");
|
||||
return EditActionHandled(insertBRElementResult.unwrapErr());
|
||||
@ -1707,7 +1703,8 @@ EditActionResult HTMLEditor::InsertParagraphSeparatorAsSubAction() {
|
||||
// Therefore, even if it returns NS_OK, editor might have been destroyed
|
||||
// at restoring Selection.
|
||||
nsresult rv = FormatBlockContainerWithTransaction(
|
||||
MOZ_KnownLive(HTMLEditor::ToParagraphSeparatorTagName(separator)));
|
||||
MOZ_KnownLive(HTMLEditor::ToParagraphSeparatorTagName(separator)),
|
||||
aEditingHost);
|
||||
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED) ||
|
||||
NS_WARN_IF(Destroyed())) {
|
||||
return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
|
||||
@ -1738,7 +1735,7 @@ EditActionResult HTMLEditor::InsertParagraphSeparatorAsSubAction() {
|
||||
if (NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(*editableBlockElement))) {
|
||||
// Didn't create a new block for some reason, fall back to <br>
|
||||
CreateElementResult insertBRElementResult =
|
||||
HandleInsertBRElement(atStartOfSelection, *editingHost);
|
||||
HandleInsertBRElement(atStartOfSelection, aEditingHost);
|
||||
if (insertBRElementResult.isErr()) {
|
||||
NS_WARNING("HTMLEditor::HandleInsertBRElement() failed");
|
||||
return EditActionResult(insertBRElementResult.unwrapErr());
|
||||
@ -1793,12 +1790,12 @@ EditActionResult HTMLEditor::InsertParagraphSeparatorAsSubAction() {
|
||||
|
||||
RefPtr<Element> maybeNonEditableListItem =
|
||||
HTMLEditUtils::GetClosestAncestorListItemElement(*editableBlockElement,
|
||||
editingHost);
|
||||
&aEditingHost);
|
||||
if (maybeNonEditableListItem &&
|
||||
HTMLEditUtils::IsSplittableNode(*maybeNonEditableListItem)) {
|
||||
Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
|
||||
HandleInsertParagraphInListItemElement(
|
||||
*maybeNonEditableListItem, atStartOfSelection, *editingHost);
|
||||
*maybeNonEditableListItem, atStartOfSelection, aEditingHost);
|
||||
if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
|
||||
if (NS_WARN_IF(pointToPutCaretOrError.unwrapErr() ==
|
||||
NS_ERROR_EDITOR_DESTROYED)) {
|
||||
@ -1884,7 +1881,7 @@ EditActionResult HTMLEditor::InsertParagraphSeparatorAsSubAction() {
|
||||
|
||||
// If nobody handles this edit action, let's insert new <br> at the selection.
|
||||
CreateElementResult insertBRElementResult =
|
||||
HandleInsertBRElement(atStartOfSelection, *editingHost);
|
||||
HandleInsertBRElement(atStartOfSelection, aEditingHost);
|
||||
if (insertBRElementResult.isErr()) {
|
||||
NS_WARNING("HTMLEditor::HandleInsertBRElement() failed");
|
||||
return EditActionIgnored(insertBRElementResult.unwrapErr());
|
||||
@ -1898,7 +1895,7 @@ EditActionResult HTMLEditor::InsertParagraphSeparatorAsSubAction() {
|
||||
}
|
||||
|
||||
CreateElementResult HTMLEditor::HandleInsertBRElement(
|
||||
const EditorDOMPoint& aPointToBreak, Element& aEditingHost) {
|
||||
const EditorDOMPoint& aPointToBreak, const Element& aEditingHost) {
|
||||
MOZ_ASSERT(aPointToBreak.IsSet());
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
|
||||
@ -2052,7 +2049,7 @@ CreateElementResult HTMLEditor::HandleInsertBRElement(
|
||||
}
|
||||
|
||||
nsresult HTMLEditor::HandleInsertLinefeed(const EditorDOMPoint& aPointToBreak,
|
||||
Element& aEditingHost) {
|
||||
const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
|
||||
if (NS_WARN_IF(!aPointToBreak.IsSet())) {
|
||||
@ -2194,7 +2191,7 @@ nsresult HTMLEditor::HandleInsertLinefeed(const EditorDOMPoint& aPointToBreak,
|
||||
Result<EditorDOMPoint, nsresult>
|
||||
HTMLEditor::HandleInsertParagraphInMailCiteElement(
|
||||
Element& aMailCiteElement, const EditorDOMPoint& aPointToSplit,
|
||||
Element& aEditingHost) {
|
||||
const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
MOZ_ASSERT(aPointToSplit.IsSet());
|
||||
NS_ASSERTION(!HTMLEditUtils::IsEmptyNode(aMailCiteElement),
|
||||
@ -3037,9 +3034,13 @@ EditActionResult HTMLEditor::MakeOrChangeListAndListItemAsSubAction(
|
||||
// ChangeSelectedHardLinesToList() creates AutoSelectionRestorer.
|
||||
// Therefore, even if it returns NS_OK, editor might have been destroyed
|
||||
// at restoring Selection.
|
||||
result = ChangeSelectedHardLinesToList(MOZ_KnownLive(*listTagName),
|
||||
MOZ_KnownLive(*listItemTagName),
|
||||
aBulletType, aSelectAllOfCurrentList);
|
||||
const RefPtr<Element> editingHost = ComputeEditingHost();
|
||||
if (MOZ_UNLIKELY(!editingHost)) {
|
||||
return EditActionIgnored(NS_SUCCESS_DOM_NO_OPERATION);
|
||||
}
|
||||
result = ChangeSelectedHardLinesToList(
|
||||
MOZ_KnownLive(*listTagName), MOZ_KnownLive(*listItemTagName), aBulletType,
|
||||
aSelectAllOfCurrentList, *editingHost);
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
|
||||
}
|
||||
@ -3051,15 +3052,11 @@ EditActionResult HTMLEditor::MakeOrChangeListAndListItemAsSubAction(
|
||||
EditActionResult HTMLEditor::ChangeSelectedHardLinesToList(
|
||||
nsAtom& aListElementTagName, nsAtom& aListItemElementTagName,
|
||||
const nsAString& aBulletType,
|
||||
SelectAllOfCurrentList aSelectAllOfCurrentList) {
|
||||
SelectAllOfCurrentList aSelectAllOfCurrentList,
|
||||
const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
|
||||
MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
|
||||
|
||||
RefPtr<Element> editingHost = ComputeEditingHost();
|
||||
if (MOZ_UNLIKELY(NS_WARN_IF(!editingHost))) {
|
||||
return EditActionResult(NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
AutoSelectionRestorer restoreSelectionLater(*this);
|
||||
|
||||
AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
|
||||
@ -3073,11 +3070,11 @@ EditActionResult HTMLEditor::ChangeSelectedHardLinesToList(
|
||||
} else {
|
||||
AutoTransactionsConserveSelection dontChangeMySelection(*this);
|
||||
|
||||
AutoTArray<OwningNonNull<nsRange>, 4> extendedSelectionRanges;
|
||||
GetSelectionRangesExtendedToHardLineStartAndEnd(
|
||||
extendedSelectionRanges, EditSubAction::eCreateOrChangeList);
|
||||
AutoRangeArray extendedSelectionRanges(SelectionRef());
|
||||
extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
|
||||
EditSubAction::eCreateOrChangeList, aEditingHost);
|
||||
nsresult rv = SplitInlinesAndCollectEditTargetNodes(
|
||||
extendedSelectionRanges, arrayOfContents,
|
||||
extendedSelectionRanges.Ranges(), arrayOfContents,
|
||||
EditSubAction::eCreateOrChangeList, CollectNonEditableNodes::No);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING(
|
||||
@ -3134,7 +3131,7 @@ EditActionResult HTMLEditor::ChangeSelectedHardLinesToList(
|
||||
CreateElementResult createNewListElementResult =
|
||||
InsertElementWithSplittingAncestorsWithTransaction(
|
||||
aListElementTagName, atStartOfSelection,
|
||||
BRElementNextToSplitPoint::Keep, *editingHost,
|
||||
BRElementNextToSplitPoint::Keep, aEditingHost,
|
||||
// MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
|
||||
[&newListItemElement, &aListItemElementTagName](
|
||||
HTMLEditor& aHTMLEditor, Element& aListElement,
|
||||
@ -3531,7 +3528,7 @@ EditActionResult HTMLEditor::ChangeSelectedHardLinesToList(
|
||||
CreateElementResult createNewListElementResult =
|
||||
InsertElementWithSplittingAncestorsWithTransaction(
|
||||
aListElementTagName, atContent, BRElementNextToSplitPoint::Keep,
|
||||
*editingHost);
|
||||
aEditingHost);
|
||||
if (createNewListElementResult.isErr()) {
|
||||
NS_WARNING(
|
||||
nsPrintfCString(
|
||||
@ -3661,7 +3658,8 @@ EditActionResult HTMLEditor::ChangeSelectedHardLinesToList(
|
||||
return EditActionHandled();
|
||||
}
|
||||
|
||||
nsresult HTMLEditor::RemoveListAtSelectionAsSubAction() {
|
||||
nsresult HTMLEditor::RemoveListAtSelectionAsSubAction(
|
||||
const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
|
||||
EditActionResult result = CanHandleHTMLEditSubAction();
|
||||
@ -3699,11 +3697,11 @@ nsresult HTMLEditor::RemoveListAtSelectionAsSubAction() {
|
||||
{
|
||||
AutoTransactionsConserveSelection dontChangeMySelection(*this);
|
||||
|
||||
AutoTArray<OwningNonNull<nsRange>, 4> extendedSelectionRanges;
|
||||
GetSelectionRangesExtendedToHardLineStartAndEnd(
|
||||
extendedSelectionRanges, EditSubAction::eCreateOrChangeList);
|
||||
AutoRangeArray extendedSelectionRanges(SelectionRef());
|
||||
extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
|
||||
EditSubAction::eCreateOrChangeList, aEditingHost);
|
||||
nsresult rv = SplitInlinesAndCollectEditTargetNodes(
|
||||
extendedSelectionRanges, arrayOfContents,
|
||||
extendedSelectionRanges.Ranges(), arrayOfContents,
|
||||
EditSubAction::eCreateOrChangeList, CollectNonEditableNodes::No);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING(
|
||||
@ -3752,14 +3750,10 @@ nsresult HTMLEditor::RemoveListAtSelectionAsSubAction() {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult HTMLEditor::FormatBlockContainerWithTransaction(nsAtom& blockType) {
|
||||
nsresult HTMLEditor::FormatBlockContainerWithTransaction(
|
||||
nsAtom& blockType, const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
|
||||
|
||||
const RefPtr<Element> editingHost = ComputeEditingHost();
|
||||
if (MOZ_UNLIKELY(NS_WARN_IF(!editingHost))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (!SelectionRef().IsCollapsed()) {
|
||||
nsresult rv = MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
|
||||
if (NS_FAILED(rv)) {
|
||||
@ -3775,11 +3769,11 @@ nsresult HTMLEditor::FormatBlockContainerWithTransaction(nsAtom& blockType) {
|
||||
|
||||
AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
|
||||
{
|
||||
AutoTArray<OwningNonNull<nsRange>, 4> extendedSelectionRanges;
|
||||
GetSelectionRangesExtendedToHardLineStartAndEnd(
|
||||
extendedSelectionRanges, EditSubAction::eCreateOrRemoveBlock);
|
||||
AutoRangeArray extendedSelectionRanges(SelectionRef());
|
||||
extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
|
||||
EditSubAction::eCreateOrRemoveBlock, aEditingHost);
|
||||
nsresult rv = SplitInlinesAndCollectEditTargetNodes(
|
||||
extendedSelectionRanges, arrayOfContents,
|
||||
extendedSelectionRanges.Ranges(), arrayOfContents,
|
||||
EditSubAction::eCreateOrRemoveBlock, CollectNonEditableNodes::Yes);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING(
|
||||
@ -3826,7 +3820,7 @@ nsresult HTMLEditor::FormatBlockContainerWithTransaction(nsAtom& blockType) {
|
||||
// which is visually bad.
|
||||
if (nsCOMPtr<nsIContent> brContent = HTMLEditUtils::GetNextContent(
|
||||
pointToInsertBlock, {WalkTreeOption::IgnoreNonEditableNode},
|
||||
editingHost)) {
|
||||
&aEditingHost)) {
|
||||
if (brContent && brContent->IsHTMLElement(nsGkAtoms::br)) {
|
||||
AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
|
||||
nsresult rv = DeleteNodeWithTransaction(*brContent);
|
||||
@ -3868,7 +3862,7 @@ nsresult HTMLEditor::FormatBlockContainerWithTransaction(nsAtom& blockType) {
|
||||
pointToInsertBlock,
|
||||
{WalkTreeOption::IgnoreNonEditableNode,
|
||||
WalkTreeOption::StopAtBlockBoundary},
|
||||
editingHost)) {
|
||||
&aEditingHost)) {
|
||||
if (maybeBRContent->IsHTMLElement(nsGkAtoms::br)) {
|
||||
AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
|
||||
nsresult rv = DeleteNodeWithTransaction(*maybeBRContent);
|
||||
@ -3884,7 +3878,7 @@ nsresult HTMLEditor::FormatBlockContainerWithTransaction(nsAtom& blockType) {
|
||||
CreateElementResult createNewBlockElementResult =
|
||||
InsertElementWithSplittingAncestorsWithTransaction(
|
||||
blockType, pointToInsertBlock, BRElementNextToSplitPoint::Keep,
|
||||
*editingHost);
|
||||
aEditingHost);
|
||||
if (createNewBlockElementResult.isErr()) {
|
||||
NS_WARNING(
|
||||
nsPrintfCString(
|
||||
@ -3932,7 +3926,7 @@ nsresult HTMLEditor::FormatBlockContainerWithTransaction(nsAtom& blockType) {
|
||||
if (&blockType == nsGkAtoms::blockquote) {
|
||||
Result<EditorDOMPoint, nsresult> wrapContentsInBlockquoteElementsResult =
|
||||
WrapContentsInBlockquoteElementsWithTransaction(arrayOfContents,
|
||||
*editingHost);
|
||||
aEditingHost);
|
||||
if (wrapContentsInBlockquoteElementsResult.isErr()) {
|
||||
NS_WARNING(
|
||||
"HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction() "
|
||||
@ -3957,7 +3951,7 @@ nsresult HTMLEditor::FormatBlockContainerWithTransaction(nsAtom& blockType) {
|
||||
}
|
||||
Result<EditorDOMPoint, nsresult> wrapContentsInBlockElementResult =
|
||||
CreateOrChangeBlockContainerElement(arrayOfContents, blockType,
|
||||
*editingHost);
|
||||
aEditingHost);
|
||||
if (MOZ_UNLIKELY(wrapContentsInBlockElementResult.isErr())) {
|
||||
NS_WARNING("HTMLEditor::CreateOrChangeBlockContainerElement() failed");
|
||||
return wrapContentsInBlockElementResult.unwrapErr();
|
||||
@ -3996,7 +3990,7 @@ nsresult HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() {
|
||||
return rv;
|
||||
}
|
||||
|
||||
EditActionResult HTMLEditor::IndentAsSubAction() {
|
||||
EditActionResult HTMLEditor::IndentAsSubAction(const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
|
||||
AutoPlaceholderBatch treatAsOneTransaction(
|
||||
@ -4023,7 +4017,7 @@ EditActionResult HTMLEditor::IndentAsSubAction() {
|
||||
return EditActionIgnored();
|
||||
}
|
||||
|
||||
result |= HandleIndentAtSelection();
|
||||
result |= HandleIndentAtSelection(aEditingHost);
|
||||
if (result.Failed() || result.Canceled()) {
|
||||
NS_WARNING_ASSERTION(result.Succeeded(),
|
||||
"HTMLEditor::HandleIndentAtSelection() failed");
|
||||
@ -4182,7 +4176,8 @@ nsresult HTMLEditor::IndentListChild(RefPtr<Element>* aCurList,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
EditActionResult HTMLEditor::HandleIndentAtSelection() {
|
||||
EditActionResult HTMLEditor::HandleIndentAtSelection(
|
||||
const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
|
||||
|
||||
@ -4219,18 +4214,18 @@ EditActionResult HTMLEditor::HandleIndentAtSelection() {
|
||||
}
|
||||
|
||||
if (IsCSSEnabled()) {
|
||||
nsresult rv = HandleCSSIndentAtSelection();
|
||||
nsresult rv = HandleCSSIndentAtSelection(aEditingHost);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
||||
"HTMLEditor::HandleCSSIndentAtSelection() failed");
|
||||
return EditActionHandled(rv);
|
||||
}
|
||||
rv = HandleHTMLIndentAtSelection();
|
||||
rv = HandleHTMLIndentAtSelection(aEditingHost);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
||||
"HTMLEditor::HandleHTMLIndent() failed");
|
||||
return EditActionHandled(rv);
|
||||
}
|
||||
|
||||
nsresult HTMLEditor::HandleCSSIndentAtSelection() {
|
||||
nsresult HTMLEditor::HandleCSSIndentAtSelection(const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
|
||||
|
||||
@ -4247,7 +4242,7 @@ nsresult HTMLEditor::HandleCSSIndentAtSelection() {
|
||||
// HandleCSSIndentAtSelectionInternal() creates AutoSelectionRestorer.
|
||||
// Therefore, even if it returns NS_OK, editor might have been destroyed
|
||||
// at restoring Selection.
|
||||
nsresult rv = HandleCSSIndentAtSelectionInternal();
|
||||
nsresult rv = HandleCSSIndentAtSelectionInternal(aEditingHost);
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return NS_ERROR_EDITOR_DESTROYED;
|
||||
}
|
||||
@ -4257,15 +4252,11 @@ nsresult HTMLEditor::HandleCSSIndentAtSelection() {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult HTMLEditor::HandleCSSIndentAtSelectionInternal() {
|
||||
nsresult HTMLEditor::HandleCSSIndentAtSelectionInternal(
|
||||
const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
|
||||
MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
|
||||
|
||||
RefPtr<Element> editingHost = ComputeEditingHost();
|
||||
if (MOZ_UNLIKELY(NS_WARN_IF(!editingHost))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
AutoSelectionRestorer restoreSelectionLater(*this);
|
||||
AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
|
||||
|
||||
@ -4289,12 +4280,12 @@ nsresult HTMLEditor::HandleCSSIndentAtSelectionInternal() {
|
||||
}
|
||||
|
||||
if (arrayOfContents.IsEmpty()) {
|
||||
AutoTArray<OwningNonNull<nsRange>, 4> extendedSelectionRanges;
|
||||
GetSelectionRangesExtendedToHardLineStartAndEnd(extendedSelectionRanges,
|
||||
EditSubAction::eIndent);
|
||||
AutoRangeArray extendedSelectionRanges(SelectionRef());
|
||||
extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
|
||||
EditSubAction::eIndent, aEditingHost);
|
||||
nsresult rv = SplitInlinesAndCollectEditTargetNodes(
|
||||
extendedSelectionRanges, arrayOfContents, EditSubAction::eIndent,
|
||||
CollectNonEditableNodes::Yes);
|
||||
extendedSelectionRanges.Ranges(), arrayOfContents,
|
||||
EditSubAction::eIndent, CollectNonEditableNodes::Yes);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING(
|
||||
"SplitInlinesAndCollectEditTargetNodes(EditSubAction::eIndent, "
|
||||
@ -4322,7 +4313,7 @@ nsresult HTMLEditor::HandleCSSIndentAtSelectionInternal() {
|
||||
CreateElementResult createNewDivElementResult =
|
||||
InsertElementWithSplittingAncestorsWithTransaction(
|
||||
*nsGkAtoms::div, atStartOfSelection,
|
||||
BRElementNextToSplitPoint::Keep, *editingHost);
|
||||
BRElementNextToSplitPoint::Keep, aEditingHost);
|
||||
if (createNewDivElementResult.isErr()) {
|
||||
NS_WARNING(
|
||||
"HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
|
||||
@ -4418,7 +4409,7 @@ nsresult HTMLEditor::HandleCSSIndentAtSelectionInternal() {
|
||||
CreateElementResult createNewDivElementResult =
|
||||
InsertElementWithSplittingAncestorsWithTransaction(
|
||||
*nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
|
||||
*editingHost);
|
||||
aEditingHost);
|
||||
if (createNewDivElementResult.isErr()) {
|
||||
NS_WARNING(
|
||||
"HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
|
||||
@ -4471,7 +4462,7 @@ nsresult HTMLEditor::HandleCSSIndentAtSelectionInternal() {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult HTMLEditor::HandleHTMLIndentAtSelection() {
|
||||
nsresult HTMLEditor::HandleHTMLIndentAtSelection(const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
|
||||
|
||||
@ -4488,7 +4479,7 @@ nsresult HTMLEditor::HandleHTMLIndentAtSelection() {
|
||||
// HandleHTMLIndentAtSelectionInternal() creates AutoSelectionRestorer.
|
||||
// Therefore, even if it returns NS_OK, editor might have been destroyed
|
||||
// at restoring Selection.
|
||||
nsresult rv = HandleHTMLIndentAtSelectionInternal();
|
||||
nsresult rv = HandleHTMLIndentAtSelectionInternal(aEditingHost);
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return NS_ERROR_EDITOR_DESTROYED;
|
||||
}
|
||||
@ -4498,14 +4489,10 @@ nsresult HTMLEditor::HandleHTMLIndentAtSelection() {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult HTMLEditor::HandleHTMLIndentAtSelectionInternal() {
|
||||
nsresult HTMLEditor::HandleHTMLIndentAtSelectionInternal(
|
||||
const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
|
||||
|
||||
RefPtr<Element> editingHost = ComputeEditingHost();
|
||||
if (MOZ_UNLIKELY(NS_WARN_IF(!editingHost))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
AutoSelectionRestorer restoreSelectionLater(*this);
|
||||
|
||||
// convert the selection ranges into "promoted" selection ranges:
|
||||
@ -4513,14 +4500,14 @@ nsresult HTMLEditor::HandleHTMLIndentAtSelectionInternal() {
|
||||
// block parent, and then further expands to include any ancestors
|
||||
// whose children are all in the range
|
||||
|
||||
AutoTArray<OwningNonNull<nsRange>, 4> arrayOfRanges;
|
||||
GetSelectionRangesExtendedToHardLineStartAndEnd(arrayOfRanges,
|
||||
EditSubAction::eIndent);
|
||||
AutoRangeArray extendedSelectionRanges(SelectionRef());
|
||||
extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
|
||||
EditSubAction::eIndent, aEditingHost);
|
||||
|
||||
// use these ranges to construct a list of nodes to act on.
|
||||
AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
|
||||
nsresult rv = SplitInlinesAndCollectEditTargetNodes(
|
||||
arrayOfRanges, arrayOfContents, EditSubAction::eIndent,
|
||||
extendedSelectionRanges.Ranges(), arrayOfContents, EditSubAction::eIndent,
|
||||
CollectNonEditableNodes::Yes);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING(
|
||||
@ -4547,7 +4534,7 @@ nsresult HTMLEditor::HandleHTMLIndentAtSelectionInternal() {
|
||||
CreateElementResult createNewBlockQuoteElementResult =
|
||||
InsertElementWithSplittingAncestorsWithTransaction(
|
||||
*nsGkAtoms::blockquote, atStartOfSelection,
|
||||
BRElementNextToSplitPoint::Keep, *editingHost);
|
||||
BRElementNextToSplitPoint::Keep, aEditingHost);
|
||||
if (createNewBlockQuoteElementResult.isErr()) {
|
||||
NS_WARNING(
|
||||
"HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
|
||||
@ -4622,7 +4609,7 @@ nsresult HTMLEditor::HandleHTMLIndentAtSelectionInternal() {
|
||||
// to act on that is still inside the same li.
|
||||
if (RefPtr<Element> listItem =
|
||||
HTMLEditUtils::GetClosestAncestorListItemElement(content,
|
||||
editingHost)) {
|
||||
&aEditingHost)) {
|
||||
if (indentedLI == listItem) {
|
||||
// already indented this list item
|
||||
continue;
|
||||
@ -4645,7 +4632,7 @@ nsresult HTMLEditor::HandleHTMLIndentAtSelectionInternal() {
|
||||
CreateElementResult createNewListElementResult =
|
||||
InsertElementWithSplittingAncestorsWithTransaction(
|
||||
MOZ_KnownLive(*containerName), atListItem,
|
||||
BRElementNextToSplitPoint::Keep, *editingHost);
|
||||
BRElementNextToSplitPoint::Keep, aEditingHost);
|
||||
if (createNewListElementResult.isErr()) {
|
||||
NS_WARNING(nsPrintfCString("HTMLEditor::"
|
||||
"InsertElementWithSplittingAncestorsWithTr"
|
||||
@ -4709,7 +4696,7 @@ nsresult HTMLEditor::HandleHTMLIndentAtSelectionInternal() {
|
||||
CreateElementResult createNewBlockQuoteElementResult =
|
||||
InsertElementWithSplittingAncestorsWithTransaction(
|
||||
*nsGkAtoms::blockquote, atContent,
|
||||
BRElementNextToSplitPoint::Keep, *editingHost);
|
||||
BRElementNextToSplitPoint::Keep, aEditingHost);
|
||||
if (createNewBlockQuoteElementResult.isErr()) {
|
||||
NS_WARNING(
|
||||
"HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
|
||||
@ -4759,7 +4746,7 @@ nsresult HTMLEditor::HandleHTMLIndentAtSelectionInternal() {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
EditActionResult HTMLEditor::OutdentAsSubAction() {
|
||||
EditActionResult HTMLEditor::OutdentAsSubAction(const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
|
||||
AutoPlaceholderBatch treatAsOneTransaction(
|
||||
@ -4786,7 +4773,7 @@ EditActionResult HTMLEditor::OutdentAsSubAction() {
|
||||
return EditActionIgnored();
|
||||
}
|
||||
|
||||
result |= HandleOutdentAtSelection();
|
||||
result |= HandleOutdentAtSelection(aEditingHost);
|
||||
if (result.Failed() || result.Canceled()) {
|
||||
NS_WARNING_ASSERTION(result.Succeeded(),
|
||||
"HTMLEditor::HandleOutdentAtSelection() failed");
|
||||
@ -4806,7 +4793,8 @@ EditActionResult HTMLEditor::OutdentAsSubAction() {
|
||||
return result.SetResult(rv);
|
||||
}
|
||||
|
||||
EditActionResult HTMLEditor::HandleOutdentAtSelection() {
|
||||
EditActionResult HTMLEditor::HandleOutdentAtSelection(
|
||||
const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
|
||||
|
||||
@ -4821,7 +4809,7 @@ EditActionResult HTMLEditor::HandleOutdentAtSelection() {
|
||||
// Therefore, even if it returns NS_OK, the editor might have been destroyed
|
||||
// at restoring Selection.
|
||||
SplitRangeOffFromNodeResult outdentResult =
|
||||
HandleOutdentAtSelectionInternal();
|
||||
HandleOutdentAtSelectionInternal(aEditingHost);
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
|
||||
}
|
||||
@ -4900,7 +4888,8 @@ EditActionResult HTMLEditor::HandleOutdentAtSelection() {
|
||||
return EditActionHandled();
|
||||
}
|
||||
|
||||
SplitRangeOffFromNodeResult HTMLEditor::HandleOutdentAtSelectionInternal() {
|
||||
SplitRangeOffFromNodeResult HTMLEditor::HandleOutdentAtSelectionInternal(
|
||||
const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
|
||||
AutoSelectionRestorer restoreSelectionLater(*this);
|
||||
@ -4913,12 +4902,12 @@ SplitRangeOffFromNodeResult HTMLEditor::HandleOutdentAtSelectionInternal() {
|
||||
// in the range
|
||||
AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
|
||||
{
|
||||
AutoTArray<OwningNonNull<nsRange>, 4> extendedSelectionRanges;
|
||||
GetSelectionRangesExtendedToHardLineStartAndEnd(extendedSelectionRanges,
|
||||
EditSubAction::eOutdent);
|
||||
AutoRangeArray extendedSelectionRanges(SelectionRef());
|
||||
extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
|
||||
EditSubAction::eOutdent, aEditingHost);
|
||||
nsresult rv = SplitInlinesAndCollectEditTargetNodes(
|
||||
extendedSelectionRanges, arrayOfContents, EditSubAction::eOutdent,
|
||||
CollectNonEditableNodes::Yes);
|
||||
extendedSelectionRanges.Ranges(), arrayOfContents,
|
||||
EditSubAction::eOutdent, CollectNonEditableNodes::Yes);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING(
|
||||
"HTMLEditor::SplitInlinesAndCollectEditTargetNodes(EditSubAction::"
|
||||
@ -5622,7 +5611,8 @@ nsresult HTMLEditor::CreateStyleForInsertText(
|
||||
return rv;
|
||||
}
|
||||
|
||||
EditActionResult HTMLEditor::AlignAsSubAction(const nsAString& aAlignType) {
|
||||
EditActionResult HTMLEditor::AlignAsSubAction(const nsAString& aAlignType,
|
||||
const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
|
||||
AutoPlaceholderBatch treatAsOneTransaction(
|
||||
@ -5695,7 +5685,7 @@ EditActionResult HTMLEditor::AlignAsSubAction(const nsAString& aAlignType) {
|
||||
// AlignContentsAtSelection() creates AutoSelectionRestorer. Therefore,
|
||||
// we need to check whether we've been destroyed or not even if it returns
|
||||
// NS_OK.
|
||||
rv = AlignContentsAtSelection(aAlignType);
|
||||
rv = AlignContentsAtSelection(aAlignType, aEditingHost);
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
|
||||
}
|
||||
@ -5717,7 +5707,8 @@ EditActionResult HTMLEditor::AlignAsSubAction(const nsAString& aAlignType) {
|
||||
return EditActionHandled(rv);
|
||||
}
|
||||
|
||||
nsresult HTMLEditor::AlignContentsAtSelection(const nsAString& aAlignType) {
|
||||
nsresult HTMLEditor::AlignContentsAtSelection(const nsAString& aAlignType,
|
||||
const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
|
||||
|
||||
@ -5729,11 +5720,11 @@ nsresult HTMLEditor::AlignContentsAtSelection(const nsAString& aAlignType) {
|
||||
// in the range
|
||||
AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
|
||||
{
|
||||
AutoTArray<OwningNonNull<nsRange>, 4> extendedSelectionRanges;
|
||||
GetSelectionRangesExtendedToHardLineStartAndEnd(
|
||||
extendedSelectionRanges, EditSubAction::eSetOrClearAlignment);
|
||||
AutoRangeArray extendedSelectionRanges(SelectionRef());
|
||||
extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
|
||||
EditSubAction::eSetOrClearAlignment, aEditingHost);
|
||||
nsresult rv = SplitInlinesAndCollectEditTargetNodes(
|
||||
extendedSelectionRanges, arrayOfContents,
|
||||
extendedSelectionRanges.Ranges(), arrayOfContents,
|
||||
EditSubAction::eSetOrClearAlignment, CollectNonEditableNodes::Yes);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING(
|
||||
@ -6396,42 +6387,6 @@ nsresult HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() {
|
||||
return error.StealNSResult();
|
||||
}
|
||||
|
||||
void HTMLEditor::GetSelectionRangesExtendedToHardLineStartAndEnd(
|
||||
nsTArray<OwningNonNull<nsRange>>& aOutArrayOfRanges,
|
||||
EditSubAction aEditSubAction) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
MOZ_ASSERT(aOutArrayOfRanges.IsEmpty());
|
||||
|
||||
Element* editingHost = ComputeEditingHost();
|
||||
|
||||
const uint32_t rangeCount = SelectionRef().RangeCount();
|
||||
aOutArrayOfRanges.SetCapacity(rangeCount);
|
||||
for (const uint32_t i : IntegerRange(rangeCount)) {
|
||||
MOZ_ASSERT(SelectionRef().RangeCount() == rangeCount);
|
||||
// Make a new adjusted range to represent the appropriate block content.
|
||||
// The basic idea is to push out the range endpoints to truly enclose the
|
||||
// blocks that we will affect. This call alters opRange.
|
||||
nsRange* selectionRange = SelectionRef().GetRangeAt(i);
|
||||
MOZ_ASSERT(selectionRange);
|
||||
EditorDOMRange editorRange(*selectionRange);
|
||||
if (!editorRange.IsPositioned() ||
|
||||
!editorRange.EnsureNotInNativeAnonymousSubtree()) {
|
||||
continue; // ignore ranges which are in orphan fragment which were
|
||||
// disconnected from native anonymous subtrees
|
||||
}
|
||||
RefPtr<nsRange> extendedRange;
|
||||
if (editingHost) {
|
||||
extendedRange = AutoRangeArray::
|
||||
CreateRangeWrappingStartAndEndLinesContainingBoundaries(
|
||||
editorRange, aEditSubAction, *editingHost);
|
||||
}
|
||||
if (!extendedRange) {
|
||||
extendedRange = selectionRange->CloneRange();
|
||||
}
|
||||
aOutArrayOfRanges.AppendElement(std::move(extendedRange));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename EditorDOMRangeType>
|
||||
already_AddRefed<nsRange> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
|
||||
const EditorDOMRangeType& aRange) {
|
||||
@ -7573,7 +7528,7 @@ nsresult HTMLEditor::SplitParagraph(Element& aParentDivOrP,
|
||||
Result<EditorDOMPoint, nsresult>
|
||||
HTMLEditor::HandleInsertParagraphInListItemElement(
|
||||
Element& aListItemElement, const EditorDOMPoint& aPointToSplit,
|
||||
Element& aEditingHost) {
|
||||
const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
MOZ_ASSERT(HTMLEditUtils::IsListItem(&aListItemElement));
|
||||
|
||||
@ -10044,7 +9999,8 @@ nsresult HTMLEditor::ChangeMarginStart(Element& aElement,
|
||||
return rv;
|
||||
}
|
||||
|
||||
EditActionResult HTMLEditor::SetSelectionToAbsoluteAsSubAction() {
|
||||
EditActionResult HTMLEditor::SetSelectionToAbsoluteAsSubAction(
|
||||
const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
|
||||
|
||||
AutoPlaceholderBatch treatAsOneTransaction(
|
||||
@ -10115,7 +10071,7 @@ EditActionResult HTMLEditor::SetSelectionToAbsoluteAsSubAction() {
|
||||
|
||||
RefPtr<Element> divElement;
|
||||
rv = MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
|
||||
address_of(divElement));
|
||||
address_of(divElement), aEditingHost);
|
||||
// MoveSelectedContentsToDivElementToMakeItAbsolutePosition() may restore
|
||||
// selection with AutoSelectionRestorer. Therefore, the editor might have
|
||||
// already been destroyed now.
|
||||
@ -10155,26 +10111,21 @@ EditActionResult HTMLEditor::SetSelectionToAbsoluteAsSubAction() {
|
||||
}
|
||||
|
||||
nsresult HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
|
||||
RefPtr<Element>* aTargetElement) {
|
||||
RefPtr<Element>* aTargetElement, const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
MOZ_ASSERT(aTargetElement);
|
||||
|
||||
RefPtr<Element> editingHost = ComputeEditingHost();
|
||||
if (MOZ_UNLIKELY(NS_WARN_IF(!editingHost))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
AutoSelectionRestorer restoreSelectionLater(*this);
|
||||
|
||||
AutoTArray<OwningNonNull<nsRange>, 4> arrayOfRanges;
|
||||
GetSelectionRangesExtendedToHardLineStartAndEnd(
|
||||
arrayOfRanges, EditSubAction::eSetPositionToAbsolute);
|
||||
AutoRangeArray extendedSelectionRanges(SelectionRef());
|
||||
extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
|
||||
EditSubAction::eSetPositionToAbsolute, aEditingHost);
|
||||
|
||||
// Use these ranges to construct a list of nodes to act on.
|
||||
AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
|
||||
nsresult rv = SplitInlinesAndCollectEditTargetNodes(
|
||||
arrayOfRanges, arrayOfContents, EditSubAction::eSetPositionToAbsolute,
|
||||
CollectNonEditableNodes::Yes);
|
||||
extendedSelectionRanges.Ranges(), arrayOfContents,
|
||||
EditSubAction::eSetPositionToAbsolute, CollectNonEditableNodes::Yes);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING(
|
||||
"HTMLEditor::SplitInlinesAndCollectEditTargetNodes("
|
||||
@ -10200,7 +10151,7 @@ nsresult HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
|
||||
CreateElementResult createNewDivElementResult =
|
||||
InsertElementWithSplittingAncestorsWithTransaction(
|
||||
*nsGkAtoms::div, atCaret, BRElementNextToSplitPoint::Keep,
|
||||
*editingHost);
|
||||
aEditingHost);
|
||||
if (createNewDivElementResult.isErr()) {
|
||||
NS_WARNING(
|
||||
"HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
|
||||
@ -10292,7 +10243,7 @@ nsresult HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
|
||||
CreateElementResult createNewDivElementResult =
|
||||
InsertElementWithSplittingAncestorsWithTransaction(
|
||||
*nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
|
||||
*editingHost);
|
||||
aEditingHost);
|
||||
if (createNewDivElementResult.isErr()) {
|
||||
NS_WARNING(
|
||||
"HTMLEditor::"
|
||||
@ -10358,7 +10309,7 @@ nsresult HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
|
||||
// because we want to keep indent level of the contents.
|
||||
if (RefPtr<Element> listItemElement =
|
||||
HTMLEditUtils::GetClosestAncestorListItemElement(content,
|
||||
editingHost)) {
|
||||
&aEditingHost)) {
|
||||
if (handledListItemElement == listItemElement) {
|
||||
// Current node has already been moved into the `<div>` element.
|
||||
continue;
|
||||
@ -10402,7 +10353,7 @@ nsresult HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
|
||||
CreateElementResult createNewDivElementResult =
|
||||
InsertElementWithSplittingAncestorsWithTransaction(
|
||||
*nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
|
||||
*editingHost);
|
||||
aEditingHost);
|
||||
if (createNewDivElementResult.isErr()) {
|
||||
NS_WARNING(
|
||||
"HTMLEditor::"
|
||||
@ -10481,7 +10432,7 @@ nsresult HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
|
||||
CreateElementResult createNewDivElementResult =
|
||||
InsertElementWithSplittingAncestorsWithTransaction(
|
||||
*nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
|
||||
*editingHost);
|
||||
aEditingHost);
|
||||
if (createNewDivElementResult.isErr()) {
|
||||
NS_WARNING(
|
||||
"HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
|
||||
|
@ -1257,7 +1257,12 @@ NS_IMETHODIMP HTMLEditor::InsertLineBreak() {
|
||||
return EditorBase::ToGenericNSResult(rv);
|
||||
}
|
||||
|
||||
EditActionResult result = InsertParagraphSeparatorAsSubAction();
|
||||
const RefPtr<Element> editingHost = ComputeEditingHost();
|
||||
if (!editingHost) {
|
||||
return NS_SUCCESS_DOM_NO_OPERATION;
|
||||
}
|
||||
|
||||
EditActionResult result = InsertParagraphSeparatorAsSubAction(*editingHost);
|
||||
NS_WARNING_ASSERTION(
|
||||
result.Succeeded(),
|
||||
"HTMLEditor::InsertParagraphSeparatorAsSubAction() failed");
|
||||
@ -1297,7 +1302,12 @@ nsresult HTMLEditor::InsertParagraphSeparatorAsAction(
|
||||
return EditorBase::ToGenericNSResult(rv);
|
||||
}
|
||||
|
||||
EditActionResult result = InsertParagraphSeparatorAsSubAction();
|
||||
const RefPtr<Element> editingHost = ComputeEditingHost();
|
||||
if (!editingHost) {
|
||||
return NS_SUCCESS_DOM_NO_OPERATION;
|
||||
}
|
||||
|
||||
EditActionResult result = InsertParagraphSeparatorAsSubAction(*editingHost);
|
||||
NS_WARNING_ASSERTION(
|
||||
result.Succeeded(),
|
||||
"HTMLEditor::InsertParagraphSeparatorAsSubAction() failed");
|
||||
@ -2153,6 +2163,12 @@ nsresult HTMLEditor::SetParagraphFormatAsAction(
|
||||
return EditorBase::ToGenericNSResult(rv);
|
||||
}
|
||||
|
||||
// TODO: Computing the editing host here makes the `execCommand` in
|
||||
// docshell/base/crashtests/file_432114-2.xhtml cannot run
|
||||
// `DOMNodeRemoved` event listener with deleting the bogus <br> element.
|
||||
// So that it should be rewritten with different mutation event listener
|
||||
// since we'd like to stop using it.
|
||||
|
||||
nsAutoString lowerCaseTagName(aParagraphFormat);
|
||||
ToLowerCase(lowerCaseTagName);
|
||||
RefPtr<nsAtom> tagName = NS_Atomize(lowerCaseTagName);
|
||||
@ -2165,6 +2181,7 @@ nsresult HTMLEditor::SetParagraphFormatAsAction(
|
||||
"SelectAllOfCurrentList::No) failed");
|
||||
return EditorBase::ToGenericNSResult(result.Rv());
|
||||
}
|
||||
|
||||
rv = FormatBlockContainerAsSubAction(*tagName);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
||||
"HTMLEditor::FormatBlockContainerAsSubAction() failed");
|
||||
@ -2573,7 +2590,12 @@ nsresult HTMLEditor::RemoveListAsAction(const nsAString& aListType,
|
||||
return EditorBase::ToGenericNSResult(rv);
|
||||
}
|
||||
|
||||
rv = RemoveListAtSelectionAsSubAction();
|
||||
const RefPtr<Element> editingHost = ComputeEditingHost();
|
||||
if (!editingHost) {
|
||||
return NS_SUCCESS_DOM_NO_OPERATION;
|
||||
}
|
||||
|
||||
rv = RemoveListAtSelectionAsSubAction(*editingHost);
|
||||
NS_WARNING_ASSERTION(NS_FAILED(rv),
|
||||
"HTMLEditor::RemoveListAtSelectionAsSubAction() failed");
|
||||
return rv;
|
||||
@ -2642,7 +2664,11 @@ nsresult HTMLEditor::FormatBlockContainerAsSubAction(nsAtom& aTagName) {
|
||||
// FormatBlockContainerWithTransaction() creates AutoSelectionRestorer.
|
||||
// Therefore, even if it returns NS_OK, editor might have been destroyed
|
||||
// at restoring Selection.
|
||||
rv = FormatBlockContainerWithTransaction(aTagName);
|
||||
const RefPtr<Element> editingHost = ComputeEditingHost();
|
||||
if (MOZ_UNLIKELY(!editingHost)) {
|
||||
return NS_SUCCESS_DOM_NO_OPERATION;
|
||||
}
|
||||
rv = FormatBlockContainerWithTransaction(aTagName, *editingHost);
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return NS_ERROR_EDITOR_DESTROYED;
|
||||
}
|
||||
@ -2673,7 +2699,12 @@ nsresult HTMLEditor::IndentAsAction(nsIPrincipal* aPrincipal) {
|
||||
return EditorBase::ToGenericNSResult(rv);
|
||||
}
|
||||
|
||||
EditActionResult result = IndentAsSubAction();
|
||||
const RefPtr<Element> editingHost = ComputeEditingHost();
|
||||
if (!editingHost) {
|
||||
return NS_SUCCESS_DOM_NO_OPERATION;
|
||||
}
|
||||
|
||||
EditActionResult result = IndentAsSubAction(*editingHost);
|
||||
NS_WARNING_ASSERTION(result.Succeeded(),
|
||||
"HTMLEditor::IndentAsSubAction() failed");
|
||||
return EditorBase::ToGenericNSResult(result.Rv());
|
||||
@ -2693,7 +2724,12 @@ nsresult HTMLEditor::OutdentAsAction(nsIPrincipal* aPrincipal) {
|
||||
return EditorBase::ToGenericNSResult(rv);
|
||||
}
|
||||
|
||||
EditActionResult result = OutdentAsSubAction();
|
||||
const RefPtr<Element> editingHost = ComputeEditingHost();
|
||||
if (!editingHost) {
|
||||
return NS_SUCCESS_DOM_NO_OPERATION;
|
||||
}
|
||||
|
||||
EditActionResult result = OutdentAsSubAction(*editingHost);
|
||||
NS_WARNING_ASSERTION(result.Succeeded(),
|
||||
"HTMLEditor::OutdentAsSubAction() failed");
|
||||
return EditorBase::ToGenericNSResult(result.Rv());
|
||||
@ -2712,7 +2748,12 @@ nsresult HTMLEditor::AlignAsAction(const nsAString& aAlignType,
|
||||
return EditorBase::ToGenericNSResult(rv);
|
||||
}
|
||||
|
||||
EditActionResult result = AlignAsSubAction(aAlignType);
|
||||
const RefPtr<Element> editingHost = ComputeEditingHost();
|
||||
if (!editingHost) {
|
||||
return NS_SUCCESS_DOM_NO_OPERATION;
|
||||
}
|
||||
|
||||
EditActionResult result = AlignAsSubAction(aAlignType, *editingHost);
|
||||
NS_WARNING_ASSERTION(result.Succeeded(),
|
||||
"HTMLEditor::AlignAsSubAction() failed");
|
||||
return EditorBase::ToGenericNSResult(result.Rv());
|
||||
@ -5860,9 +5901,8 @@ nsresult HTMLEditor::SetBackgroundColorAsAction(const nsAString& aColor,
|
||||
}
|
||||
|
||||
Result<EditorDOMPoint, nsresult>
|
||||
HTMLEditor::CopyLastEditableChildStylesWithTransaction(Element& aPreviousBlock,
|
||||
Element& aNewBlock,
|
||||
Element& aEditingHost) {
|
||||
HTMLEditor::CopyLastEditableChildStylesWithTransaction(
|
||||
Element& aPreviousBlock, Element& aNewBlock, const Element& aEditingHost) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
|
||||
// First, clear out aNewBlock. Contract is that we want only the styles
|
||||
|
@ -758,14 +758,14 @@ class HTMLEditor final : public EditorBase,
|
||||
* are cloned to aNewBlock.
|
||||
* @param aNewBlock New block container element. All children of
|
||||
* this is deleted first.
|
||||
* @param aEditingHost Current editing host.
|
||||
* @param aEditingHost The editing host.
|
||||
* @return If succeeded, returns a suggesting point to put
|
||||
* caret.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
|
||||
CopyLastEditableChildStylesWithTransaction(Element& aPreviousBlock,
|
||||
Element& aNewBlock,
|
||||
Element& aEditingHost);
|
||||
const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* RemoveBlockContainerWithTransaction() removes aElement from the DOM tree
|
||||
@ -1087,14 +1087,14 @@ class HTMLEditor final : public EditorBase,
|
||||
*
|
||||
* @param aMailCiteElement The mail-cite element which should be split.
|
||||
* @param aPointToSplit The point to split.
|
||||
* @param aEditingHost Current editing host element.
|
||||
* @param aEditingHost The editing host.
|
||||
* @return Candidate caret position where is at inserted
|
||||
* <br> element into the split point.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
|
||||
HandleInsertParagraphInMailCiteElement(Element& aMailCiteElement,
|
||||
const EditorDOMPoint& aPointToSplit,
|
||||
Element& aEditingHost);
|
||||
const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* HandleInsertBRElement() inserts a <br> element into aInsertToBreak.
|
||||
@ -1108,7 +1108,7 @@ class HTMLEditor final : public EditorBase,
|
||||
* candidate caret point.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT CreateElementResult HandleInsertBRElement(
|
||||
const EditorDOMPoint& aInsertToBreak, Element& aEditingHost);
|
||||
const EditorDOMPoint& aInsertToBreak, const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* HandleInsertLinefeed() inserts a linefeed character into aInsertToBreak.
|
||||
@ -1118,7 +1118,7 @@ class HTMLEditor final : public EditorBase,
|
||||
* @param aEditingHost Current active editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleInsertLinefeed(
|
||||
const EditorDOMPoint& aInsertToBreak, Element& aEditingHost);
|
||||
const EditorDOMPoint& aInsertToBreak, const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* SplitParentInlineElementsAtRangeEdges() splits parent inline nodes at both
|
||||
@ -1243,19 +1243,6 @@ class HTMLEditor final : public EditorBase,
|
||||
const EditorDOMPointType1& aStartPoint,
|
||||
const EditorDOMPointType2& aEndPoint);
|
||||
|
||||
/**
|
||||
* GetSelectionRangesExtendedToHardLineStartAndEnd() collects selection ranges
|
||||
* with extending to start/end of hard line from each range start and end.
|
||||
* XXX This means that same range may be included in the result.
|
||||
*
|
||||
* @param aOutArrayOfRanges [out] Always appended same number of ranges
|
||||
* as Selection::RangeCount(). Must be empty
|
||||
* when you call this.
|
||||
*/
|
||||
void GetSelectionRangesExtendedToHardLineStartAndEnd(
|
||||
nsTArray<OwningNonNull<nsRange>>& aOutArrayOfRanges,
|
||||
EditSubAction aEditSubAction);
|
||||
|
||||
/**
|
||||
* GetChildNodesOf() returns all child nodes of aParent with an array.
|
||||
*/
|
||||
@ -1497,9 +1484,10 @@ class HTMLEditor final : public EditorBase,
|
||||
* be called.
|
||||
* Otherwise, CreateOrChangeBlockContainerElement() will be
|
||||
* called.
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
|
||||
FormatBlockContainerWithTransaction(nsAtom& aBlockType);
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult FormatBlockContainerWithTransaction(
|
||||
nsAtom& aBlockType, const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() determines if
|
||||
@ -1583,20 +1571,22 @@ class HTMLEditor final : public EditorBase,
|
||||
*
|
||||
* @param aListItemElement The list item which has the following point.
|
||||
* @param aPointToSplit The point to split aListItemElement.
|
||||
* @param aEditingHost The editing host for aListItemElement
|
||||
* @param aEditingHost The editing host.
|
||||
* @return A candidate position to put caret.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
|
||||
HandleInsertParagraphInListItemElement(Element& aListItemElement,
|
||||
const EditorDOMPoint& aPointToSplit,
|
||||
Element& aEditingHost);
|
||||
const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* InsertParagraphSeparatorAsSubAction() handles insertPargraph commad
|
||||
* (i.e., handling Enter key press) with the above helper methods.
|
||||
*
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
|
||||
InsertParagraphSeparatorAsSubAction();
|
||||
InsertParagraphSeparatorAsSubAction(const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* InsertLineBreakAsSubAction() inserts a new <br> element or a linefeed
|
||||
@ -1641,12 +1631,14 @@ class HTMLEditor final : public EditorBase,
|
||||
* attributes will be removed.
|
||||
* @param aSelectAllOfCurrentList Yes if this should treat all of
|
||||
* ancestor list element at selection.
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
|
||||
ChangeSelectedHardLinesToList(nsAtom& aListElementTagName,
|
||||
nsAtom& aListItemElementTagName,
|
||||
const nsAString& aBulletType,
|
||||
SelectAllOfCurrentList aSelectAllOfCurrentList);
|
||||
SelectAllOfCurrentList aSelectAllOfCurrentList,
|
||||
const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* MakeOrChangeListAndListItemAsSubAction() handles create list commands with
|
||||
@ -2318,8 +2310,11 @@ class HTMLEditor final : public EditorBase,
|
||||
* RemoveListAtSelectionAsSubAction() removes list elements and list item
|
||||
* elements at Selection. And move contents in them where the removed list
|
||||
* was.
|
||||
*
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult RemoveListAtSelectionAsSubAction();
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
|
||||
RemoveListAtSelectionAsSubAction(const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* ChangeMarginStart() changes margin of aElement to indent or outdent.
|
||||
@ -2336,9 +2331,11 @@ class HTMLEditor final : public EditorBase,
|
||||
* need to check if the editor is still available even if this returns
|
||||
* NS_OK.
|
||||
* NOTE: Use HandleCSSIndentAtSelection() instead.
|
||||
*
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
|
||||
HandleCSSIndentAtSelectionInternal();
|
||||
HandleCSSIndentAtSelectionInternal(const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* HandleHTMLIndentAtSelectionInternal() indents around Selection with HTML.
|
||||
@ -2346,30 +2343,41 @@ class HTMLEditor final : public EditorBase,
|
||||
* need to check if the editor is still available even if this returns
|
||||
* NS_OK.
|
||||
* NOTE: Use HandleHTMLIndentAtSelection() instead.
|
||||
*
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
|
||||
HandleHTMLIndentAtSelectionInternal();
|
||||
HandleHTMLIndentAtSelectionInternal(const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* HandleCSSIndentAtSelection() indents around Selection with CSS.
|
||||
* NOTE: This is a helper method of `HandleIndentAtSelection()`. If you
|
||||
* want to call this directly, you should check whether you need
|
||||
* do do something which `HandleIndentAtSelection()` does.
|
||||
*
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleCSSIndentAtSelection();
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
|
||||
HandleCSSIndentAtSelection(const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* HandleHTMLIndentAtSelection() indents around Selection with HTML.
|
||||
* NOTE: This is a helper method of `HandleIndentAtSelection()`. If you
|
||||
* want to call this directly, you should check whether you need
|
||||
* do do something which `HandleIndentAtSelection()` does.
|
||||
*
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleHTMLIndentAtSelection();
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
|
||||
HandleHTMLIndentAtSelection(const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* HandleIndentAtSelection() indents around Selection with HTML or CSS.
|
||||
*
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult HandleIndentAtSelection();
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
|
||||
HandleIndentAtSelection(const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* OutdentPartOfBlock() outdents the nodes between aStartOfOutdent and
|
||||
@ -2407,6 +2415,7 @@ class HTMLEditor final : public EditorBase,
|
||||
* NS_OK.
|
||||
* NOTE: Call `HandleOutdentAtSelection()` instead.
|
||||
*
|
||||
* @param aEditingHost The editing host.
|
||||
* @return The left content is left content of last
|
||||
* outdented element.
|
||||
* The right content is right content of last
|
||||
@ -2415,12 +2424,15 @@ class HTMLEditor final : public EditorBase,
|
||||
* outdented element.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT SplitRangeOffFromNodeResult
|
||||
HandleOutdentAtSelectionInternal();
|
||||
HandleOutdentAtSelectionInternal(const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* HandleOutdentAtSelection() outdents contents around Selection.
|
||||
*
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult HandleOutdentAtSelection();
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
|
||||
HandleOutdentAtSelection(const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* AlignBlockContentsWithDivElement() sets align attribute of <div> element
|
||||
@ -2555,18 +2567,20 @@ class HTMLEditor final : public EditorBase,
|
||||
*
|
||||
* @param aAlignType New align attribute value where the contents
|
||||
* should be aligned to.
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
|
||||
AlignContentsAtSelection(const nsAString& aAlignType);
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult AlignContentsAtSelection(
|
||||
const nsAString& aAlignType, const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* AlignAsSubAction() handles "align" command with `Selection`.
|
||||
*
|
||||
* @param aAlignType New align attribute value where the contents
|
||||
* should be aligned to.
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
|
||||
AlignAsSubAction(const nsAString& aAlignType);
|
||||
AlignAsSubAction(const nsAString& aAlignType, const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* AdjustCaretPositionAndEnsurePaddingBRElement() may adjust caret
|
||||
@ -2642,10 +2656,12 @@ class HTMLEditor final : public EditorBase,
|
||||
*
|
||||
* @param aTargetElement Returns target `<div>` element which should be
|
||||
* changed to absolute positioned.
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
// TODO: Rewrite this with `Result<RefPtr<Element>, nsresult>`.
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
|
||||
MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
|
||||
RefPtr<Element>* aTargetElement);
|
||||
RefPtr<Element>* aTargetElement, const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* SetSelectionToAbsoluteAsSubAction() move selected contents to first
|
||||
@ -2653,9 +2669,11 @@ class HTMLEditor final : public EditorBase,
|
||||
* the `<div>` element positioned absolutely.
|
||||
* mNewBlockElement of TopLevelEditSubActionData will be set to the `<div>`
|
||||
* element.
|
||||
*
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
|
||||
SetSelectionToAbsoluteAsSubAction();
|
||||
SetSelectionToAbsoluteAsSubAction(const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* SetSelectionToStaticAsSubAction() sets the `position` property of a
|
||||
@ -3275,13 +3293,19 @@ class HTMLEditor final : public EditorBase,
|
||||
|
||||
/**
|
||||
* IndentAsSubAction() indents the content around Selection.
|
||||
*
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult IndentAsSubAction();
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
|
||||
IndentAsSubAction(const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* OutdentAsSubAction() outdents the content around Selection.
|
||||
*
|
||||
* @param aEditingHost The editing host.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult OutdentAsSubAction();
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult
|
||||
OutdentAsSubAction(const Element& aEditingHost);
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT nsresult LoadHTML(const nsAString& aInputString);
|
||||
|
||||
@ -4486,10 +4510,8 @@ class HTMLEditor final : public EditorBase,
|
||||
|
||||
ParagraphSeparator mDefaultParagraphSeparator;
|
||||
|
||||
friend class
|
||||
AlignStateAtSelection; // CollectEditableTargetNodes,
|
||||
// CollectNonEditableNodes,
|
||||
// GetSelectionRangesExtendedToHardLineStartAndEnd
|
||||
friend class AlignStateAtSelection; // CollectEditableTargetNodes,
|
||||
// CollectNonEditableNodes
|
||||
friend class AutoSelectionSetterAfterTableEdit; // SetSelectionAfterEdit
|
||||
friend class
|
||||
AutoSetTemporaryAncestorLimiter; // InitializeSelectionAncestorLimit
|
||||
@ -4503,29 +4525,23 @@ class HTMLEditor final : public EditorBase,
|
||||
// ReflectPaddingBRElementForEmptyEditor,
|
||||
// RefreshEditingUI,
|
||||
// RemoveEmptyInclusiveAncestorInlineElements,
|
||||
// mComposerUpdater, mHasBeforeINputBeenCancelded
|
||||
// mComposerUpdater, mHasBeforeInputBeenCanceled
|
||||
friend class JoinNodesTransaction; // DidJoinNodesTransaction, DoJoinNodes,
|
||||
// DoSplitNode, RangeUpdaterRef
|
||||
friend class
|
||||
ListElementSelectionState; // CollectEditTargetNodes,
|
||||
// CollectNonEditableNodes,
|
||||
// GetSelectionRangesExtendedToHardLineStartAndEnd
|
||||
friend class
|
||||
ListItemElementSelectionState; // CollectEditTargetNodes,
|
||||
// CollectNonEditableNodes,
|
||||
// GetSelectionRangesExtendedToHardLineStartAndEnd
|
||||
friend class ListElementSelectionState; // CollectEditTargetNodes,
|
||||
// CollectNonEditableNodes
|
||||
friend class ListItemElementSelectionState; // CollectEditTargetNodes,
|
||||
// CollectNonEditableNodes
|
||||
friend class MoveNodeResult; // AllowsTransactionsToChangeSelection,
|
||||
// CollapseSelectionTo
|
||||
friend class MoveNodeTransaction; // AllowsTransactionsToChangeSelection,
|
||||
// CollapseSelectionTo, MarkElementDirty,
|
||||
// RangeUpdaterRef
|
||||
friend class
|
||||
ParagraphStateAtSelection; // CollectChildren,
|
||||
// CollectEditTargetNodes,
|
||||
// CollectListChildren,
|
||||
// CollectNonEditableNodes,
|
||||
// CollectTableChildren,
|
||||
// GetSelectionRangesExtendedToHardLineStartAndEnd
|
||||
friend class ParagraphStateAtSelection; // CollectChildren,
|
||||
// CollectEditTargetNodes,
|
||||
// CollectListChildren,
|
||||
// CollectNonEditableNodes,
|
||||
// CollectTableChildren
|
||||
friend class SlurpBlobEventListener; // BlobReader
|
||||
friend class SplitNodeResult; // CollapseSelectionTo
|
||||
friend class SplitNodeTransaction; // DoJoinNodes, DoSplitNode
|
||||
@ -4662,13 +4678,13 @@ class MOZ_STACK_CLASS ParagraphStateAtSelection final {
|
||||
/**
|
||||
* CollectEditableFormatNodesInSelection() collects only editable nodes
|
||||
* around selection ranges (with
|
||||
* `HTMLEditor::GetSelectionRangesExtendedToHardLineStartAndEnd()` and
|
||||
* `AutoRangeArray::ExtendRangesToWrapLinesToHandleBlockLevelEditAction()` and
|
||||
* `HTMLEditor::CollectEditTargetNodes()`, see its document for the detail).
|
||||
* If it includes list, list item or table related elements, they will be
|
||||
* replaced their children.
|
||||
*/
|
||||
static nsresult CollectEditableFormatNodesInSelection(
|
||||
HTMLEditor& aHTMLEditor,
|
||||
HTMLEditor& aHTMLEditor, const dom::Element& aEditingHost,
|
||||
nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents);
|
||||
|
||||
RefPtr<nsAtom> mFirstParagraphState;
|
||||
|
@ -64,13 +64,23 @@ ListElementSelectionState::ListElementSelectionState(HTMLEditor& aHTMLEditor,
|
||||
return;
|
||||
}
|
||||
|
||||
Element* editingHostOrRoot = aHTMLEditor.ComputeEditingHost();
|
||||
if (!editingHostOrRoot) {
|
||||
// This is not a handler of editing command so that if there is no active
|
||||
// editing host, let's use the <body> or document element instead.
|
||||
editingHostOrRoot = aHTMLEditor.GetRoot();
|
||||
if (!editingHostOrRoot) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
|
||||
{
|
||||
AutoTArray<OwningNonNull<nsRange>, 4> extendedSelectionRanges;
|
||||
aHTMLEditor.GetSelectionRangesExtendedToHardLineStartAndEnd(
|
||||
extendedSelectionRanges, EditSubAction::eCreateOrChangeList);
|
||||
AutoRangeArray extendedSelectionRanges(aHTMLEditor.SelectionRef());
|
||||
extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
|
||||
EditSubAction::eCreateOrChangeList, *editingHostOrRoot);
|
||||
nsresult rv = aHTMLEditor.CollectEditTargetNodes(
|
||||
extendedSelectionRanges, arrayOfContents,
|
||||
extendedSelectionRanges.Ranges(), arrayOfContents,
|
||||
EditSubAction::eCreateOrChangeList,
|
||||
HTMLEditor::CollectNonEditableNodes::No);
|
||||
if (NS_FAILED(rv)) {
|
||||
@ -135,13 +145,23 @@ ListItemElementSelectionState::ListItemElementSelectionState(
|
||||
return;
|
||||
}
|
||||
|
||||
Element* editingHostOrRoot = aHTMLEditor.ComputeEditingHost();
|
||||
if (!editingHostOrRoot) {
|
||||
// This is not a handler of editing command so that if there is no active
|
||||
// editing host, let's use the <body> or document element instead.
|
||||
editingHostOrRoot = aHTMLEditor.GetRoot();
|
||||
if (!editingHostOrRoot) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
|
||||
{
|
||||
AutoTArray<OwningNonNull<nsRange>, 4> extendedSelectionRanges;
|
||||
aHTMLEditor.GetSelectionRangesExtendedToHardLineStartAndEnd(
|
||||
extendedSelectionRanges, EditSubAction::eCreateOrChangeList);
|
||||
AutoRangeArray extendedSelectionRanges(aHTMLEditor.SelectionRef());
|
||||
extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
|
||||
EditSubAction::eCreateOrChangeList, *editingHostOrRoot);
|
||||
nsresult rv = aHTMLEditor.CollectEditTargetNodes(
|
||||
extendedSelectionRanges, arrayOfContents,
|
||||
extendedSelectionRanges.Ranges(), arrayOfContents,
|
||||
EditSubAction::eCreateOrChangeList,
|
||||
HTMLEditor::CollectNonEditableNodes::No);
|
||||
if (NS_FAILED(rv)) {
|
||||
@ -274,13 +294,23 @@ AlignStateAtSelection::AlignStateAtSelection(HTMLEditor& aHTMLEditor,
|
||||
// ranges. `HTMLEditor` should have
|
||||
// `GetFirstSelectionRangeExtendedToHardLineStartAndEnd()`.
|
||||
else {
|
||||
AutoTArray<OwningNonNull<nsRange>, 4> arrayOfRanges;
|
||||
aHTMLEditor.GetSelectionRangesExtendedToHardLineStartAndEnd(
|
||||
arrayOfRanges, EditSubAction::eSetOrClearAlignment);
|
||||
Element* editingHostOrRoot = aHTMLEditor.ComputeEditingHost();
|
||||
if (!editingHostOrRoot) {
|
||||
// This is not a handler of editing command so that if there is no active
|
||||
// editing host, let's use the <body> or document element instead.
|
||||
editingHostOrRoot = aHTMLEditor.GetRoot();
|
||||
if (!editingHostOrRoot) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
AutoRangeArray extendedSelectionRanges(aHTMLEditor.SelectionRef());
|
||||
extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
|
||||
EditSubAction::eSetOrClearAlignment, *editingHostOrRoot);
|
||||
|
||||
AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
|
||||
nsresult rv = aHTMLEditor.CollectEditTargetNodes(
|
||||
arrayOfRanges, arrayOfContents, EditSubAction::eSetOrClearAlignment,
|
||||
extendedSelectionRanges.Ranges(), arrayOfContents,
|
||||
EditSubAction::eSetOrClearAlignment,
|
||||
HTMLEditor::CollectNonEditableNodes::Yes);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING(
|
||||
@ -440,9 +470,19 @@ ParagraphStateAtSelection::ParagraphStateAtSelection(HTMLEditor& aHTMLEditor,
|
||||
return;
|
||||
}
|
||||
|
||||
Element* editingHostOrRoot = aHTMLEditor.ComputeEditingHost();
|
||||
if (!editingHostOrRoot) {
|
||||
// This is not a handler of editing command so that if there is no active
|
||||
// editing host, let's use the <body> or document element instead.
|
||||
editingHostOrRoot = aHTMLEditor.GetRoot();
|
||||
if (!editingHostOrRoot) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
|
||||
nsresult rv =
|
||||
CollectEditableFormatNodesInSelection(aHTMLEditor, arrayOfContents);
|
||||
nsresult rv = CollectEditableFormatNodesInSelection(
|
||||
aHTMLEditor, *editingHostOrRoot, arrayOfContents);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING(
|
||||
"ParagraphStateAtSelection::CollectEditableFormatNodesInSelection() "
|
||||
@ -581,14 +621,14 @@ void ParagraphStateAtSelection::AppendDescendantFormatNodesAndFirstInlineNode(
|
||||
|
||||
// static
|
||||
nsresult ParagraphStateAtSelection::CollectEditableFormatNodesInSelection(
|
||||
HTMLEditor& aHTMLEditor,
|
||||
HTMLEditor& aHTMLEditor, const Element& aEditingHost,
|
||||
nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents) {
|
||||
{
|
||||
AutoTArray<OwningNonNull<nsRange>, 4> extendedSelectionRanges;
|
||||
aHTMLEditor.GetSelectionRangesExtendedToHardLineStartAndEnd(
|
||||
extendedSelectionRanges, EditSubAction::eCreateOrRemoveBlock);
|
||||
AutoRangeArray extendedSelectionRanges(aHTMLEditor.SelectionRef());
|
||||
extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
|
||||
EditSubAction::eCreateOrRemoveBlock, aEditingHost);
|
||||
nsresult rv = aHTMLEditor.CollectEditTargetNodes(
|
||||
extendedSelectionRanges, aArrayOfContents,
|
||||
extendedSelectionRanges.Ranges(), aArrayOfContents,
|
||||
EditSubAction::eCreateOrRemoveBlock,
|
||||
HTMLEditor::CollectNonEditableNodes::Yes);
|
||||
if (NS_FAILED(rv)) {
|
||||
|
@ -717,7 +717,7 @@ EditActionResult WhiteSpaceVisibilityKeeper::
|
||||
// static
|
||||
CreateElementResult WhiteSpaceVisibilityKeeper::InsertBRElement(
|
||||
HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToInsert,
|
||||
Element& aEditingHost) {
|
||||
const Element& aEditingHost) {
|
||||
if (MOZ_UNLIKELY(NS_WARN_IF(!aPointToInsert.IsSet()))) {
|
||||
return CreateElementResult(NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
@ -1471,7 +1471,7 @@ class WhiteSpaceVisibilityKeeper final {
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static CreateElementResult InsertBRElement(
|
||||
HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToInsert,
|
||||
Element& aEditingHost);
|
||||
const Element& aEditingHost);
|
||||
|
||||
/**
|
||||
* InsertText() inserts aStringToInsert to aPointToInsert and makes any needed
|
||||
|
@ -1510,6 +1510,18 @@ class Matrix4x4Typed {
|
||||
_44 = UnspecifiedNaN<T>();
|
||||
}
|
||||
|
||||
// Verifies that the matrix contains no Infs or NaNs
|
||||
bool IsFinite() const {
|
||||
return mozilla::IsFinite(_11) && mozilla::IsFinite(_12) &&
|
||||
mozilla::IsFinite(_13) && mozilla::IsFinite(_14) &&
|
||||
mozilla::IsFinite(_21) && mozilla::IsFinite(_22) &&
|
||||
mozilla::IsFinite(_23) && mozilla::IsFinite(_24) &&
|
||||
mozilla::IsFinite(_31) && mozilla::IsFinite(_32) &&
|
||||
mozilla::IsFinite(_33) && mozilla::IsFinite(_34) &&
|
||||
mozilla::IsFinite(_41) && mozilla::IsFinite(_42) &&
|
||||
mozilla::IsFinite(_43) && mozilla::IsFinite(_44);
|
||||
}
|
||||
|
||||
void SkewXY(double aXSkew, double aYSkew) {
|
||||
// XXX Is double precision really necessary here
|
||||
T tanX = SafeTangent(aXSkew);
|
||||
|
@ -1322,8 +1322,10 @@ HitTestingTreeNode* APZCTreeManager::PrepareNodeForLayer(
|
||||
// In such cases, go with the one that does not include the perspective
|
||||
// component; the perspective transform is remembered and applied to the
|
||||
// children instead.
|
||||
if (!aAncestorTransform.CombinedTransform().FuzzyEqualsMultiplicative(
|
||||
apzc->GetAncestorTransform())) {
|
||||
auto ancestorTransform = aAncestorTransform.CombinedTransform();
|
||||
auto existingAncestorTransform = apzc->GetAncestorTransform();
|
||||
if (!ancestorTransform.FuzzyEqualsMultiplicative(
|
||||
existingAncestorTransform)) {
|
||||
typedef TreeBuildingState::DeferredTransformMap::value_type PairType;
|
||||
if (!aAncestorTransform.ContainsPerspectiveTransform() &&
|
||||
!apzc->AncestorTransformContainsPerspective()) {
|
||||
@ -1333,9 +1335,16 @@ HitTestingTreeNode* APZCTreeManager::PrepareNodeForLayer(
|
||||
// cases, it's expected that different instances can have different
|
||||
// transforms, since each page renders a different part of the item.
|
||||
if (!aLayer.Metadata().IsPaginatedPresentation()) {
|
||||
MOZ_ASSERT(false,
|
||||
"Two layers that scroll together have different ancestor "
|
||||
"transforms");
|
||||
if (ancestorTransform.IsFinite() &&
|
||||
existingAncestorTransform.IsFinite()) {
|
||||
MOZ_ASSERT(
|
||||
false,
|
||||
"Two layers that scroll together have different ancestor "
|
||||
"transforms");
|
||||
} else {
|
||||
MOZ_ASSERT(ancestorTransform.IsFinite() ==
|
||||
existingAncestorTransform.IsFinite());
|
||||
}
|
||||
}
|
||||
} else if (!aAncestorTransform.ContainsPerspectiveTransform()) {
|
||||
aState.mPerspectiveTransformsDeferredToChildren.insert(
|
||||
|
@ -95,8 +95,9 @@ class DownscalingFilter final : public SurfaceFilter {
|
||||
|
||||
mInputSize = aConfig.mInputSize;
|
||||
gfx::IntSize outputSize = mNext.InputSize();
|
||||
mScale = gfxSize(double(mInputSize.width) / outputSize.width,
|
||||
double(mInputSize.height) / outputSize.height);
|
||||
mScale =
|
||||
gfx::MatrixScalesDouble(double(mInputSize.width) / outputSize.width,
|
||||
double(mInputSize.height) / outputSize.height);
|
||||
mHasAlpha = aConfig.mFormat == gfx::SurfaceFormat::OS_RGBA;
|
||||
|
||||
ReleaseWindow();
|
||||
@ -154,7 +155,7 @@ class DownscalingFilter final : public SurfaceFilter {
|
||||
|
||||
if (invalidRect) {
|
||||
// Compute the input space invalid rect by scaling.
|
||||
invalidRect->mInputSpaceRect.ScaleRoundOut(mScale.width, mScale.height);
|
||||
invalidRect->mInputSpaceRect.ScaleRoundOut(mScale.xScale, mScale.yScale);
|
||||
}
|
||||
|
||||
return invalidRect;
|
||||
@ -285,10 +286,10 @@ class DownscalingFilter final : public SurfaceFilter {
|
||||
|
||||
Next mNext; /// The next SurfaceFilter in the chain.
|
||||
|
||||
gfx::IntSize mInputSize; /// The size of the input image.
|
||||
gfxSize mScale; /// The scale factors in each dimension.
|
||||
/// Computed from @mInputSize and
|
||||
/// the next filter's input size.
|
||||
gfx::IntSize mInputSize; /// The size of the input image.
|
||||
gfx::MatrixScalesDouble mScale; /// The scale factors in each dimension.
|
||||
/// Computed from @mInputSize and
|
||||
/// the next filter's input size.
|
||||
|
||||
UniquePtr<uint8_t[]> mRowBuffer; /// The buffer into which input is written.
|
||||
UniquePtr<uint8_t*[]> mWindow; /// The last few rows which were written.
|
||||
|
@ -1210,39 +1210,6 @@ set_define(
|
||||
depends_if("--enable-wasm-type-reflections")(lambda x: True),
|
||||
)
|
||||
|
||||
# Support for WebAssembly exceptions
|
||||
# ==================================
|
||||
|
||||
|
||||
@depends("--enable-cranelift")
|
||||
def default_wasm_exceptions(cranelift_enabled):
|
||||
if cranelift_enabled:
|
||||
return
|
||||
|
||||
return True
|
||||
|
||||
|
||||
option(
|
||||
"--enable-wasm-exceptions",
|
||||
default=default_wasm_exceptions,
|
||||
help="{Enable|Disable} WebAssembly exceptions",
|
||||
)
|
||||
|
||||
|
||||
@depends("--enable-wasm-exceptions", "--enable-cranelift")
|
||||
def wasm_exceptions(value, cranelift_enabled):
|
||||
if not value:
|
||||
return
|
||||
|
||||
if cranelift_enabled:
|
||||
die("--enable-wasm-exceptions is not supported for --enable-cranelift")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
set_config("ENABLE_WASM_EXCEPTIONS", wasm_exceptions)
|
||||
set_define("ENABLE_WASM_EXCEPTIONS", wasm_exceptions)
|
||||
|
||||
# Wasi configuration
|
||||
# ===================================================
|
||||
|
||||
|
@ -265,24 +265,6 @@ enum JSWhyMagic {
|
||||
/** for local use */
|
||||
JS_GENERIC_MAGIC,
|
||||
|
||||
/**
|
||||
* Write records queued up in WritableStreamDefaultController.[[queue]] in the
|
||||
* spec are either "close" (a String) or Record { [[chunk]]: chunk }, where
|
||||
* chunk is an arbitrary user-provided (and therefore non-magic) value.
|
||||
* Represent "close" the String as this magic value; represent Record records
|
||||
* as the |chunk| value within each of them.
|
||||
*/
|
||||
JS_WRITABLESTREAM_CLOSE_RECORD,
|
||||
|
||||
/**
|
||||
* The ReadableStream pipe-to operation concludes with a "finalize" operation
|
||||
* that accepts an optional |error| argument. In certain cases that optional
|
||||
* |error| must be stored in a handler function, for use after a promise has
|
||||
* settled. We represent the argument not being provided, in those cases,
|
||||
* using this magic value.
|
||||
*/
|
||||
JS_READABLESTREAM_PIPETO_FINALIZE_WITHOUT_ERROR,
|
||||
|
||||
/**
|
||||
* When an error object is created without the error cause argument, we set
|
||||
* the error's cause slot to this magic value.
|
||||
|
@ -70,11 +70,6 @@
|
||||
#else
|
||||
# define WASM_EXTENDED_CONST_ENABLED 0
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_EXCEPTIONS
|
||||
# define WASM_EXCEPTIONS_ENABLED 1
|
||||
#else
|
||||
# define WASM_EXCEPTIONS_ENABLED 0
|
||||
#endif
|
||||
#ifdef ENABLE_WASM_FUNCTION_REFERENCES
|
||||
# define WASM_FUNCTION_REFERENCES_ENABLED 1
|
||||
#else
|
||||
@ -116,7 +111,7 @@
|
||||
TENTATIVE( \
|
||||
/* capitalized name */ Exceptions, \
|
||||
/* lower case name */ exceptions, \
|
||||
/* compile predicate */ WASM_EXCEPTIONS_ENABLED, \
|
||||
/* compile predicate */ true, \
|
||||
/* compiler predicate */ BaselineAvailable(cx) || IonAvailable(cx), \
|
||||
/* flag predicate */ !IsFuzzingCranelift(cx), \
|
||||
/* shell flag */ "exceptions", \
|
||||
|
@ -169,10 +169,7 @@ MSG_DEF(JSMSG_PRECISION_RANGE, 1, JSEXN_RANGEERR, "precision {0} out of
|
||||
|
||||
// Function
|
||||
MSG_DEF(JSMSG_BAD_APPLY_ARGS, 1, JSEXN_TYPEERR, "second argument to Function.prototype.{0} must be an array")
|
||||
MSG_DEF(JSMSG_BAD_FORMAL, 0, JSEXN_SYNTAXERR, "malformed formal parameter")
|
||||
MSG_DEF(JSMSG_CALLER_IS_STRICT, 0, JSEXN_TYPEERR, "access to strict mode caller function is censored")
|
||||
MSG_DEF(JSMSG_DEPRECATED_USAGE, 1, JSEXN_REFERENCEERR, "deprecated {0} usage")
|
||||
MSG_DEF(JSMSG_NOT_SCRIPTED_FUNCTION, 1, JSEXN_TYPEERR, "{0} is not a scripted function")
|
||||
MSG_DEF(JSMSG_NO_REST_NAME, 0, JSEXN_SYNTAXERR, "no parameter name after ...")
|
||||
MSG_DEF(JSMSG_PARAMETER_AFTER_REST, 0, JSEXN_SYNTAXERR, "parameter after rest parameter")
|
||||
MSG_DEF(JSMSG_TOO_MANY_ARGUMENTS, 0, JSEXN_RANGEERR, "too many arguments provided for a function call")
|
||||
@ -198,12 +195,10 @@ MSG_DEF(JSMSG_USER_DEFINED_ERROR, 0, JSEXN_ERR, "JS_ReportError was called"
|
||||
MSG_DEF(JSMSG_ALLOC_OVERFLOW, 0, JSEXN_INTERNALERR, "allocation size overflow")
|
||||
MSG_DEF(JSMSG_BAD_BYTECODE, 1, JSEXN_INTERNALERR, "unimplemented JavaScript bytecode {0}")
|
||||
MSG_DEF(JSMSG_BUFFER_TOO_SMALL, 0, JSEXN_INTERNALERR, "buffer too small")
|
||||
MSG_DEF(JSMSG_BUILD_ID_NOT_AVAILABLE, 0, JSEXN_INTERNALERR, "build ID is not available")
|
||||
MSG_DEF(JSMSG_BYTECODE_TOO_BIG, 2, JSEXN_INTERNALERR, "bytecode {0} too large (limit {1})")
|
||||
MSG_DEF(JSMSG_NEED_DIET, 1, JSEXN_INTERNALERR, "{0} too large")
|
||||
MSG_DEF(JSMSG_OUT_OF_MEMORY, 0, JSEXN_INTERNALERR, "out of memory")
|
||||
MSG_DEF(JSMSG_OVER_RECURSED, 0, JSEXN_INTERNALERR, "too much recursion")
|
||||
MSG_DEF(JSMSG_TOO_BIG_TO_ENCODE, 0, JSEXN_INTERNALERR, "data are to big to encode")
|
||||
MSG_DEF(JSMSG_TOO_DEEP, 1, JSEXN_INTERNALERR, "{0} nested too deeply")
|
||||
MSG_DEF(JSMSG_UNCAUGHT_EXCEPTION, 1, JSEXN_INTERNALERR, "uncaught exception: {0}")
|
||||
MSG_DEF(JSMSG_UNKNOWN_FORMAT, 1, JSEXN_INTERNALERR, "unknown bytecode format {0}")
|
||||
@ -220,7 +215,6 @@ MSG_DEF(JSMSG_AWAIT_OUTSIDE_ASYNC, 0, JSEXN_SYNTAXERR, "await is only valid
|
||||
MSG_DEF(JSMSG_AWAIT_OUTSIDE_ASYNC_OR_MODULE, 0, JSEXN_SYNTAXERR, "await is only valid in async functions, async generators and modules")
|
||||
MSG_DEF(JSMSG_TOP_LEVEL_AWAIT_NOT_SUPPORTED, 0, JSEXN_SYNTAXERR, "top level await is not supported in this context")
|
||||
MSG_DEF(JSMSG_BAD_ARROW_ARGS, 0, JSEXN_SYNTAXERR, "invalid arrow-function arguments (parentheses around the arrow-function may help)")
|
||||
MSG_DEF(JSMSG_BAD_BINDING, 1, JSEXN_SYNTAXERR, "redefining {0} is deprecated")
|
||||
MSG_DEF(JSMSG_BAD_COALESCE_MIXING, 0, JSEXN_SYNTAXERR, "cannot use `??` unparenthesized within `||` and `&&` expressions")
|
||||
MSG_DEF(JSMSG_BAD_CONST_DECL, 0, JSEXN_SYNTAXERR, "missing = in const declaration")
|
||||
MSG_DEF(JSMSG_BAD_CONTINUE, 0, JSEXN_SYNTAXERR, "continue must be inside loop")
|
||||
@ -229,7 +223,6 @@ MSG_DEF(JSMSG_BAD_DESTRUCT_TARGET, 0, JSEXN_SYNTAXERR, "invalid destructurin
|
||||
MSG_DEF(JSMSG_BAD_DESTRUCT_PARENS, 0, JSEXN_SYNTAXERR, "destructuring patterns in assignments can't be parenthesized")
|
||||
MSG_DEF(JSMSG_BAD_DESTRUCT_DECL, 0, JSEXN_SYNTAXERR, "missing = in destructuring declaration")
|
||||
MSG_DEF(JSMSG_BAD_DUP_ARGS, 0, JSEXN_SYNTAXERR, "duplicate argument names not allowed in this context")
|
||||
MSG_DEF(JSMSG_BAD_FOR_EACH_LOOP, 0, JSEXN_SYNTAXERR, "invalid for each loop")
|
||||
MSG_DEF(JSMSG_BAD_FOR_LEFTSIDE, 0, JSEXN_SYNTAXERR, "invalid for-in/of left-hand side")
|
||||
MSG_DEF(JSMSG_LEXICAL_DECL_DEFINES_LET,0, JSEXN_SYNTAXERR, "a lexical declaration can't define a 'let' binding")
|
||||
MSG_DEF(JSMSG_BAD_STARTING_FOROF_LHS, 1, JSEXN_SYNTAXERR, "an expression X in 'for (X of Y)' must not start with '{0}'")
|
||||
@ -252,7 +245,6 @@ MSG_DEF(JSMSG_BAD_ARGUMENTS, 0, JSEXN_SYNTAXERR, "arguments is not val
|
||||
MSG_DEF(JSMSG_BRACKET_AFTER_LIST, 0, JSEXN_SYNTAXERR, "missing ] after element list")
|
||||
MSG_DEF(JSMSG_BRACKET_IN_INDEX, 0, JSEXN_SYNTAXERR, "missing ] in index expression")
|
||||
MSG_DEF(JSMSG_BRACKET_OPENED, 2, JSEXN_NOTE, "[ opened at line {0}, column {1}")
|
||||
MSG_DEF(JSMSG_CATCH_AFTER_GENERAL, 0, JSEXN_SYNTAXERR, "catch after unconditional catch")
|
||||
MSG_DEF(JSMSG_CATCH_IDENTIFIER, 0, JSEXN_SYNTAXERR, "missing identifier in catch")
|
||||
MSG_DEF(JSMSG_CATCH_OR_FINALLY, 0, JSEXN_SYNTAXERR, "missing catch or finally after try")
|
||||
MSG_DEF(JSMSG_CATCH_WITHOUT_TRY, 0, JSEXN_SYNTAXERR, "catch without try")
|
||||
@ -285,7 +277,6 @@ MSG_DEF(JSMSG_DUPLICATE_FORMAL, 1, JSEXN_SYNTAXERR, "duplicate formal arg
|
||||
MSG_DEF(JSMSG_DUPLICATE_LABEL, 0, JSEXN_SYNTAXERR, "duplicate label")
|
||||
MSG_DEF(JSMSG_DUPLICATE_PROPERTY, 1, JSEXN_SYNTAXERR, "property name {0} appears more than once in object literal")
|
||||
MSG_DEF(JSMSG_DUPLICATE_PROTO_PROPERTY, 0, JSEXN_SYNTAXERR, "property name __proto__ appears more than once in object literal")
|
||||
MSG_DEF(JSMSG_EMPTY_CONSEQUENT, 0, JSEXN_SYNTAXERR, "mistyped ; after conditional?")
|
||||
MSG_DEF(JSMSG_EQUAL_AS_ASSIGN, 0, JSEXN_SYNTAXERR, "test for equality (==) mistyped as assignment (=)?")
|
||||
MSG_DEF(JSMSG_EXPORT_DECL_AT_TOP_LEVEL,0, JSEXN_SYNTAXERR, "export declarations may only appear at top level of a module")
|
||||
MSG_DEF(JSMSG_FINALLY_WITHOUT_TRY, 0, JSEXN_SYNTAXERR, "finally without try")
|
||||
@ -312,7 +303,6 @@ MSG_DEF(JSMSG_GENERATOR_LABEL, 0, JSEXN_SYNTAXERR, "generator functions
|
||||
MSG_DEF(JSMSG_FUNCTION_LABEL, 0, JSEXN_SYNTAXERR, "functions cannot be labelled")
|
||||
MSG_DEF(JSMSG_SLOPPY_FUNCTION_LABEL, 0, JSEXN_SYNTAXERR, "functions can only be labelled inside blocks")
|
||||
MSG_DEF(JSMSG_LINE_BREAK_AFTER_THROW, 0, JSEXN_SYNTAXERR, "no line break is allowed between 'throw' and its expression")
|
||||
MSG_DEF(JSMSG_LINE_BREAK_BEFORE_ARROW, 0, JSEXN_SYNTAXERR, "no line break is allowed before '=>'")
|
||||
MSG_DEF(JSMSG_MALFORMED_ESCAPE, 1, JSEXN_SYNTAXERR, "malformed {0} character escape sequence")
|
||||
MSG_DEF(JSMSG_MISSING_BINARY_DIGITS, 0, JSEXN_SYNTAXERR, "missing binary digits after '0b'")
|
||||
MSG_DEF(JSMSG_MISSING_EXPONENT, 0, JSEXN_SYNTAXERR, "missing exponent")
|
||||
@ -329,14 +319,12 @@ MSG_DEF(JSMSG_NO_BINDING_NAME, 0, JSEXN_SYNTAXERR, "missing binding name"
|
||||
MSG_DEF(JSMSG_NO_EXPORT_NAME, 0, JSEXN_SYNTAXERR, "missing export name")
|
||||
MSG_DEF(JSMSG_NO_IMPORT_NAME, 0, JSEXN_SYNTAXERR, "missing import name")
|
||||
MSG_DEF(JSMSG_NO_VARIABLE_NAME, 0, JSEXN_SYNTAXERR, "missing variable name")
|
||||
MSG_DEF(JSMSG_OF_AFTER_FOR_NAME, 0, JSEXN_SYNTAXERR, "missing 'of' after for")
|
||||
MSG_DEF(JSMSG_PAREN_AFTER_ARGS, 0, JSEXN_SYNTAXERR, "missing ) after argument list")
|
||||
MSG_DEF(JSMSG_PAREN_AFTER_CATCH, 0, JSEXN_SYNTAXERR, "missing ) after catch")
|
||||
MSG_DEF(JSMSG_PAREN_AFTER_COND, 0, JSEXN_SYNTAXERR, "missing ) after condition")
|
||||
MSG_DEF(JSMSG_PAREN_AFTER_FOR, 0, JSEXN_SYNTAXERR, "missing ( after for")
|
||||
MSG_DEF(JSMSG_PAREN_AFTER_FORMAL, 0, JSEXN_SYNTAXERR, "missing ) after formal parameters")
|
||||
MSG_DEF(JSMSG_PAREN_AFTER_FOR_CTRL, 0, JSEXN_SYNTAXERR, "missing ) after for-loop control")
|
||||
MSG_DEF(JSMSG_PAREN_AFTER_FOR_OF_ITERABLE, 0, JSEXN_SYNTAXERR, "missing ) after for-of iterable")
|
||||
MSG_DEF(JSMSG_PAREN_AFTER_SWITCH, 0, JSEXN_SYNTAXERR, "missing ) after switch expression")
|
||||
MSG_DEF(JSMSG_PAREN_AFTER_WITH, 0, JSEXN_SYNTAXERR, "missing ) after with-statement object")
|
||||
MSG_DEF(JSMSG_PAREN_BEFORE_CATCH, 0, JSEXN_SYNTAXERR, "missing ( before catch")
|
||||
@ -347,13 +335,10 @@ MSG_DEF(JSMSG_PAREN_BEFORE_WITH, 0, JSEXN_SYNTAXERR, "missing ( before wit
|
||||
MSG_DEF(JSMSG_PAREN_IN_PAREN, 0, JSEXN_SYNTAXERR, "missing ) in parenthetical")
|
||||
MSG_DEF(JSMSG_RC_AFTER_EXPORT_SPEC_LIST, 0, JSEXN_SYNTAXERR, "missing '}' after export specifier list")
|
||||
MSG_DEF(JSMSG_RC_AFTER_IMPORT_SPEC_LIST, 0, JSEXN_SYNTAXERR, "missing '}' after module specifier list")
|
||||
MSG_DEF(JSMSG_REDECLARED_CATCH_IDENTIFIER, 1, JSEXN_SYNTAXERR, "redeclaration of identifier '{0}' in catch")
|
||||
MSG_DEF(JSMSG_RESERVED_ID, 1, JSEXN_SYNTAXERR, "{0} is a reserved identifier")
|
||||
MSG_DEF(JSMSG_REST_WITH_COMMA, 0, JSEXN_SYNTAXERR, "rest element may not have a trailing comma")
|
||||
MSG_DEF(JSMSG_REST_WITH_DEFAULT, 0, JSEXN_SYNTAXERR, "rest parameter may not have a default")
|
||||
MSG_DEF(JSMSG_SELFHOSTED_TOP_LEVEL_LEXICAL, 1, JSEXN_SYNTAXERR, "self-hosted code cannot contain top-level {0} declarations")
|
||||
MSG_DEF(JSMSG_SELFHOSTED_METHOD_CALL, 0, JSEXN_SYNTAXERR, "self-hosted code may not contain direct method calls. Use callFunction() or callContentFunction()")
|
||||
MSG_DEF(JSMSG_SELFHOSTED_UNBOUND_NAME, 0, JSEXN_TYPEERR, "self-hosted code may not contain unbound name lookups")
|
||||
MSG_DEF(JSMSG_SEMI_AFTER_FOR_COND, 0, JSEXN_SYNTAXERR, "missing ; after for-loop condition")
|
||||
MSG_DEF(JSMSG_SEMI_AFTER_FOR_INIT, 0, JSEXN_SYNTAXERR, "missing ; after for-loop initializer")
|
||||
MSG_DEF(JSMSG_SOURCE_TOO_LONG, 0, JSEXN_RANGEERR, "source is too long")
|
||||
@ -362,7 +347,6 @@ MSG_DEF(JSMSG_STRICT_CODE_WITH, 0, JSEXN_SYNTAXERR, "strict mode code may
|
||||
MSG_DEF(JSMSG_STRICT_NON_SIMPLE_PARAMS, 1, JSEXN_SYNTAXERR, "\"use strict\" not allowed in function with {0} parameter")
|
||||
MSG_DEF(JSMSG_TEMPLSTR_UNTERM_EXPR, 0, JSEXN_SYNTAXERR, "missing } in template string")
|
||||
MSG_DEF(JSMSG_TOO_MANY_CASES, 0, JSEXN_INTERNALERR, "too many switch cases")
|
||||
MSG_DEF(JSMSG_TOO_MANY_CATCH_VARS, 0, JSEXN_SYNTAXERR, "too many catch variables")
|
||||
MSG_DEF(JSMSG_TOO_MANY_CON_ARGS, 0, JSEXN_SYNTAXERR, "too many constructor arguments")
|
||||
MSG_DEF(JSMSG_TOO_MANY_DEFAULTS, 0, JSEXN_SYNTAXERR, "more than one switch default")
|
||||
MSG_DEF(JSMSG_TOO_MANY_FUN_ARGS, 0, JSEXN_SYNTAXERR, "too many function arguments")
|
||||
@ -388,14 +372,9 @@ MSG_DEF(JSMSG_YIELD_IN_PARAMETER, 0, JSEXN_SYNTAXERR, "yield expression can
|
||||
MSG_DEF(JSMSG_YIELD_OUTSIDE_GENERATOR, 0, JSEXN_SYNTAXERR, "yield expression is only valid in generators")
|
||||
MSG_DEF(JSMSG_BAD_COLUMN_NUMBER, 0, JSEXN_RANGEERR, "column number out of range")
|
||||
MSG_DEF(JSMSG_BAD_LINE_NUMBER, 0, JSEXN_RANGEERR, "line number out of range")
|
||||
MSG_DEF(JSMSG_COMPUTED_NAME_IN_PATTERN,0, JSEXN_SYNTAXERR, "computed property names aren't supported in this destructuring declaration")
|
||||
MSG_DEF(JSMSG_DEFAULT_IN_PATTERN, 0, JSEXN_SYNTAXERR, "destructuring defaults aren't supported in this destructuring declaration")
|
||||
MSG_DEF(JSMSG_BAD_NEWTARGET, 0, JSEXN_SYNTAXERR, "new.target only allowed within functions")
|
||||
MSG_DEF(JSMSG_BAD_NEW_OPTIONAL, 0, JSEXN_SYNTAXERR, "new keyword cannot be used with an optional chain")
|
||||
MSG_DEF(JSMSG_BAD_OPTIONAL_TEMPLATE, 0, JSEXN_SYNTAXERR, "tagged template cannot be used with optional chain")
|
||||
MSG_DEF(JSMSG_ESCAPED_KEYWORD, 0, JSEXN_SYNTAXERR, "keywords must be written literally, without embedded escapes")
|
||||
MSG_DEF(JSMSG_PRIVATE_FIELDS_NOT_SUPPORTED, 0, JSEXN_SYNTAXERR, "private fields are not currently supported")
|
||||
MSG_DEF(JSMSG_CLASS_STATIC_NOT_SUPPORTED, 0, JSEXN_SYNTAXERR, "class static blocks are not currently supported")
|
||||
MSG_DEF(JSMSG_IMPORT_ASSERTIONS_NOT_SUPPORTED, 0, JSEXN_SYNTAXERR, "import assertions are not currently supported")
|
||||
MSG_DEF(JSMSG_ILLEGAL_PRIVATE_FIELD, 0, JSEXN_SYNTAXERR, "private fields aren't valid in this context")
|
||||
MSG_DEF(JSMSG_ILLEGAL_PRIVATE_NAME, 0, JSEXN_SYNTAXERR, "private names aren't valid in this context")
|
||||
@ -431,7 +410,6 @@ MSG_DEF(JSMSG_WASM_VERBOSE, 1, JSEXN_WARN, "WebAssembly verbose: {
|
||||
MSG_DEF(JSMSG_WASM_COMPILE_WARNING, 1, JSEXN_WARN, "WebAssembly module validated with warning: {0}")
|
||||
MSG_DEF(JSMSG_WASM_HUGE_MEMORY_FAILED, 0, JSEXN_WARN, "WebAssembly.Memory failed to reserve a large virtual memory region. This may be due to low configured virtual memory limits on this system.")
|
||||
MSG_DEF(JSMSG_WASM_COMPILE_ERROR, 1, JSEXN_WASMCOMPILEERROR, "{0}")
|
||||
MSG_DEF(JSMSG_WASM_NO_SHMEM_COMPILE, 0, JSEXN_WASMCOMPILEERROR, "shared memory is disabled")
|
||||
MSG_DEF(JSMSG_WASM_BAD_IMPORT_TYPE, 2, JSEXN_WASMLINKERROR, "import object field '{0}' is not a {1}")
|
||||
MSG_DEF(JSMSG_WASM_BAD_IMPORT_SIG, 2, JSEXN_WASMLINKERROR, "imported function '{0}.{1}' signature mismatch")
|
||||
MSG_DEF(JSMSG_WASM_BAD_TAG_SIG, 2, JSEXN_WASMLINKERROR, "imported tag '{0}.{1}' signature mismatch")
|
||||
@ -459,7 +437,7 @@ MSG_DEF(JSMSG_WASM_BAD_CAST, 0, JSEXN_WASMRUNTIMEERROR, "bad cast")
|
||||
MSG_DEF(JSMSG_WASM_MEM_IMP_LIMIT, 0, JSEXN_WASMRUNTIMEERROR, "too many memory pages")
|
||||
MSG_DEF(JSMSG_WASM_TABLE_IMP_LIMIT, 0, JSEXN_WASMRUNTIMEERROR, "too many table elements")
|
||||
MSG_DEF(JSMSG_WASM_ARRAY_IMP_LIMIT, 0, JSEXN_WASMRUNTIMEERROR, "too many array elements")
|
||||
MSG_DEF(JSMSG_WASM_BAD_RANGE , 2, JSEXN_RANGEERR, "bad {0} {1}")
|
||||
MSG_DEF(JSMSG_WASM_BAD_RANGE, 2, JSEXN_RANGEERR, "bad {0} {1}")
|
||||
MSG_DEF(JSMSG_WASM_BAD_GROW, 1, JSEXN_RANGEERR, "failed to grow {0}")
|
||||
MSG_DEF(JSMSG_WASM_TABLE_OUT_OF_BOUNDS, 0, JSEXN_WASMRUNTIMEERROR, "table index out of bounds")
|
||||
MSG_DEF(JSMSG_WASM_BAD_UINT32, 2, JSEXN_TYPEERR, "bad {0} {1}")
|
||||
@ -481,23 +459,19 @@ MSG_DEF(JSMSG_WASM_BAD_EXN_PAYLOAD_LEN, 2, JSEXN_TYPEERR, "expected {0} value
|
||||
MSG_DEF(JSMSG_WASM_BAD_EXN_TAG, 0, JSEXN_TYPEERR, "exception's tag did not match the provided exception tag")
|
||||
MSG_DEF(JSMSG_WASM_BAD_EXN_OPTIONS, 0, JSEXN_TYPEERR, "argument cannot be converted to an ExceptionOptions")
|
||||
MSG_DEF(JSMSG_WASM_BAD_FUNCTION_VALUE, 0, JSEXN_TYPEERR, "second argument must be a function")
|
||||
MSG_DEF(JSMSG_WASM_BAD_ARG_TYPE, 0, JSEXN_TYPEERR, "parameters and results must be an iterator of value types")
|
||||
MSG_DEF(JSMSG_WASM_NO_TRANSFER, 0, JSEXN_TYPEERR, "cannot transfer WebAssembly/asm.js ArrayBuffer")
|
||||
MSG_DEF(JSMSG_WASM_TEXT_FAIL, 1, JSEXN_SYNTAXERR, "wasm text error: {0}")
|
||||
MSG_DEF(JSMSG_WASM_MISSING_MAXIMUM, 0, JSEXN_TYPEERR, "'shared' is true but maximum is not specified")
|
||||
MSG_DEF(JSMSG_WASM_GLOBAL_IMMUTABLE, 0, JSEXN_TYPEERR, "can't set value of immutable global")
|
||||
MSG_DEF(JSMSG_WASM_WRONG_NUMBER_OF_VALUES, 2, JSEXN_TYPEERR, "wrong number of values returned by JavaScript to WebAssembly (expected {0}, got {1})")
|
||||
MSG_DEF(JSMSG_WASM_NONSHARED_WAIT , 0, JSEXN_WASMRUNTIMEERROR, "atomic wait on non-shared memory")
|
||||
MSG_DEF(JSMSG_WASM_NULL_REQUIRED, 0, JSEXN_TYPEERR, "nullref requires a null value")
|
||||
MSG_DEF(JSMSG_WASM_NONSHARED_WAIT, 0, JSEXN_WASMRUNTIMEERROR, "atomic wait on non-shared memory")
|
||||
MSG_DEF(JSMSG_WASM_SUPPLY_ONLY_ONE, 2, JSEXN_TYPEERR, "exactly one of {0} and {1} must be supplied")
|
||||
MSG_DEF(JSMSG_WASM_MISSING_REQUIRED, 1, JSEXN_TYPEERR, "Missing required argument {0}")
|
||||
|
||||
// Proxy
|
||||
MSG_DEF(JSMSG_BAD_TRAP_RETURN_VALUE, 2, JSEXN_TYPEERR,"trap {1} for {0} returned a primitive value")
|
||||
MSG_DEF(JSMSG_BAD_GETPROTOTYPEOF_TRAP_RETURN,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler returned a non-object, non-null value")
|
||||
MSG_DEF(JSMSG_INCONSISTENT_GETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler didn't return the target object's prototype")
|
||||
MSG_DEF(JSMSG_PROXY_SETPROTOTYPEOF_RETURNED_FALSE, 0, JSEXN_TYPEERR, "proxy setPrototypeOf handler returned false")
|
||||
MSG_DEF(JSMSG_PROXY_ISEXTENSIBLE_RETURNED_FALSE,0,JSEXN_TYPEERR,"proxy isExtensible handler must return the same extensibility as target")
|
||||
MSG_DEF(JSMSG_INCONSISTENT_SETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy setPrototypeOf handler returned true, even though the target's prototype is immutable because the target is non-extensible")
|
||||
MSG_DEF(JSMSG_CANT_CHANGE_EXTENSIBILITY, 0, JSEXN_TYPEERR, "can't change object's extensibility")
|
||||
MSG_DEF(JSMSG_CANT_DEFINE_INVALID, 2, JSEXN_TYPEERR, "proxy can't define an incompatible property descriptor ('{0}', {1})")
|
||||
@ -527,7 +501,6 @@ MSG_DEF(JSMSG_PROXY_CONSTRUCT_OBJECT, 0, JSEXN_TYPEERR, "proxy [[Construct]] mu
|
||||
MSG_DEF(JSMSG_PROXY_EXTENSIBILITY, 0, JSEXN_TYPEERR, "proxy must report same extensiblitity as target")
|
||||
MSG_DEF(JSMSG_PROXY_GETOWN_OBJORUNDEF, 1, JSEXN_TYPEERR, "proxy [[GetOwnProperty]] must return an object or undefined for property '{0}'")
|
||||
MSG_DEF(JSMSG_PROXY_REVOKED, 0, JSEXN_TYPEERR, "illegal operation attempted on a revoked proxy")
|
||||
MSG_DEF(JSMSG_PROXY_ARG_REVOKED, 1, JSEXN_TYPEERR, "argument {0} cannot be a revoked proxy")
|
||||
MSG_DEF(JSMSG_BAD_TRAP, 1, JSEXN_TYPEERR, "proxy handler's {0} trap wasn't undefined, null, or callable")
|
||||
|
||||
// Structured cloning
|
||||
@ -550,7 +523,6 @@ MSG_DEF(JSMSG_DEBUG_BAD_OFFSET, 0, JSEXN_TYPEERR, "invalid script offset"
|
||||
MSG_DEF(JSMSG_DEBUG_BAD_REFERENT, 2, JSEXN_TYPEERR, "{0} does not refer to {1}")
|
||||
MSG_DEF(JSMSG_DEBUG_BAD_RESUMPTION, 0, JSEXN_TYPEERR, "debugger resumption value must be undefined, {throw: val}, {return: val}, or null")
|
||||
MSG_DEF(JSMSG_DEBUG_RESUMPTION_CONFLICT, 0, JSEXN_TYPEERR, "debugger hook returned a resumption, but an earlier hook already did")
|
||||
MSG_DEF(JSMSG_DEBUG_BAD_YIELD, 0, JSEXN_TYPEERR, "generator yielded invalid value")
|
||||
MSG_DEF(JSMSG_DEBUG_CANT_DEBUG_GLOBAL, 0, JSEXN_TYPEERR, "passing non-debuggable global to addDebuggee")
|
||||
MSG_DEF(JSMSG_DEBUG_SAME_COMPARTMENT, 0, JSEXN_TYPEERR, "debugger and debuggee must be in different compartments")
|
||||
MSG_DEF(JSMSG_DEBUG_CCW_REQUIRED, 1, JSEXN_TYPEERR, "{0}: argument must be an object from a different compartment")
|
||||
@ -583,7 +555,6 @@ MSG_DEF(JSMSG_DEBUG_PROMISE_NOT_RESOLVED, 0, JSEXN_TYPEERR, "Promise hasn't been
|
||||
MSG_DEF(JSMSG_DEBUG_PROMISE_NOT_FULFILLED, 0, JSEXN_TYPEERR, "Promise hasn't been fulfilled")
|
||||
MSG_DEF(JSMSG_DEBUG_PROMISE_NOT_REJECTED, 0, JSEXN_TYPEERR, "Promise hasn't been rejected")
|
||||
MSG_DEF(JSMSG_DEBUG_NO_BINARY_SOURCE, 0, JSEXN_ERR, "WebAssembly binary source is not available")
|
||||
MSG_DEF(JSMSG_DEBUG_NO_PRIVATE_METHOD, 0, JSEXN_ERR, "private method calls aren't available in this context")
|
||||
|
||||
// Testing functions
|
||||
MSG_DEF(JSMSG_TESTING_SCRIPTS_ONLY, 0, JSEXN_TYPEERR, "only works on scripts")
|
||||
@ -599,7 +570,6 @@ MSG_DEF(JSMSG_INTERNAL_INTL_ERROR, 0, JSEXN_ERR, "internal error while compu
|
||||
MSG_DEF(JSMSG_INVALID_CURRENCY_CODE, 1, JSEXN_RANGEERR, "invalid currency code in NumberFormat(): {0}")
|
||||
MSG_DEF(JSMSG_INVALID_UNIT_IDENTIFIER, 1, JSEXN_RANGEERR, "invalid unit identifier in NumberFormat(): {0}")
|
||||
MSG_DEF(JSMSG_INVALID_DIGITS_VALUE, 1, JSEXN_RANGEERR, "invalid digits value: {0}")
|
||||
MSG_DEF(JSMSG_INVALID_KEYS_TYPE, 0, JSEXN_TYPEERR, "calendar info keys must be an object or undefined")
|
||||
MSG_DEF(JSMSG_INVALID_KEY, 1, JSEXN_RANGEERR, "invalid key: {0}")
|
||||
MSG_DEF(JSMSG_INVALID_LANGUAGE_TAG, 1, JSEXN_RANGEERR, "invalid language tag: {0}")
|
||||
MSG_DEF(JSMSG_INVALID_LOCALES_ELEMENT, 0, JSEXN_TYPEERR, "invalid element in locales argument")
|
||||
@ -665,7 +635,6 @@ MSG_DEF(JSMSG_NON_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected ArrayBuffer
|
||||
MSG_DEF(JSMSG_SAME_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected different ArrayBuffer, but species constructor returned same ArrayBuffer")
|
||||
MSG_DEF(JSMSG_SHORT_ARRAY_BUFFER_RETURNED, 2, JSEXN_TYPEERR, "expected ArrayBuffer with at least {0} bytes, but species constructor returns ArrayBuffer with {1} bytes")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS, 0, JSEXN_TYPEERR, "invalid arguments")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG,1, JSEXN_RANGEERR, "argument {0} must be >= 0")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_DETACHED, 0, JSEXN_TYPEERR, "attempting to access detached ArrayBuffer")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_BOUNDS, 2, JSEXN_RANGEERR, "start offset of {0}Array should be a multiple of {1}")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_MISALIGNED, 2, JSEXN_RANGEERR, "buffer length for {0}Array should be a multiple of {1}")
|
||||
@ -696,7 +665,6 @@ MSG_DEF(JSMSG_SYMBOL_TO_NUMBER, 0, JSEXN_TYPEERR, "can't convert symbol t
|
||||
|
||||
// Atomics and futexes
|
||||
MSG_DEF(JSMSG_ATOMICS_BAD_ARRAY, 0, JSEXN_TYPEERR, "invalid array type for the operation")
|
||||
MSG_DEF(JSMSG_ATOMICS_TOO_LONG, 0, JSEXN_RANGEERR, "timeout value too large")
|
||||
MSG_DEF(JSMSG_ATOMICS_WAIT_NOT_ALLOWED, 0, JSEXN_TYPEERR, "waiting is not allowed on this thread")
|
||||
|
||||
// XPConnect wrappers and DOM bindings
|
||||
@ -716,12 +684,10 @@ MSG_DEF(JSMSG_CANT_DELETE_SUPER, 0, JSEXN_REFERENCEERR, "invalid delete involvin
|
||||
MSG_DEF(JSMSG_REINIT_THIS, 0, JSEXN_REFERENCEERR, "super() called twice in derived class constructor")
|
||||
|
||||
// Modules
|
||||
MSG_DEF(JSMSG_BAD_DEFAULT_EXPORT, 0, JSEXN_SYNTAXERR, "default export cannot be provided by export *")
|
||||
MSG_DEF(JSMSG_MISSING_INDIRECT_EXPORT, 0, JSEXN_SYNTAXERR, "indirect export not found")
|
||||
MSG_DEF(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, 0, JSEXN_SYNTAXERR, "ambiguous indirect export")
|
||||
MSG_DEF(JSMSG_MISSING_IMPORT, 0, JSEXN_SYNTAXERR, "import not found")
|
||||
MSG_DEF(JSMSG_AMBIGUOUS_IMPORT, 0, JSEXN_SYNTAXERR, "ambiguous import")
|
||||
MSG_DEF(JSMSG_MISSING_NAMESPACE_EXPORT, 0, JSEXN_SYNTAXERR, "export not found for namespace")
|
||||
MSG_DEF(JSMSG_MISSING_EXPORT, 1, JSEXN_SYNTAXERR, "local binding for export '{0}' not found")
|
||||
MSG_DEF(JSMSG_BAD_MODULE_STATUS, 0, JSEXN_INTERNALERR, "module record has unexpected status")
|
||||
MSG_DEF(JSMSG_DYNAMIC_IMPORT_FAILED, 0, JSEXN_TYPEERR, "error loading dynamically imported module")
|
||||
@ -760,39 +726,17 @@ MSG_DEF(JSMSG_READABLESTREAM_BYTES_TYPE_NOT_IMPLEMENTED, 0, JSEXN_RANGEERR,"supp
|
||||
MSG_DEF(JSMSG_READABLESTREAM_BYOB_READER_FOR_NON_BYTE_STREAM,0,JSEXN_TYPEERR,"can't get a BYOB reader for a non-byte stream")
|
||||
MSG_DEF(JSMSG_READABLESTREAM_INVALID_READER_MODE, 0, JSEXN_TYPEERR,"'mode' must be \"byob\" or undefined.")
|
||||
MSG_DEF(JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, 1, JSEXN_RANGEERR, "'{0}' must be a finite, non-negative number.")
|
||||
MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_BYTESWRITTEN, 0, JSEXN_RANGEERR, "'bytesWritten' exceeds remaining length.")
|
||||
MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_SIZE, 0, JSEXN_RANGEERR, "view size does not match requested data.")
|
||||
MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_OFFSET, 0, JSEXN_RANGEERR, "view offset does not match requested position.")
|
||||
MSG_DEF(JSMSG_READABLESTREAM_LOCKED_METHOD, 1, JSEXN_TYPEERR, "'{0}' can't be called on a locked stream.")
|
||||
MSG_DEF(JSMSG_READABLESTREAM_LOCKED, 0, JSEXN_TYPEERR, "A Reader may only be created for an unlocked ReadableStream.")
|
||||
MSG_DEF(JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER, 1, JSEXN_TYPEERR, "{0} requires a ReadableByteStreamController.")
|
||||
MSG_DEF(JSMSG_READABLESTREAM_NOT_DEFAULT_CONTROLLER, 1, JSEXN_TYPEERR, "{0} requires a ReadableStreamDefaultController.")
|
||||
MSG_DEF(JSMSG_READABLESTREAM_CONTROLLER_SET, 0, JSEXN_TYPEERR, "The ReadableStream already has a controller defined.")
|
||||
MSG_DEF(JSMSG_READABLESTREAMREADER_NOT_OWNED, 1, JSEXN_TYPEERR, "The ReadableStream reader method '{0}' may only be called on a reader owned by a stream.")
|
||||
MSG_DEF(JSMSG_READABLESTREAMREADER_NOT_EMPTY, 1, JSEXN_TYPEERR, "The ReadableStream reader method '{0}' may not be called on a reader with read requests.")
|
||||
MSG_DEF(JSMSG_READABLESTREAMBYOBREADER_READ_EMPTY_VIEW, 0, JSEXN_TYPEERR, "ReadableStreamBYOBReader.read() was passed an empty TypedArrayBuffer view.")
|
||||
MSG_DEF(JSMSG_READABLESTREAMREADER_RELEASED, 0, JSEXN_TYPEERR, "The ReadableStream reader was released.")
|
||||
MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_CLOSED, 1, JSEXN_TYPEERR, "'{0}' called on a stream already closing.")
|
||||
MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, 1, JSEXN_TYPEERR, "'{0}' may only be called on a stream in the 'readable' state.")
|
||||
MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNKSIZE,0, JSEXN_RANGEERR, "ReadableByteStreamController requires a positive integer or undefined for 'autoAllocateChunkSize'.")
|
||||
MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK, 1, JSEXN_TYPEERR, "{0} passed a bad chunk.")
|
||||
MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL, 0, JSEXN_TYPEERR, "The ReadableByteStreamController cannot be closed while the buffer is being filled.")
|
||||
MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, 1, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method '{0}' called on a request with no controller.")
|
||||
MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_RESPOND_CLOSED, 0, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method 'respond' called with non-zero number of bytes with a closed controller.")
|
||||
MSG_DEF(JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, 1, JSEXN_TYPEERR, "ReadableStream method {0} not yet implemented")
|
||||
MSG_DEF(JSMSG_READABLESTREAM_PIPETO_BAD_SIGNAL, 0, JSEXN_TYPEERR, "signal must be either undefined or an AbortSignal")
|
||||
|
||||
// WritableStream
|
||||
MSG_DEF(JSMSG_READABLESTREAM_UNDERLYINGSINK_TYPE_WRONG, 0, JSEXN_RANGEERR,"'underlyingSink.type' must be undefined.")
|
||||
MSG_DEF(JSMSG_WRITABLESTREAMWRITER_NOT_OWNED, 1, JSEXN_TYPEERR, "the WritableStream writer method '{0}' may only be called on a writer owned by a stream")
|
||||
MSG_DEF(JSMSG_WRITABLESTREAM_CLOSED_OR_ERRORED, 0, JSEXN_TYPEERR, "writable stream is already closed or errored")
|
||||
MSG_DEF(JSMSG_WRITABLESTREAM_RELEASED_DURING_WRITE, 0, JSEXN_TYPEERR, "writer's lock on the stream was released before writing completed")
|
||||
MSG_DEF(JSMSG_WRITABLESTREAM_WRITE_CLOSING_OR_CLOSED, 0, JSEXN_TYPEERR, "can't write to a stream that's currently closing or already closed")
|
||||
MSG_DEF(JSMSG_CANT_USE_LOCKED_WRITABLESTREAM, 1, JSEXN_TYPEERR, "can't {0} a WritableStream that's locked to a writer")
|
||||
MSG_DEF(JSMSG_WRITABLESTREAM_CLOSE_CLOSING_OR_CLOSED, 0, JSEXN_TYPEERR, "can't close a stream that's currently closing or already closed")
|
||||
MSG_DEF(JSMSG_WRITABLESTREAM_CANT_RELEASE_ALREADY_CLOSED,0, JSEXN_TYPEERR, "writer has already been released and can't be closed")
|
||||
MSG_DEF(JSMSG_WRITABLESTREAM_ALREADY_LOCKED, 0, JSEXN_TYPEERR, "writable stream is already locked by another writer")
|
||||
MSG_DEF(JSMSG_READABLESTREAM_NYI, 0, JSEXN_ERR, "full WritableStream support is not yet implemented")
|
||||
|
||||
// Other Stream-related
|
||||
MSG_DEF(JSMSG_STREAM_MISSING_HIGHWATERMARK, 0, JSEXN_TYPEERR, "'highWaterMark' must not be undefined.")
|
||||
@ -815,15 +759,11 @@ MSG_DEF(JSMSG_BIGINT_DIVISION_BY_ZERO, 0, JSEXN_RANGEERR, "BigInt division by ze
|
||||
MSG_DEF(JSMSG_BIGINT_NEGATIVE_EXPONENT, 0, JSEXN_RANGEERR, "BigInt negative exponent")
|
||||
MSG_DEF(JSMSG_BIGINT_INVALID_SYNTAX, 0, JSEXN_SYNTAXERR, "invalid BigInt syntax")
|
||||
MSG_DEF(JSMSG_BIGINT_NOT_SERIALIZABLE, 0, JSEXN_TYPEERR, "BigInt value can't be serialized in JSON")
|
||||
MSG_DEF(JSMSG_SC_BIGINT_DISABLED, 0, JSEXN_ERR, "BigInt not cloned - feature disabled in receiver")
|
||||
|
||||
// FinalizationRegistry
|
||||
MSG_DEF(JSMSG_NOT_A_FINALIZATION_REGISTRY, 1, JSEXN_TYPEERR, "{0} is not a FinalizationRegistry")
|
||||
MSG_DEF(JSMSG_NOT_A_FINALIZATION_ITERATOR, 1, JSEXN_TYPEERR, "{0} is not a FinalizationRegistryCleanupIterator")
|
||||
MSG_DEF(JSMSG_BAD_HELD_VALUE, 0, JSEXN_TYPEERR, "The heldValue parameter passed to FinalizationRegistry.register must not be the same as the target parameter")
|
||||
MSG_DEF(JSMSG_BAD_UNREGISTER_TOKEN, 1, JSEXN_TYPEERR, "Invalid unregister token passed to {0}")
|
||||
MSG_DEF(JSMSG_STALE_FINALIZATION_REGISTRY_ITERATOR, 0, JSEXN_TYPEERR, "Can't use stale finalization registry iterator")
|
||||
MSG_DEF(JSMSG_BAD_CLEANUP_STATE, 0, JSEXN_TYPEERR, "Can't call FinalizeRegistry.cleanupSome while cleanup is in progress")
|
||||
MSG_DEF(JSMSG_BAD_FINALIZATION_REGISTRY_OBJECT, 0, JSEXN_TYPEERR, "cannot register the given object with a FinalizationRegistry")
|
||||
|
||||
// WeakRef
|
||||
@ -837,12 +777,8 @@ MSG_DEF(JSMSG_NEGATIVE_LIMIT, 0, JSEXN_RANGEERR, "Ite
|
||||
MSG_DEF(JSMSG_RECORD_TUPLE_NO_OBJECT, 0, JSEXN_TYPEERR, "Record and Tuple can only contain primitive values")
|
||||
MSG_DEF(JSMSG_RECORD_NO_PROTO, 0, JSEXN_SYNTAXERR, "__proto__ is not a valid literal key in records")
|
||||
MSG_DEF(JSMSG_RECORD_NO_SYMBOL_KEY, 0, JSEXN_TYPEERR, "Symbols cannot be used as record keys")
|
||||
MSG_DEF(JSMSG_RECORD_INVALID_DESCRIPTOR, 0, JSEXN_TYPEERR, "Invalid record property descriptor")
|
||||
MSG_DEF(JSMSG_BAD_TUPLE_LENGTH, 0, JSEXN_TYPEERR, "attempted to create excessively long tuple")
|
||||
MSG_DEF(JSMSG_BAD_TUPLE_INDEX, 0, JSEXN_RANGEERR, "index out of range for tuple")
|
||||
MSG_DEF(JSMSG_BAD_TUPLE_OBJECT, 0, JSEXN_TYPEERR, "value of TupleObject must be a Tuple")
|
||||
MSG_DEF(JSMSG_EMPTY_TUPLE_REDUCE, 0, JSEXN_TYPEERR, "reduce of empty tuple with no initial values")
|
||||
MSG_DEF(JSMSG_RECORD_TUPLE_TO_NUMBER, 0, JSEXN_TYPEERR, "can't convert Record or Tuple to number")
|
||||
MSG_DEF(JSMSG_BAD_RECORD_TUPLE_WEAKMAP_KEY, 0, JSEXN_TYPEERR, "a Record or Tuple WeakMap key must contain a Box")
|
||||
|
||||
//clang-format on
|
||||
|
@ -1354,8 +1354,6 @@ class NativeObject : public JSObject {
|
||||
inline void ensureDenseInitializedLength(uint32_t index, uint32_t extra);
|
||||
|
||||
void setDenseElement(uint32_t index, const Value& val) {
|
||||
// Note: Streams code can call this for the internal ListObject type with
|
||||
// MagicValue(JS_WRITABLESTREAM_CLOSE_RECORD).
|
||||
MOZ_ASSERT_IF(val.isMagic(), val.whyMagic() != JS_ELEMENTS_HOLE);
|
||||
setDenseElementUnchecked(index, val);
|
||||
}
|
||||
|
@ -814,9 +814,7 @@ CoderResult CodeMetadataTier(Coder<mode>& coder,
|
||||
MOZ_TRY((CodeVector<mode, FuncExport, CodeFuncExport<mode>>(
|
||||
coder, &item->funcExports)));
|
||||
MOZ_TRY(CodeStackMaps(coder, &item->stackMaps, codeStart));
|
||||
#ifdef ENABLE_WASM_EXCEPTIONS
|
||||
MOZ_TRY(CodePodVector(coder, &item->tryNotes));
|
||||
#endif
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@ -838,9 +836,7 @@ CoderResult CodeMetadata(Coder<mode>& coder,
|
||||
MOZ_TRY((CodeVector<mode, GlobalDesc, &CodeGlobalDesc<mode>>(
|
||||
coder, &item->globals)));
|
||||
MOZ_TRY(CodePodVector(coder, &item->tables));
|
||||
#ifdef ENABLE_WASM_EXCEPTIONS
|
||||
MOZ_TRY((CodeVector<mode, TagDesc, &CodeTagDesc<mode>>(coder, &item->tags)));
|
||||
#endif
|
||||
MOZ_TRY(CodePod(coder, &item->moduleName));
|
||||
MOZ_TRY(CodePodVector(coder, &item->funcNames));
|
||||
MOZ_TRY(CodeCacheableChars(coder, &item->filename));
|
||||
|
@ -31,6 +31,8 @@
|
||||
#include "mozilla/dom/DOMException.h"
|
||||
#include "mozilla/dom/Exceptions.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/glean/bindings/Glean.h"
|
||||
#include "mozilla/glean/bindings/GleanPings.h"
|
||||
#include "mozilla/ScriptPreloader.h"
|
||||
|
||||
#include "nsDOMMutationObserver.h"
|
||||
|
@ -5462,7 +5462,7 @@ PresShell::CanvasBackground PresShell::ComputeCanvasBackground() const {
|
||||
return {GetDefaultBackgroundColorToDraw(), false};
|
||||
}
|
||||
|
||||
ComputedStyle* bgStyle =
|
||||
const ComputedStyle* bgStyle =
|
||||
nsCSSRendering::FindRootFrameBackground(rootStyleFrame);
|
||||
// XXX We should really be passing the canvasframe, not the root element
|
||||
// style frame but we don't have access to the canvasframe here. It isn't
|
||||
|
22
layout/base/crashtests/1771503.html
Normal file
22
layout/base/crashtests/1771503.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
* {
|
||||
transform: matrix3d(130, -7052, 1000, 35803, 122, 7197, 197, 126, 201, 64, 38, -69, 5.727476671737168, 124, 22882, 168.04863081346616);
|
||||
overflow: auto clip ! important;
|
||||
padding-right: 39%;
|
||||
min-inline-size: min-content;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table>
|
||||
<th>
|
||||
<fieldset>
|
||||
<textarea cols='4096' autofocus></textarea>
|
||||
</fieldset>
|
||||
</th>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
@ -568,3 +568,4 @@ pref(layout.accessiblecaret.enabled,true) load 1746989.html
|
||||
load 1752649.html
|
||||
load 1753779.html
|
||||
pref(layout.css.backdrop-filter.enabled,true) load 1755790.html
|
||||
load 1771503.html
|
||||
|
@ -6868,15 +6868,16 @@ nsTransparencyMode nsLayoutUtils::GetFrameTransparency(
|
||||
return eTransparencyOpaque;
|
||||
}
|
||||
|
||||
ComputedStyle* bgSC;
|
||||
if (!nsCSSRendering::FindBackground(aBackgroundFrame, &bgSC)) {
|
||||
ComputedStyle* bgSC = nsCSSRendering::FindBackground(aBackgroundFrame);
|
||||
if (!bgSC) {
|
||||
return eTransparencyTransparent;
|
||||
}
|
||||
const nsStyleBackground* bg = bgSC->StyleBackground();
|
||||
if (NS_GET_A(bg->BackgroundColor(bgSC)) < 255 ||
|
||||
// bottom layer's clip is used for the color
|
||||
bg->BottomLayer().mClip != StyleGeometryBox::BorderBox)
|
||||
bg->BottomLayer().mClip != StyleGeometryBox::BorderBox) {
|
||||
return eTransparencyTransparent;
|
||||
}
|
||||
return eTransparencyOpaque;
|
||||
}
|
||||
|
||||
@ -8923,14 +8924,11 @@ ScrollMetadata nsLayoutUtils::ComputeScrollMetadata(
|
||||
if (isRootScrollFrame) {
|
||||
metadata.SetBackgroundColor(
|
||||
sRGBColor::FromABGR(presShell->GetCanvasBackground()));
|
||||
} else {
|
||||
ComputedStyle* backgroundStyle;
|
||||
if (nsCSSRendering::FindBackground(aScrollFrame, &backgroundStyle)) {
|
||||
nscolor backgroundColor =
|
||||
backgroundStyle->StyleBackground()->BackgroundColor(
|
||||
backgroundStyle);
|
||||
metadata.SetBackgroundColor(sRGBColor::FromABGR(backgroundColor));
|
||||
}
|
||||
} else if (const auto* backgroundStyle =
|
||||
nsCSSRendering::FindBackground(aScrollFrame)) {
|
||||
nscolor backgroundColor =
|
||||
backgroundStyle->StyleBackground()->BackgroundColor(backgroundStyle);
|
||||
metadata.SetBackgroundColor(sRGBColor::FromABGR(backgroundColor));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,7 +237,7 @@ static nscolor GetBackplateColor(nsIFrame* aFrame) {
|
||||
// colors with the non-native theme, and native system colors should also
|
||||
// match the native theme), then we're alright and we should compute an
|
||||
// appropriate backplate color.
|
||||
auto* style = frame->Style();
|
||||
const auto* style = frame->Style();
|
||||
if (style->StyleBackground()->IsTransparent(style)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -461,11 +461,13 @@ void nsCanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
||||
ComputedStyle* bg = nullptr;
|
||||
nsIFrame* dependentFrame = nullptr;
|
||||
bool isThemed = IsThemed();
|
||||
if (!isThemed &&
|
||||
nsCSSRendering::FindBackgroundFrame(this, &dependentFrame)) {
|
||||
bg = dependentFrame->Style();
|
||||
if (dependentFrame == this) {
|
||||
dependentFrame = nullptr;
|
||||
if (!isThemed) {
|
||||
dependentFrame = nsCSSRendering::FindBackgroundFrame(this);
|
||||
if (dependentFrame) {
|
||||
bg = dependentFrame->Style();
|
||||
if (dependentFrame == this) {
|
||||
dependentFrame = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5773,7 +5773,11 @@ void nsIFrame::DisassociateImage(const StyleImage& aImage) {
|
||||
StyleImageRendering nsIFrame::UsedImageRendering() const {
|
||||
ComputedStyle* style;
|
||||
if (nsCSSRendering::IsCanvasFrame(this)) {
|
||||
nsCSSRendering::FindBackground(this, &style);
|
||||
// XXXdholbert Maybe we should use FindCanvasBackground here (instead of
|
||||
// FindBackground), since we're inside an IsCanvasFrame check? Though then
|
||||
// we'd also have to copypaste or abstract-away the multi-part root-frame
|
||||
// lookup that the canvas-flavored API requires.
|
||||
style = nsCSSRendering::FindBackground(this);
|
||||
} else {
|
||||
style = Style();
|
||||
}
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
/* utility functions for drawing borders and backgrounds */
|
||||
|
||||
#include "nsCSSRendering.h"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
#include "gfx2DGlue.h"
|
||||
@ -41,7 +43,6 @@
|
||||
#include "nsIScrollableFrame.h"
|
||||
#include "imgIContainer.h"
|
||||
#include "ImageOps.h"
|
||||
#include "nsCSSRendering.h"
|
||||
#include "nsCSSColorUtils.h"
|
||||
#include "nsITheme.h"
|
||||
#include "nsLayoutUtils.h"
|
||||
@ -1137,8 +1138,8 @@ auto nsCSSRendering::FindNonTransparentBackgroundFrame(nsIFrame* aFrame,
|
||||
}
|
||||
|
||||
if (IsCanvasFrame(frame)) {
|
||||
nsIFrame* bgFrame = nullptr;
|
||||
if (FindBackgroundFrame(frame, &bgFrame) &&
|
||||
nsIFrame* bgFrame = FindBackgroundFrame(frame);
|
||||
if (bgFrame &&
|
||||
NS_GET_A(bgFrame->StyleBackground()->BackgroundColor(bgFrame))) {
|
||||
return {bgFrame, false, true};
|
||||
}
|
||||
@ -1223,16 +1224,23 @@ nsIFrame* nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame) {
|
||||
* + we don't paint the background on the BODY element in *some* cases,
|
||||
* and for SGML-based HTML documents only.
|
||||
*
|
||||
* |FindBackground| returns true if a background should be painted, and
|
||||
* the resulting ComputedStyle to use for the background information
|
||||
* will be filled in to |aBackground|.
|
||||
* |FindBackground| checks whether a background should be painted. If yes, it
|
||||
* returns the resulting ComputedStyle to use for the background information;
|
||||
* Otherwise, it returns nullptr.
|
||||
*/
|
||||
ComputedStyle* nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame) {
|
||||
return FindBackgroundStyleFrame(aForFrame)->Style();
|
||||
}
|
||||
|
||||
inline bool FindElementBackground(const nsIFrame* aForFrame,
|
||||
nsIFrame* aRootElementFrame) {
|
||||
// Helper for FindBackgroundFrame. Returns true if aForFrame has a meaningful
|
||||
// background that it should draw (i.e. that it hasn't propagated to another
|
||||
// frame). See documentation for FindBackground.
|
||||
inline bool FrameHasMeaningfulBackground(const nsIFrame* aForFrame,
|
||||
nsIFrame* aRootElementFrame) {
|
||||
MOZ_ASSERT(!nsCSSRendering::IsCanvasFrame(aForFrame),
|
||||
"FindBackgroundFrame handles canvas frames before calling us, "
|
||||
"so we don't need to consider them here");
|
||||
|
||||
if (aForFrame == aRootElementFrame) {
|
||||
// We must have propagated our background to the viewport or canvas. Abort.
|
||||
return false;
|
||||
@ -1272,27 +1280,25 @@ inline bool FindElementBackground(const nsIFrame* aForFrame,
|
||||
return !htmlBG->IsTransparent(aRootElementFrame);
|
||||
}
|
||||
|
||||
bool nsCSSRendering::FindBackgroundFrame(const nsIFrame* aForFrame,
|
||||
nsIFrame** aBackgroundFrame) {
|
||||
nsIFrame* nsCSSRendering::FindBackgroundFrame(const nsIFrame* aForFrame) {
|
||||
nsIFrame* rootElementFrame =
|
||||
aForFrame->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
|
||||
if (IsCanvasFrame(aForFrame)) {
|
||||
*aBackgroundFrame = FindCanvasBackgroundFrame(aForFrame, rootElementFrame);
|
||||
return true;
|
||||
return FindCanvasBackgroundFrame(aForFrame, rootElementFrame);
|
||||
}
|
||||
|
||||
*aBackgroundFrame = const_cast<nsIFrame*>(aForFrame);
|
||||
return FindElementBackground(aForFrame, rootElementFrame);
|
||||
if (FrameHasMeaningfulBackground(aForFrame, rootElementFrame)) {
|
||||
return const_cast<nsIFrame*>(aForFrame);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool nsCSSRendering::FindBackground(const nsIFrame* aForFrame,
|
||||
ComputedStyle** aBackgroundSC) {
|
||||
nsIFrame* backgroundFrame = nullptr;
|
||||
if (FindBackgroundFrame(aForFrame, &backgroundFrame)) {
|
||||
*aBackgroundSC = backgroundFrame->Style();
|
||||
return true;
|
||||
ComputedStyle* nsCSSRendering::FindBackground(const nsIFrame* aForFrame) {
|
||||
if (auto* backgroundFrame = FindBackgroundFrame(aForFrame)) {
|
||||
return backgroundFrame->Style();
|
||||
}
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void nsCSSRendering::BeginFrameTreesLocked() { ++gFrameTreeLockCount; }
|
||||
@ -1790,8 +1796,8 @@ ImgDrawResult nsCSSRendering::PaintStyleImageLayer(const PaintBGParams& aParams,
|
||||
MOZ_ASSERT(aParams.frame,
|
||||
"Frame is expected to be provided to PaintStyleImageLayer");
|
||||
|
||||
ComputedStyle* sc;
|
||||
if (!FindBackground(aParams.frame, &sc)) {
|
||||
const ComputedStyle* sc = FindBackground(aParams.frame);
|
||||
if (!sc) {
|
||||
// We don't want to bail out if moz-appearance is set on a root
|
||||
// node. If it has a parent content node, bail because it's not
|
||||
// a root, otherwise keep going in order to let the theme stuff
|
||||
@ -1878,8 +1884,8 @@ ImgDrawResult nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer(
|
||||
"Frame is expected to be provided to "
|
||||
"BuildWebRenderDisplayItemsForStyleImageLayer");
|
||||
|
||||
ComputedStyle* sc;
|
||||
if (!FindBackground(aParams.frame, &sc)) {
|
||||
ComputedStyle* sc = FindBackground(aParams.frame);
|
||||
if (!sc) {
|
||||
// We don't want to bail out if moz-appearance is set on a root
|
||||
// node. If it has a parent content node, bail because it's not
|
||||
// a root, otherwise keep going in order to let the theme stuff
|
||||
@ -2287,7 +2293,8 @@ static Maybe<nscolor> CalcScrollbarColor(nsIFrame* aFrame,
|
||||
return Some(color.CalcColor(*scrollbarStyle));
|
||||
}
|
||||
|
||||
static nscolor GetBackgroundColor(nsIFrame* aFrame, ComputedStyle* aStyle) {
|
||||
static nscolor GetBackgroundColor(nsIFrame* aFrame,
|
||||
const ComputedStyle* aStyle) {
|
||||
switch (aStyle->StyleDisplay()->EffectiveAppearance()) {
|
||||
case StyleAppearance::ScrollbarthumbVertical:
|
||||
case StyleAppearance::ScrollbarthumbHorizontal: {
|
||||
@ -2313,7 +2320,7 @@ static nscolor GetBackgroundColor(nsIFrame* aFrame, ComputedStyle* aStyle) {
|
||||
}
|
||||
|
||||
nscolor nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext,
|
||||
ComputedStyle* aStyle,
|
||||
const ComputedStyle* aStyle,
|
||||
nsIFrame* aFrame,
|
||||
bool& aDrawBackgroundImage,
|
||||
bool& aDrawBackgroundColor) {
|
||||
@ -2380,7 +2387,7 @@ static CompositionOp DetermineCompositionOp(
|
||||
|
||||
ImgDrawResult nsCSSRendering::PaintStyleImageLayerWithSC(
|
||||
const PaintBGParams& aParams, gfxContext& aRenderingCtx,
|
||||
ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) {
|
||||
const ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) {
|
||||
MOZ_ASSERT(aParams.frame,
|
||||
"Frame is expected to be provided to PaintStyleImageLayerWithSC");
|
||||
|
||||
|
@ -283,15 +283,16 @@ struct nsCSSRendering {
|
||||
static bool IsCanvasFrame(const nsIFrame* aFrame);
|
||||
|
||||
/**
|
||||
* Fill in an aBackgroundSC to be used to paint the background
|
||||
* for an element. This applies the rules for propagating
|
||||
* backgrounds between BODY, the root element, and the canvas.
|
||||
* @return true if there is some meaningful background.
|
||||
* Returns the ComputedStyle to be used to paint the background for the given
|
||||
* frame, if its element has a meaningful background. This applies the rules
|
||||
* for propagating backgrounds between BODY, the root element, and the
|
||||
* canvas.
|
||||
*
|
||||
* @return the ComputedStyle (if any) to be used for painting aForFrame's
|
||||
* background.
|
||||
*/
|
||||
static bool FindBackground(const nsIFrame* aForFrame,
|
||||
mozilla::ComputedStyle** aBackgroundSC);
|
||||
static bool FindBackgroundFrame(const nsIFrame* aForFrame,
|
||||
nsIFrame** aBackgroundFrame);
|
||||
static mozilla::ComputedStyle* FindBackground(const nsIFrame* aForFrame);
|
||||
static nsIFrame* FindBackgroundFrame(const nsIFrame* aForFrame);
|
||||
|
||||
/**
|
||||
* As FindBackground, but the passed-in frame is known to be a root frame
|
||||
@ -348,7 +349,7 @@ struct nsCSSRendering {
|
||||
* Determine the background color to draw taking into account print settings.
|
||||
*/
|
||||
static nscolor DetermineBackgroundColor(nsPresContext* aPresContext,
|
||||
mozilla::ComputedStyle* aStyle,
|
||||
const mozilla::ComputedStyle* aStyle,
|
||||
nsIFrame* aFrame,
|
||||
bool& aDrawBackgroundImage,
|
||||
bool& aDrawBackgroundColor);
|
||||
@ -502,7 +503,8 @@ struct nsCSSRendering {
|
||||
*/
|
||||
static ImgDrawResult PaintStyleImageLayerWithSC(
|
||||
const PaintBGParams& aParams, gfxContext& aRenderingCtx,
|
||||
mozilla::ComputedStyle* mBackgroundSC, const nsStyleBorder& aBorder);
|
||||
const mozilla::ComputedStyle* aBackgroundSC,
|
||||
const nsStyleBorder& aBorder);
|
||||
|
||||
static bool CanBuildWebRenderDisplayItemsForStyleImageLayer(
|
||||
WebRenderLayerManager* aManager, nsPresContext& aPresCtx,
|
||||
|
@ -2885,7 +2885,7 @@ static Maybe<nsRect> GetViewportRectRelativeToReferenceFrame(
|
||||
nsDisplayBackgroundImage::GetInitData(nsDisplayListBuilder* aBuilder,
|
||||
nsIFrame* aFrame, uint16_t aLayer,
|
||||
const nsRect& aBackgroundRect,
|
||||
ComputedStyle* aBackgroundStyle) {
|
||||
const ComputedStyle* aBackgroundStyle) {
|
||||
nsPresContext* presContext = aFrame->PresContext();
|
||||
uint32_t flags = aBuilder->GetBackgroundPaintFlags();
|
||||
const nsStyleImageLayers::Layer& layer =
|
||||
@ -2957,8 +2957,8 @@ nsDisplayBackgroundImage::~nsDisplayBackgroundImage() {
|
||||
}
|
||||
|
||||
static nsIFrame* GetBackgroundComputedStyleFrame(nsIFrame* aFrame) {
|
||||
nsIFrame* f;
|
||||
if (!nsCSSRendering::FindBackgroundFrame(aFrame, &f)) {
|
||||
nsIFrame* f = nsCSSRendering::FindBackgroundFrame(aFrame);
|
||||
if (!f) {
|
||||
// We don't want to bail out if moz-appearance is set on a root
|
||||
// node. If it has a parent content node, bail because it's not
|
||||
// a root, other wise keep going in order to let the theme stuff
|
||||
@ -3085,7 +3085,7 @@ static nsDisplayThemedBackground* CreateThemedBackground(
|
||||
|
||||
static nsDisplayBackgroundColor* CreateBackgroundColor(
|
||||
nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame,
|
||||
nsRect& aBgRect, ComputedStyle* aBgSC, nscolor aColor) {
|
||||
nsRect& aBgRect, const ComputedStyle* aBgSC, nscolor aColor) {
|
||||
if (aSecondaryFrame) {
|
||||
const uint16_t index = static_cast<uint16_t>(GetTableTypeFromFrame(aFrame));
|
||||
return MakeDisplayItemWithIndex<nsDisplayTableBackgroundColor>(
|
||||
@ -3104,7 +3104,7 @@ AppendedBackgroundType nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
|
||||
const nsRect& aBackgroundOriginRect, nsIFrame* aSecondaryReferenceFrame,
|
||||
Maybe<nsDisplayListBuilder::AutoBuildingDisplayList>*
|
||||
aAutoBuildingDisplayList) {
|
||||
ComputedStyle* bgSC = aComputedStyle;
|
||||
const ComputedStyle* bgSC = aComputedStyle;
|
||||
const nsStyleBackground* bg = nullptr;
|
||||
nsRect bgRect = aBackgroundRect;
|
||||
nsRect bgOriginRect = bgRect;
|
||||
|
@ -4117,7 +4117,7 @@ class nsDisplayBackgroundImage : public nsPaintedDisplayItem {
|
||||
public:
|
||||
struct InitData {
|
||||
nsDisplayListBuilder* builder;
|
||||
ComputedStyle* backgroundStyle;
|
||||
const ComputedStyle* backgroundStyle;
|
||||
nsCOMPtr<imgIContainer> image;
|
||||
nsRect backgroundRect;
|
||||
nsRect fillArea;
|
||||
@ -4136,7 +4136,7 @@ class nsDisplayBackgroundImage : public nsPaintedDisplayItem {
|
||||
*/
|
||||
static InitData GetInitData(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
|
||||
uint16_t aLayer, const nsRect& aBackgroundRect,
|
||||
ComputedStyle* aBackgroundStyle);
|
||||
const ComputedStyle* aBackgroundStyle);
|
||||
|
||||
explicit nsDisplayBackgroundImage(nsDisplayListBuilder* aBuilder,
|
||||
nsIFrame* aFrame, const InitData& aInitData,
|
||||
@ -4251,7 +4251,7 @@ class nsDisplayBackgroundImage : public nsPaintedDisplayItem {
|
||||
|
||||
// Cache the result of nsCSSRendering::FindBackground. Always null if
|
||||
// mIsThemed is true or if FindBackground returned false.
|
||||
RefPtr<ComputedStyle> mBackgroundStyle;
|
||||
RefPtr<const ComputedStyle> mBackgroundStyle;
|
||||
nsCOMPtr<imgIContainer> mImage;
|
||||
nsIFrame* mDependentFrame;
|
||||
nsRect mBackgroundRect; // relative to the reference frame
|
||||
|
@ -2127,7 +2127,7 @@ bool nsStyleBackground::IsTransparent(const nsIFrame* aFrame) const {
|
||||
return IsTransparent(aFrame->Style());
|
||||
}
|
||||
|
||||
bool nsStyleBackground::IsTransparent(mozilla::ComputedStyle* aStyle) const {
|
||||
bool nsStyleBackground::IsTransparent(const ComputedStyle* aStyle) const {
|
||||
return BottomLayer().mImage.IsNone() && mImage.mImageCount == 1 &&
|
||||
NS_GET_A(BackgroundColor(aStyle)) == 0;
|
||||
}
|
||||
|
@ -369,7 +369,7 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleBackground {
|
||||
|
||||
// True if this background is completely transparent.
|
||||
bool IsTransparent(const nsIFrame* aFrame) const;
|
||||
bool IsTransparent(mozilla::ComputedStyle* aStyle) const;
|
||||
bool IsTransparent(const mozilla::ComputedStyle* aStyle) const;
|
||||
|
||||
// We have to take slower codepaths for fixed background attachment,
|
||||
// but we don't want to do that when there's no image.
|
||||
|
@ -491,7 +491,7 @@ using PaintFramesParams = SVGIntegrationUtils::PaintFramesParams;
|
||||
*/
|
||||
static bool PaintMaskSurface(const PaintFramesParams& aParams,
|
||||
DrawTarget* aMaskDT, float aOpacity,
|
||||
ComputedStyle* aSC,
|
||||
const ComputedStyle* aSC,
|
||||
const nsTArray<SVGMaskFrame*>& aMaskFrames,
|
||||
const nsPoint& aOffsetToUserSpace) {
|
||||
MOZ_ASSERT(aMaskFrames.Length() > 0);
|
||||
@ -568,7 +568,7 @@ struct MaskPaintResult {
|
||||
};
|
||||
|
||||
static MaskPaintResult CreateAndPaintMaskSurface(
|
||||
const PaintFramesParams& aParams, float aOpacity, ComputedStyle* aSC,
|
||||
const PaintFramesParams& aParams, float aOpacity, const ComputedStyle* aSC,
|
||||
const nsTArray<SVGMaskFrame*>& aMaskFrames,
|
||||
const nsPoint& aOffsetToUserSpace) {
|
||||
const nsStyleSVGReset* svgReset = aSC->StyleSVGReset();
|
||||
|
@ -7499,9 +7499,13 @@ nsRect nsDisplayTableItem::GetBounds(nsDisplayListBuilder* aBuilder,
|
||||
}
|
||||
|
||||
void nsDisplayTableItem::UpdateForFrameBackground(nsIFrame* aFrame) {
|
||||
ComputedStyle* bgSC;
|
||||
if (!nsCSSRendering::FindBackground(aFrame, &bgSC)) return;
|
||||
if (!bgSC->StyleBackground()->HasFixedBackground(aFrame)) return;
|
||||
ComputedStyle* bgSC = nsCSSRendering::FindBackground(aFrame);
|
||||
if (!bgSC) {
|
||||
return;
|
||||
}
|
||||
if (!bgSC->StyleBackground()->HasFixedBackground(aFrame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPartHasFixedBackground = true;
|
||||
}
|
||||
|
@ -52,13 +52,24 @@ public class ExampleCrashHandler extends Service {
|
||||
|
||||
String id = createNotificationChannel();
|
||||
|
||||
int intentFlag = 0;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
intentFlag = PendingIntent.FLAG_IMMUTABLE;
|
||||
}
|
||||
|
||||
PendingIntent reportIntent =
|
||||
PendingIntent.getService(
|
||||
this, 0, new Intent(ACTION_REPORT_CRASH, null, this, ExampleCrashHandler.class), 0);
|
||||
this,
|
||||
0,
|
||||
new Intent(ACTION_REPORT_CRASH, null, this, ExampleCrashHandler.class),
|
||||
intentFlag);
|
||||
|
||||
PendingIntent dismissIntent =
|
||||
PendingIntent.getService(
|
||||
this, 0, new Intent(ACTION_DISMISS, null, this, ExampleCrashHandler.class), 0);
|
||||
this,
|
||||
0,
|
||||
new Intent(ACTION_DISMISS, null, this, ExampleCrashHandler.class),
|
||||
intentFlag);
|
||||
|
||||
Notification notification =
|
||||
new NotificationCompat.Builder(this, id)
|
||||
@ -66,8 +77,8 @@ public class ExampleCrashHandler extends Service {
|
||||
.setContentTitle(getResources().getString(R.string.crashed_title))
|
||||
.setContentText(getResources().getString(R.string.crashed_text))
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.setContentIntent(reportIntent)
|
||||
.addAction(0, getResources().getString(R.string.crashed_ignore), dismissIntent)
|
||||
.addAction(0, getResources().getString(R.string.crashed_report), reportIntent)
|
||||
.setAutoCancel(true)
|
||||
.setOngoing(false)
|
||||
.build();
|
||||
|
@ -812,7 +812,8 @@ public class GeckoViewActivity extends AppCompatActivity
|
||||
Intent clickIntent = new Intent(GeckoViewActivity.this, GeckoViewActivity.class);
|
||||
clickIntent.putExtra("onClick", notification);
|
||||
PendingIntent dismissIntent =
|
||||
PendingIntent.getActivity(GeckoViewActivity.this, mLastID, clickIntent, 0);
|
||||
PendingIntent.getActivity(
|
||||
GeckoViewActivity.this, mLastID, clickIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
NotificationCompat.Builder builder =
|
||||
new NotificationCompat.Builder(GeckoViewActivity.this, CHANNEL_ID)
|
||||
|
@ -33,6 +33,7 @@
|
||||
<string name="crashed_title">GeckoView Example Crashed</string>
|
||||
<string name="crashed_text">Tap to report to Mozilla.</string>
|
||||
<string name="crashed_ignore">Ignore</string>
|
||||
<string name="crashed_report">Report</string>
|
||||
<string name="device_sharing_microphone">Microphone is on</string>
|
||||
<string name="device_sharing_camera">Camera is on</string>
|
||||
<string name="device_sharing_camera_and_mic">Camera and microphone are on</string>
|
||||
|
@ -6861,12 +6861,10 @@
|
||||
mirror: always
|
||||
#endif // defined(ENABLE_WASM_EXTENDED_CONST)
|
||||
|
||||
#if defined(ENABLE_WASM_EXCEPTIONS)
|
||||
- name: javascript.options.wasm_exceptions
|
||||
type: bool
|
||||
value: true
|
||||
mirror: always
|
||||
#endif // defined(ENABLE_WASM_EXCEPTIONS)
|
||||
|
||||
#if defined(ENABLE_WASM_FUNCTION_REFERENCES)
|
||||
- name: javascript.options.wasm_function_references
|
||||
|
@ -5003,7 +5003,7 @@ HttpBaseChannel::SetAllRedirectsPassTimingAllowCheck(bool aPassesCheck) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// http://www.w3.org/TR/resource-timing/#timing-allow-check
|
||||
// https://fetch.spec.whatwg.org/#tao-check
|
||||
NS_IMETHODIMP
|
||||
HttpBaseChannel::TimingAllowCheck(nsIPrincipal* aOrigin, bool* _retval) {
|
||||
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
||||
@ -5017,7 +5017,13 @@ HttpBaseChannel::TimingAllowCheck(nsIPrincipal* aOrigin, bool* _retval) {
|
||||
|
||||
bool sameOrigin = false;
|
||||
rv = resourcePrincipal->Equals(aOrigin, &sameOrigin);
|
||||
if (NS_SUCCEEDED(rv) && sameOrigin) {
|
||||
|
||||
nsAutoCString serializedOrigin;
|
||||
nsContentSecurityManager::GetSerializedOrigin(aOrigin, resourcePrincipal,
|
||||
serializedOrigin, mLoadInfo);
|
||||
|
||||
// All redirects are same origin
|
||||
if (sameOrigin && !serializedOrigin.IsEmpty()) {
|
||||
*_retval = true;
|
||||
return NS_OK;
|
||||
}
|
||||
@ -5029,9 +5035,6 @@ HttpBaseChannel::TimingAllowCheck(nsIPrincipal* aOrigin, bool* _retval) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsAutoCString origin;
|
||||
aOrigin->GetAsciiOrigin(origin);
|
||||
|
||||
Tokenizer p(headerValue);
|
||||
Tokenizer::Token t;
|
||||
|
||||
@ -5044,7 +5047,7 @@ HttpBaseChannel::TimingAllowCheck(nsIPrincipal* aOrigin, bool* _retval) {
|
||||
nsHttp::TrimHTTPWhitespace(headerItem, headerItem);
|
||||
// If the list item contains a case-sensitive match for the value of the
|
||||
// origin, or a wildcard, return pass
|
||||
if (headerItem == origin || headerItem == "*") {
|
||||
if (headerItem == serializedOrigin || headerItem == "*") {
|
||||
*_retval = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -731,31 +731,6 @@ NullHttpChannel::SetAllRedirectsPassTimingAllowCheck(
|
||||
|
||||
NS_IMETHODIMP
|
||||
NullHttpChannel::TimingAllowCheck(nsIPrincipal* aOrigin, bool* _retval) {
|
||||
if (!mResourcePrincipal || !aOrigin) {
|
||||
*_retval = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool sameOrigin = false;
|
||||
nsresult rv = mResourcePrincipal->Equals(aOrigin, &sameOrigin);
|
||||
if (NS_SUCCEEDED(rv) && sameOrigin) {
|
||||
*_retval = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (mTimingAllowOriginHeader == "*") {
|
||||
*_retval = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsAutoCString origin;
|
||||
aOrigin->GetAsciiOrigin(origin);
|
||||
|
||||
if (mTimingAllowOriginHeader == origin) {
|
||||
*_retval = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
*_retval = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -600,7 +600,7 @@
|
||||
temp_rows = rows;
|
||||
input.expect_delim('/')?;
|
||||
flow = parse_auto_flow(input, false)?;
|
||||
auto_cols = grid_auto_columns::parse(context, input).unwrap_or_default();
|
||||
auto_cols = input.try_parse(|i| grid_auto_columns::parse(context, i)).unwrap_or_default();
|
||||
} else {
|
||||
flow = parse_auto_flow(input, true)?;
|
||||
auto_rows = input.try_parse(|i| grid_auto_rows::parse(context, i)).unwrap_or_default();
|
||||
|
@ -1,7 +0,0 @@
|
||||
[SO-XO-SO-redirect-chain-tao.https.html]
|
||||
[Verify that cross origin resources' timings are not exposed when same-origin=>cross-origin=>same-origin redirects have `Timing-Allow-Origin:` headers only on some of the responses.]
|
||||
expected: FAIL
|
||||
|
||||
[Verify that cross origin resources' timings are not exposed when same-origin=>cross-origin=>same-origin redirects have `Timing-Allow-Origin:` headers with a specific origin.]
|
||||
expected: FAIL
|
||||
|
@ -39,11 +39,11 @@
|
||||
expected:
|
||||
if release_or_beta: FAIL
|
||||
|
||||
[anyfunc, mutable]
|
||||
[funcref, mutable]
|
||||
expected:
|
||||
if release_or_beta: FAIL
|
||||
|
||||
[anyfunc, immutable]
|
||||
[funcref, immutable]
|
||||
expected:
|
||||
if release_or_beta: FAIL
|
||||
|
||||
@ -93,11 +93,11 @@
|
||||
expected:
|
||||
if release_or_beta: FAIL
|
||||
|
||||
[anyfunc, mutable]
|
||||
[funcref, mutable]
|
||||
expected:
|
||||
if release_or_beta: FAIL
|
||||
|
||||
[anyfunc, immutable]
|
||||
[funcref, immutable]
|
||||
expected:
|
||||
if release_or_beta: FAIL
|
||||
|
||||
@ -147,11 +147,11 @@
|
||||
expected:
|
||||
if release_or_beta: FAIL
|
||||
|
||||
[anyfunc, mutable]
|
||||
[funcref, mutable]
|
||||
expected:
|
||||
if release_or_beta: FAIL
|
||||
|
||||
[anyfunc, immutable]
|
||||
[funcref, immutable]
|
||||
expected:
|
||||
if release_or_beta: FAIL
|
||||
|
||||
|
@ -46,6 +46,7 @@ test_invalid_value("grid", 'auto-flow 100px');
|
||||
test_invalid_value("grid", 'auto-flow / auto-flow');
|
||||
test_invalid_value("grid", 'auto-flow 1fr / auto-flow 1fr');
|
||||
test_invalid_value("grid", 'dense auto-flow / dense auto-flow');
|
||||
test_invalid_value("grid", 'auto / auto-flow foo()');
|
||||
// FIXME: add more values to test full syntax
|
||||
|
||||
</script>
|
||||
|
@ -16,15 +16,17 @@ var EXPORTED_SYMBOLS = [
|
||||
"TestingCrashManager",
|
||||
];
|
||||
|
||||
const { CrashManager } = ChromeUtils.import(
|
||||
"resource://gre/modules/CrashManager.jsm"
|
||||
);
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const lazy = {};
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(lazy, {
|
||||
CrashManager: "resource://gre/modules/CrashManager.jsm",
|
||||
Log: "resource://gre/modules/Log.jsm",
|
||||
OS: "resource://gre/modules/osfile.jsm",
|
||||
setTimeout: "resource://gre/modules/Timer.jsm",
|
||||
@ -54,11 +56,11 @@ var sleep = function(wait) {
|
||||
};
|
||||
|
||||
var TestingCrashManager = function(options) {
|
||||
lazy.CrashManager.call(this, options);
|
||||
CrashManager.call(this, options);
|
||||
};
|
||||
|
||||
TestingCrashManager.prototype = {
|
||||
__proto__: lazy.CrashManager.prototype,
|
||||
__proto__: CrashManager.prototype,
|
||||
|
||||
createDummyDump(submitted = false, date = new Date(), hr = false) {
|
||||
let uuid = Services.uuid.generateUUID().toString();
|
||||
@ -156,7 +158,7 @@ TestingCrashManager.prototype = {
|
||||
return this.EVENT_FILE_ERROR_UNKNOWN_EVENT;
|
||||
}
|
||||
|
||||
return lazy.CrashManager.prototype._handleEventFilePayload.call(
|
||||
return CrashManager.prototype._handleEventFilePayload.call(
|
||||
this,
|
||||
store,
|
||||
entry,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user