зеркало из https://github.com/mozilla/gecko-dev.git
Merge autoland to mozilla-central. a=merge
This commit is contained in:
Коммит
2f40a88cf8
|
@ -180,6 +180,9 @@ pref("app.update.langpack.enabled", true);
|
|||
pref("app.update.background.timeoutSec", 600);
|
||||
// By default, check for updates when the browser is not running every 7 hours.
|
||||
pref("app.update.background.interval", 25200);
|
||||
// By default, snapshot Firefox Messaging System targeting for use by the
|
||||
// background update task every 30 minutes.
|
||||
pref("app.update.background.messaging.targeting.snapshot.intervalSec", 1800);
|
||||
#endif
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
|
|
|
@ -2796,6 +2796,14 @@ BrowserGlue.prototype = {
|
|||
lazy.RemoteAgent.running) &&
|
||||
Services.prefs.getBoolPref("app.update.disabledForTesting", false);
|
||||
if (!disabledForTesting) {
|
||||
try {
|
||||
lazy.BackgroundUpdate.scheduleFirefoxMessagingSystemTargetingSnapshotting();
|
||||
} catch (e) {
|
||||
Cu.reportError(
|
||||
"There was an error scheduling Firefox Messaging System targeting snapshotting: " +
|
||||
e
|
||||
);
|
||||
}
|
||||
lazy.BackgroundUpdate.maybeScheduleBackgroundUpdateTask();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -13,7 +13,9 @@ const { AddonManager } = ChromeUtils.import(
|
|||
const INTENSITY_SOFT = "soft";
|
||||
const INTENSITY_BALANCED = "balanced";
|
||||
const INTENSITY_BOLD = "bold";
|
||||
const ID_SUFFIX_FOR_PRIMARY_INTENSITY = `-${INTENSITY_BALANCED}-colorway@mozilla.org`;
|
||||
const ID_SUFFIX_COLORWAY = "-colorway@mozilla.org";
|
||||
const ID_SUFFIX_PRIMARY_INTENSITY = `-${INTENSITY_BALANCED}${ID_SUFFIX_COLORWAY}`;
|
||||
const ID_SUFFIX_DARK_COLORWAY = `-${INTENSITY_BOLD}${ID_SUFFIX_COLORWAY}`;
|
||||
const ID_SUFFIXES_FOR_SECONDARY_INTENSITIES = new RegExp(
|
||||
`-(${INTENSITY_SOFT}|${INTENSITY_BOLD})-colorway@mozilla\\.org$`
|
||||
);
|
||||
|
@ -89,12 +91,21 @@ const ColorwayCloset = {
|
|||
}
|
||||
|
||||
// If the current active theme is part of our collection, make the UI reflect
|
||||
// that. Otherwise go ahead and enable the first theme in our list.
|
||||
// that. Otherwise go ahead and enable the first colorway in our list.
|
||||
this.selectedColorway = this.colorways.find(colorway => colorway.isActive);
|
||||
if (this.selectedColorway) {
|
||||
this.refresh();
|
||||
} else {
|
||||
this.colorwayGroups[0].enable();
|
||||
let colorwayToEnable = this.colorwayGroups[0];
|
||||
// If the user has been using a theme with a dark color scheme, make an
|
||||
// effort to default to a colorway with a dark color scheme as well.
|
||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
let firstDarkColorway = this.colorways.find(colorway =>
|
||||
colorway.id.endsWith(ID_SUFFIX_DARK_COLORWAY)
|
||||
);
|
||||
colorwayToEnable = firstDarkColorway || colorwayToEnable;
|
||||
}
|
||||
colorwayToEnable.enable();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -174,7 +185,7 @@ const ColorwayCloset = {
|
|||
_getColorwayGroupId(colorwayId) {
|
||||
let groupId = colorwayId.replace(
|
||||
ID_SUFFIXES_FOR_SECONDARY_INTENSITIES,
|
||||
ID_SUFFIX_FOR_PRIMARY_INTENSITY
|
||||
ID_SUFFIX_PRIMARY_INTENSITY
|
||||
);
|
||||
return this.colorwayGroups.map(addon => addon.id).includes(groupId)
|
||||
? groupId
|
||||
|
@ -184,7 +195,7 @@ const ColorwayCloset = {
|
|||
_changeIntensity(colorwayId, intensity) {
|
||||
return colorwayId.replace(
|
||||
MATCH_INTENSITY_FROM_ID,
|
||||
`-${intensity}-colorway@mozilla.org`
|
||||
`-${intensity}${ID_SUFFIX_COLORWAY}`
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -233,7 +244,7 @@ const ColorwayCloset = {
|
|||
this.selectedColorway.id
|
||||
);
|
||||
this.hasIntensities = this.groupIdForSelectedColorway.endsWith(
|
||||
ID_SUFFIX_FOR_PRIMARY_INTENSITY
|
||||
ID_SUFFIX_PRIMARY_INTENSITY
|
||||
);
|
||||
for (let input of this.el.colorwayRadios.children) {
|
||||
if (input.value == this.groupIdForSelectedColorway) {
|
||||
|
|
|
@ -737,8 +737,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"secondary": {
|
||||
}
|
||||
},
|
||||
"secondary": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
|
@ -749,7 +752,7 @@
|
|||
"value": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/plainText"
|
||||
"$ref": "file:///ExtensionDoorhanger.schema.json#/definitions/plainText"
|
||||
},
|
||||
{
|
||||
"description": "Button label override used when a localized version is not available."
|
||||
|
@ -780,7 +783,7 @@
|
|||
"string_id": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/plainText"
|
||||
"$ref": "file:///ExtensionDoorhanger.schema.json#/definitions/plainText"
|
||||
},
|
||||
{
|
||||
"description": "Id of localized string for button"
|
||||
|
@ -807,7 +810,7 @@
|
|||
"url": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/linkUrl"
|
||||
"$ref": "file:///ExtensionDoorhanger.schema.json#/definitions/linkUrl"
|
||||
},
|
||||
{
|
||||
"description": "URL used in combination with the primary action dispatched."
|
||||
|
|
|
@ -0,0 +1,697 @@
|
|||
[
|
||||
{
|
||||
"id": "WNP_THANK_YOU",
|
||||
"template": "update_action",
|
||||
"content": {
|
||||
"action": {
|
||||
"id": "moments-wnp",
|
||||
"data": {
|
||||
"url": "https://www.mozilla.org/%LOCALE%/etc/firefox/retention/thank-you-a/",
|
||||
"expireDelta": 172800000
|
||||
}
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"id": "momentsUpdate"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "WHATS_NEW_FINGERPRINTER_COUNTER_ALT",
|
||||
"template": "whatsnew_panel_message",
|
||||
"order": 6,
|
||||
"content": {
|
||||
"bucket_id": "WHATS_NEW_72",
|
||||
"published_date": 1574776601000,
|
||||
"title": "Title",
|
||||
"icon_url": "chrome://activity-stream/content/data/content/assets/protection-report-icon.png",
|
||||
"icon_alt": {
|
||||
"string_id": "cfr-badge-reader-label-newfeature"
|
||||
},
|
||||
"body": "Message body",
|
||||
"link_text": "Click here",
|
||||
"cta_url": "about:blank",
|
||||
"cta_type": "OPEN_PROTECTION_REPORT"
|
||||
},
|
||||
"targeting": "firefoxVersion >= 72",
|
||||
"trigger": {
|
||||
"id": "whatsNewPanelOpened"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "WHATS_NEW_70_1",
|
||||
"template": "whatsnew_panel_message",
|
||||
"order": 3,
|
||||
"content": {
|
||||
"bucket_id": "WHATS_NEW_70_1",
|
||||
"published_date": 1560969794394,
|
||||
"title": "Protection Is Our Focus",
|
||||
"icon_url": "chrome://activity-stream/content/data/content/assets/whatsnew-send-icon.png",
|
||||
"icon_alt": "Firefox Send Logo",
|
||||
"body": "The New Enhanced Tracking Protection, gives you the best level of protection and performance. Discover how this version is the safest version of firefox ever made.",
|
||||
"cta_url": "https://blog.mozilla.org/",
|
||||
"cta_type": "OPEN_URL"
|
||||
},
|
||||
"targeting": "firefoxVersion > 69",
|
||||
"trigger": {
|
||||
"id": "whatsNewPanelOpened"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "WHATS_NEW_70_2",
|
||||
"template": "whatsnew_panel_message",
|
||||
"order": 1,
|
||||
"content": {
|
||||
"bucket_id": "WHATS_NEW_70_1",
|
||||
"published_date": 1560969794394,
|
||||
"title": "Another thing new in Firefox 70",
|
||||
"body": "The New Enhanced Tracking Protection, gives you the best level of protection and performance. Discover how this version is the safest version of firefox ever made.",
|
||||
"link_text": "Learn more on our blog",
|
||||
"cta_url": "https://blog.mozilla.org/",
|
||||
"cta_type": "OPEN_URL"
|
||||
},
|
||||
"targeting": "firefoxVersion > 69",
|
||||
"trigger": {
|
||||
"id": "whatsNewPanelOpened"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "WHATS_NEW_SEARCH_SHORTCUTS_84",
|
||||
"template": "whatsnew_panel_message",
|
||||
"order": 2,
|
||||
"content": {
|
||||
"bucket_id": "WHATS_NEW_SEARCH_SHORTCUTS_84",
|
||||
"published_date": 1560969794394,
|
||||
"title": "Title",
|
||||
"icon_url": "chrome://global/skin/icons/check.svg",
|
||||
"icon_alt": "",
|
||||
"body": "Message content",
|
||||
"cta_url": "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/search-shortcuts",
|
||||
"cta_type": "OPEN_URL",
|
||||
"link_text": "Click here"
|
||||
},
|
||||
"targeting": "firefoxVersion >= 84",
|
||||
"trigger": {
|
||||
"id": "whatsNewPanelOpened"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "WHATS_NEW_PIONEER_82",
|
||||
"template": "whatsnew_panel_message",
|
||||
"order": 1,
|
||||
"content": {
|
||||
"bucket_id": "WHATS_NEW_PIONEER_82",
|
||||
"published_date": 1603152000000,
|
||||
"title": "Put your data to work for a better internet",
|
||||
"body": "Contribute your data to Mozilla's Pioneer program to help researchers understand pressing technology issues like misinformation, data privacy, and ethical AI.",
|
||||
"cta_url": "about:blank",
|
||||
"cta_where": "tab",
|
||||
"cta_type": "OPEN_ABOUT_PAGE",
|
||||
"link_text": "Join Pioneer"
|
||||
},
|
||||
"targeting": "firefoxVersion >= 82",
|
||||
"trigger": {
|
||||
"id": "whatsNewPanelOpened"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "WHATS_NEW_MEDIA_SESSION_82",
|
||||
"template": "whatsnew_panel_message",
|
||||
"order": 3,
|
||||
"content": {
|
||||
"bucket_id": "WHATS_NEW_MEDIA_SESSION_82",
|
||||
"published_date": 1603152000000,
|
||||
"title": "Title",
|
||||
"body": "Message content",
|
||||
"cta_url": "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/media-keyboard-control",
|
||||
"cta_type": "OPEN_URL",
|
||||
"link_text": "Click here"
|
||||
},
|
||||
"targeting": "firefoxVersion >= 82",
|
||||
"trigger": {
|
||||
"id": "whatsNewPanelOpened"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "WHATS_NEW_69_1",
|
||||
"template": "whatsnew_panel_message",
|
||||
"order": 1,
|
||||
"content": {
|
||||
"bucket_id": "WHATS_NEW_69_1",
|
||||
"published_date": 1557346235089,
|
||||
"title": "Something new in Firefox 69",
|
||||
"body": "The New Enhanced Tracking Protection, gives you the best level of protection and performance. Discover how this version is the safest version of firefox ever made.",
|
||||
"link_text": "Learn more on our blog",
|
||||
"cta_url": "https://blog.mozilla.org/",
|
||||
"cta_type": "OPEN_URL"
|
||||
},
|
||||
"targeting": "firefoxVersion > 68",
|
||||
"trigger": {
|
||||
"id": "whatsNewPanelOpened"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "PERSONALIZED_CFR_MESSAGE",
|
||||
"template": "cfr_doorhanger",
|
||||
"groups": [
|
||||
"cfr"
|
||||
],
|
||||
"content": {
|
||||
"layout": "icon_and_message",
|
||||
"category": "cfrFeatures",
|
||||
"bucket_id": "PERSONALIZED_CFR_MESSAGE",
|
||||
"notification_text": "Personalized CFR Recommendation",
|
||||
"heading_text": {
|
||||
"string_id": "cfr-doorhanger-bookmark-fxa-header"
|
||||
},
|
||||
"info_icon": {
|
||||
"label": {
|
||||
"attributes": {
|
||||
"tooltiptext": {
|
||||
"string_id": "cfr-doorhanger-fxa-close-btn-tooltip"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sumo_path": "https://example.com"
|
||||
},
|
||||
"text": {
|
||||
"string_id": "cfr-doorhanger-bookmark-fxa-body"
|
||||
},
|
||||
"icon": "chrome://branding/content/icon64.png",
|
||||
"icon_class": "cfr-doorhanger-large-icon",
|
||||
"persistent_doorhanger": true,
|
||||
"buttons": {
|
||||
"primary": {
|
||||
"label": {
|
||||
"string_id": "cfr-doorhanger-milestone-ok-button"
|
||||
},
|
||||
"action": {
|
||||
"type": "OPEN_URL",
|
||||
"data": {
|
||||
"args": "https://send.firefox.com/login/?utm_source=activity-stream&entrypoint=activity-stream-cfr-pdf",
|
||||
"where": "tabshifted"
|
||||
}
|
||||
}
|
||||
},
|
||||
"secondary": [
|
||||
{
|
||||
"label": {
|
||||
"string_id": "cfr-doorhanger-extension-cancel-button"
|
||||
},
|
||||
"action": {
|
||||
"type": "CANCEL"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": {
|
||||
"string_id": "cfr-doorhanger-extension-never-show-recommendation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": {
|
||||
"string_id": "cfr-doorhanger-extension-manage-settings-button"
|
||||
},
|
||||
"action": {
|
||||
"type": "OPEN_PREFERENCES_PAGE",
|
||||
"data": {
|
||||
"category": "general-cfrfeatures"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"targeting": "scores.PERSONALIZED_CFR_MESSAGE.score > scoreThreshold",
|
||||
"trigger": {
|
||||
"id": "openURL",
|
||||
"patterns": [
|
||||
"*://*/*.pdf"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "SPOTLIGHT_MESSAGE_93",
|
||||
"template": "spotlight",
|
||||
"groups": [
|
||||
"panel-test-provider"
|
||||
],
|
||||
"content": {
|
||||
"template": "logo-and-content",
|
||||
"logo": {
|
||||
"imageURL": "chrome://browser/content/logos/vpn-promo-logo.svg"
|
||||
},
|
||||
"body": {
|
||||
"title": {
|
||||
"label": {
|
||||
"string_id": "spotlight-public-wifi-vpn-header"
|
||||
}
|
||||
},
|
||||
"text": {
|
||||
"label": {
|
||||
"string_id": "spotlight-public-wifi-vpn-body"
|
||||
}
|
||||
},
|
||||
"primary": {
|
||||
"label": {
|
||||
"string_id": "spotlight-public-wifi-vpn-primary-button"
|
||||
},
|
||||
"action": {
|
||||
"type": "OPEN_URL",
|
||||
"data": {
|
||||
"args": "https://www.mozilla.org/en-US/products/vpn/",
|
||||
"where": "tabshifted"
|
||||
}
|
||||
}
|
||||
},
|
||||
"secondary": {
|
||||
"label": {
|
||||
"string_id": "spotlight-public-wifi-vpn-link"
|
||||
},
|
||||
"action": {
|
||||
"type": "CANCEL"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"frequency": {
|
||||
"lifetime": 3
|
||||
},
|
||||
"trigger": {
|
||||
"id": "defaultBrowserCheck"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "TCP_CFR_MESSAGE_103",
|
||||
"groups": [
|
||||
"cfr"
|
||||
],
|
||||
"template": "cfr_doorhanger",
|
||||
"content": {
|
||||
"bucket_id": "TCP_CFR",
|
||||
"layout": "icon_and_message",
|
||||
"icon": "chrome://branding/content/about-logo@2x.png",
|
||||
"icon_class": "cfr-doorhanger-large-icon",
|
||||
"heading_text": {
|
||||
"string_id": "cfr-total-cookie-protection-header"
|
||||
},
|
||||
"text": {
|
||||
"string_id": "cfr-total-cookie-protection-body"
|
||||
},
|
||||
"buttons": {
|
||||
"primary": {
|
||||
"label": {
|
||||
"string_id": "cfr-doorhanger-milestone-close-button"
|
||||
},
|
||||
"action": {
|
||||
"type": "CANCEL"
|
||||
}
|
||||
},
|
||||
"secondary": []
|
||||
},
|
||||
"anchor_id": "tracking-protection-icon-container",
|
||||
"skip_address_bar_notifier": true
|
||||
},
|
||||
"frequency": {
|
||||
"lifetime": 1
|
||||
},
|
||||
"trigger": {
|
||||
"id": "openURL",
|
||||
"patterns": [
|
||||
"*://*/*"
|
||||
]
|
||||
},
|
||||
"targeting": "firefoxVersion >= 103 && 'privacy.restrict3rdpartystorage.rollout.enabledByDefault'|preferenceValue"
|
||||
},
|
||||
{
|
||||
"id": "BETTER_INTERNET_GLOBAL_ROLLOUT",
|
||||
"groups": [
|
||||
"eco"
|
||||
],
|
||||
"content": {
|
||||
"template": "logo-and-content",
|
||||
"logo": {
|
||||
"imageURL": "chrome://activity-stream/content/data/content/assets/remote/mountain.svg",
|
||||
"size": "115px"
|
||||
},
|
||||
"body": {
|
||||
"title": {
|
||||
"label": {
|
||||
"string_id": "spotlight-better-internet-header"
|
||||
},
|
||||
"size": "22px"
|
||||
},
|
||||
"text": {
|
||||
"label": {
|
||||
"string_id": "spotlight-better-internet-body"
|
||||
},
|
||||
"size": "16px"
|
||||
},
|
||||
"primary": {
|
||||
"label": {
|
||||
"string_id": "spotlight-pin-primary-button"
|
||||
},
|
||||
"action": {
|
||||
"type": "PIN_FIREFOX_TO_TASKBAR"
|
||||
}
|
||||
},
|
||||
"secondary": {
|
||||
"label": {
|
||||
"string_id": "spotlight-pin-secondary-button"
|
||||
},
|
||||
"action": {
|
||||
"type": "CANCEL"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"id": "defaultBrowserCheck"
|
||||
},
|
||||
"template": "spotlight",
|
||||
"frequency": {
|
||||
"lifetime": 1
|
||||
},
|
||||
"targeting": "userMonthlyActivity|length >= 1 && userMonthlyActivity|length <= 6 && doesAppNeedPin"
|
||||
},
|
||||
{
|
||||
"id": "PEACE_OF_MIND_GLOBAL_ROLLOUT",
|
||||
"groups": [
|
||||
"eco"
|
||||
],
|
||||
"content": {
|
||||
"template": "logo-and-content",
|
||||
"logo": {
|
||||
"imageURL": "chrome://activity-stream/content/data/content/assets/remote/umbrella.png",
|
||||
"size": "115px"
|
||||
},
|
||||
"body": {
|
||||
"title": {
|
||||
"label": {
|
||||
"string_id": "spotlight-peace-mind-header"
|
||||
},
|
||||
"size": "22px"
|
||||
},
|
||||
"text": {
|
||||
"label": {
|
||||
"string_id": "spotlight-peace-mind-body"
|
||||
},
|
||||
"size": "15px"
|
||||
},
|
||||
"primary": {
|
||||
"label": {
|
||||
"string_id": "spotlight-pin-primary-button"
|
||||
},
|
||||
"action": {
|
||||
"type": "PIN_FIREFOX_TO_TASKBAR"
|
||||
}
|
||||
},
|
||||
"secondary": {
|
||||
"label": {
|
||||
"string_id": "spotlight-pin-secondary-button"
|
||||
},
|
||||
"action": {
|
||||
"type": "CANCEL"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"trigger": {
|
||||
"id": "defaultBrowserCheck"
|
||||
},
|
||||
"template": "spotlight",
|
||||
"frequency": {
|
||||
"lifetime": 1
|
||||
},
|
||||
"targeting": "userMonthlyActivity|length >= 7 && userMonthlyActivity|length <= 13 && doesAppNeedPin"
|
||||
},
|
||||
{
|
||||
"id": "MULTISTAGE_SPOTLIGHT_MESSAGE",
|
||||
"groups": [
|
||||
"panel-test-provider"
|
||||
],
|
||||
"template": "spotlight",
|
||||
"content": {
|
||||
"id": "control",
|
||||
"template": "multistage",
|
||||
"backdrop": "transparent",
|
||||
"transitions": true,
|
||||
"screens": [
|
||||
{
|
||||
"id": "AW_PIN_FIREFOX",
|
||||
"content": {
|
||||
"has_noodles": true,
|
||||
"title": {
|
||||
"string_id": "mr1-onboarding-pin-header"
|
||||
},
|
||||
"logo": {},
|
||||
"hero_text": {
|
||||
"string_id": "mr1-welcome-screen-hero-text"
|
||||
},
|
||||
"help_text": {
|
||||
"text": {
|
||||
"string_id": "mr1-onboarding-welcome-image-caption"
|
||||
}
|
||||
},
|
||||
"primary_button": {
|
||||
"label": {
|
||||
"string_id": "mr1-onboarding-pin-primary-button-label"
|
||||
},
|
||||
"action": {
|
||||
"navigate": true,
|
||||
"type": "PIN_FIREFOX_TO_TASKBAR"
|
||||
}
|
||||
},
|
||||
"secondary_button": {
|
||||
"label": {
|
||||
"string_id": "mr1-onboarding-set-default-secondary-button-label"
|
||||
},
|
||||
"action": {
|
||||
"navigate": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "AW_SET_DEFAULT",
|
||||
"content": {
|
||||
"has_noodles": true,
|
||||
"logo": {
|
||||
"imageURL": "chrome://browser/content/logos/vpn-promo-logo.svg",
|
||||
"height": "100px"
|
||||
},
|
||||
"title": {
|
||||
"fontSize": "36px",
|
||||
"fontWeight": 276,
|
||||
"string_id": "mr1-onboarding-default-header"
|
||||
},
|
||||
"subtitle": {
|
||||
"string_id": "mr1-onboarding-default-subtitle"
|
||||
},
|
||||
"primary_button": {
|
||||
"label": {
|
||||
"string_id": "mr1-onboarding-default-primary-button-label"
|
||||
},
|
||||
"action": {
|
||||
"navigate": true,
|
||||
"type": "SET_DEFAULT_BROWSER"
|
||||
}
|
||||
},
|
||||
"secondary_button": {
|
||||
"label": {
|
||||
"string_id": "mr1-onboarding-set-default-secondary-button-label"
|
||||
},
|
||||
"action": {
|
||||
"navigate": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "BACKGROUND_IMAGE",
|
||||
"content": {
|
||||
"background": "url(chrome://activity-stream/content/data/content/assets/proton-bkg.avif) no-repeat center/cover",
|
||||
"text_color": "light",
|
||||
"logo": {
|
||||
"imageURL": "https://firefox-settings-attachments.cdn.mozilla.net/main-workspace/ms-images/a3c640c8-7594-4bb2-bc18-8b4744f3aaf2.gif"
|
||||
},
|
||||
"title": "A dialog with a background image",
|
||||
"subtitle": "The text color is configurable",
|
||||
"primary_button": {
|
||||
"label": "Continue",
|
||||
"action": {
|
||||
"navigate": true
|
||||
}
|
||||
},
|
||||
"secondary_button": {
|
||||
"label": {
|
||||
"string_id": "mr1-onboarding-set-default-secondary-button-label"
|
||||
},
|
||||
"action": {
|
||||
"navigate": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "BACKGROUND_COLOR",
|
||||
"content": {
|
||||
"background": "white",
|
||||
"logo": {
|
||||
"height": "200px",
|
||||
"imageURL": ""
|
||||
},
|
||||
"title": {
|
||||
"fontSize": "36px",
|
||||
"fontWeight": 276,
|
||||
"raw": "Peace of mind."
|
||||
},
|
||||
"title_style": "fancy shine",
|
||||
"text_color": "dark",
|
||||
"subtitle": "For the best privacy protection, keep Firefox in easy reach.",
|
||||
"primary_button": {
|
||||
"label": "Continue",
|
||||
"action": {
|
||||
"navigate": true
|
||||
}
|
||||
},
|
||||
"secondary_button": {
|
||||
"label": {
|
||||
"string_id": "mr1-onboarding-set-default-secondary-button-label"
|
||||
},
|
||||
"action": {
|
||||
"navigate": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"frequency": {
|
||||
"lifetime": 3
|
||||
},
|
||||
"trigger": {
|
||||
"id": "defaultBrowserCheck"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "PB_FOCUS_PROMO",
|
||||
"groups": [
|
||||
"panel-test-provider"
|
||||
],
|
||||
"template": "spotlight",
|
||||
"content": {
|
||||
"template": "multistage",
|
||||
"backdrop": "transparent",
|
||||
"screens": [
|
||||
{
|
||||
"id": "PBM_FIREFOX_FOCUS",
|
||||
"order": 0,
|
||||
"content": {
|
||||
"logo": {
|
||||
"imageURL": "chrome://browser/content/assets/focus-logo.svg",
|
||||
"height": "48px"
|
||||
},
|
||||
"title": {
|
||||
"string_id": "spotlight-focus-promo-title"
|
||||
},
|
||||
"subtitle": {
|
||||
"string_id": "spotlight-focus-promo-subtitle"
|
||||
},
|
||||
"dismiss_button": {
|
||||
"action": {
|
||||
"navigate": true
|
||||
}
|
||||
},
|
||||
"ios": {
|
||||
"action": {
|
||||
"data": {
|
||||
"args": "https://app.adjust.com/167k4ih?campaign=firefox-desktop&adgroup=pb&creative=focus-omc172&redirect=https%3A%2F%2Fapps.apple.com%2Fus%2Fapp%2Ffirefox-focus-privacy-browser%2Fid1055677337",
|
||||
"where": "tabshifted"
|
||||
},
|
||||
"type": "OPEN_URL",
|
||||
"navigate": true
|
||||
}
|
||||
},
|
||||
"android": {
|
||||
"action": {
|
||||
"data": {
|
||||
"args": "https://app.adjust.com/167k4ih?campaign=firefox-desktop&adgroup=pb&creative=focus-omc172&redirect=https%3A%2F%2Fplay.google.com%2Fstore%2Fapps%2Fdetails%3Fid%3Dorg.mozilla.focus",
|
||||
"where": "tabshifted"
|
||||
},
|
||||
"type": "OPEN_URL",
|
||||
"navigate": true
|
||||
}
|
||||
},
|
||||
"email_link": {
|
||||
"action": {
|
||||
"data": {
|
||||
"args": "https://mozilla.org",
|
||||
"where": "tabshifted"
|
||||
},
|
||||
"type": "OPEN_URL",
|
||||
"navigate": true
|
||||
}
|
||||
},
|
||||
"tiles": {
|
||||
"type": "mobile_downloads",
|
||||
"data": {
|
||||
"QR_code": {
|
||||
"image_url": "chrome://browser/content/assets/focus-qr-code.svg",
|
||||
"alt_text": {
|
||||
"string_id": "spotlight-focus-promo-qr-code"
|
||||
},
|
||||
"image_overrides": {
|
||||
"de": "chrome://browser/content/assets/klar-qr-code.svg"
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"link_text": "Email yourself a link"
|
||||
},
|
||||
"marketplace_buttons": [
|
||||
"ios",
|
||||
"android"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": {
|
||||
"id": "defaultBrowserCheck"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "PB_NEWTAB_VPN_PROMO",
|
||||
"template": "pb_newtab",
|
||||
"content": {
|
||||
"promoEnabled": true,
|
||||
"promoType": "VPN",
|
||||
"infoEnabled": true,
|
||||
"infoBody": "fluent:about-private-browsing-info-description-private-window",
|
||||
"infoLinkText": "fluent:about-private-browsing-learn-more-link",
|
||||
"infoTitleEnabled": false,
|
||||
"promoLinkType": "button",
|
||||
"promoLinkText": "fluent:about-private-browsing-prominent-cta",
|
||||
"promoSectionStyle": "below-search",
|
||||
"promoHeader": "fluent:about-private-browsing-get-privacy",
|
||||
"promoTitle": "fluent:about-private-browsing-hide-activity-1",
|
||||
"promoTitleEnabled": true,
|
||||
"promoImageLarge": "chrome://browser/content/assets/moz-vpn.svg",
|
||||
"promoButton": {
|
||||
"action": {
|
||||
"type": "OPEN_URL",
|
||||
"data": {
|
||||
"args": "https://vpn.mozilla.org/"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
"panel-test-provider"
|
||||
],
|
||||
"targeting": "region != 'CN' && !hasActiveEnterprisePolicies",
|
||||
"frequency": {
|
||||
"lifetime": 3
|
||||
}
|
||||
}
|
||||
]
|
|
@ -10,8 +10,12 @@ from itertools import chain
|
|||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import jsonschema
|
||||
|
||||
|
||||
SCHEMA_BASE_DIR = Path("..", "templates")
|
||||
|
||||
PANEL_TEST_PROVIDER_MESSAGES = Path("PanelTestProvider.messages.json")
|
||||
|
||||
MESSAGE_TYPES = {
|
||||
"CFRUrlbarChiclet": Path("CFR", "templates", "CFRUrlbarChiclet.schema.json"),
|
||||
|
@ -25,6 +29,24 @@ MESSAGE_TYPES = {
|
|||
}
|
||||
|
||||
|
||||
class NestedRefResolver(jsonschema.RefResolver):
|
||||
"""A custom ref resolver that handles bundled schema.
|
||||
|
||||
This is the resolver used by Experimenter.
|
||||
"""
|
||||
|
||||
def __init__(self, schema):
|
||||
super().__init__(base_uri=None, referrer=None)
|
||||
|
||||
if "$id" in schema:
|
||||
self.store[schema["$id"]] = schema
|
||||
|
||||
if "$defs" in schema:
|
||||
for dfn in schema["$defs"].values():
|
||||
if "$id" in dfn:
|
||||
self.store[dfn["$id"]] = dfn
|
||||
|
||||
|
||||
def read_schema(path):
|
||||
"""Read a schema from disk and parse it as JSON."""
|
||||
with path.open("r") as f:
|
||||
|
@ -45,7 +67,12 @@ def extract_template_values(template):
|
|||
def patch_schema(schema):
|
||||
"""Patch the given schema.
|
||||
|
||||
All relative references will be re-mapped to absolute references.
|
||||
The JSON schema validator that Experimenter uses
|
||||
(https://pypi.org/project/jsonschema/) does not support relative references,
|
||||
nor does it support bundled schemas. We rewrite the schema so that all
|
||||
relative refs are transformed into absolute refs via the schema's `$id`.
|
||||
|
||||
See-also: https://github.com/python-jsonschema/jsonschema/issues/313
|
||||
"""
|
||||
schema_id = schema["$id"]
|
||||
|
||||
|
@ -132,10 +159,11 @@ def main(check=False):
|
|||
b_keys = set(templates[b])
|
||||
intersection = a_keys.intersection(b_keys)
|
||||
|
||||
if len(intersection):
|
||||
raise ValueError(
|
||||
f"Schema {a} and {b} have overlapping template values: {', '.join(intersection)}"
|
||||
)
|
||||
if len(intersection):
|
||||
raise ValueError(
|
||||
f"Schema {a} and {b} have overlapping template values: "
|
||||
f"{', '.join(intersection)}"
|
||||
)
|
||||
|
||||
all_templates = list(chain.from_iterable(templates.values()))
|
||||
|
||||
|
@ -194,6 +222,8 @@ def main(check=False):
|
|||
|
||||
raise ValueError("Schemas do not match!")
|
||||
|
||||
check_schema(filename, schema=on_disk)
|
||||
|
||||
else:
|
||||
with filename.open("wb") as f:
|
||||
print(f"Generating {filename} ...")
|
||||
|
@ -201,6 +231,28 @@ def main(check=False):
|
|||
f.write(b"\n")
|
||||
|
||||
|
||||
def check_schema(filename, schema):
|
||||
"""Check that the schema validates.
|
||||
|
||||
This uses the same validation configuration that is used in Experimenter.
|
||||
"""
|
||||
print("Validating messages with Experimenter JSON Schema validator...")
|
||||
|
||||
resolver = NestedRefResolver(schema)
|
||||
|
||||
with PANEL_TEST_PROVIDER_MESSAGES.open("r") as f:
|
||||
messages = json.load(f)
|
||||
|
||||
for message in messages:
|
||||
# PanelTestProvider overrides the targeting of all messages. Some
|
||||
# messages are missing targeting and will fail without this patch.
|
||||
message["targeting"] = 'providerCohorts.panel_local_testing == "SHOW_TEST"'
|
||||
msg_id = message["id"]
|
||||
|
||||
print(f"Validating {msg_id} with {filename}...")
|
||||
jsonschema.validate(instance=message, schema=schema, resolver=resolver)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = ArgumentParser(description=main.__doc__)
|
||||
parser.add_argument(
|
||||
|
|
|
@ -329,8 +329,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"secondary": {
|
||||
}
|
||||
},
|
||||
"secondary": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
|
|
|
@ -449,6 +449,13 @@ const TargetingGetters = {
|
|||
return lazy.isXPIInstallEnabled;
|
||||
},
|
||||
get addonsInfo() {
|
||||
let bts = Cc["@mozilla.org/backgroundtasks;1"]?.getService(
|
||||
Ci.nsIBackgroundTasks
|
||||
);
|
||||
if (bts?.isBackgroundTaskMode) {
|
||||
return { addons: {}, isFullData: true };
|
||||
}
|
||||
|
||||
return lazy.AddonManager.getActiveAddons(["extension", "service"]).then(
|
||||
({ addons, fullData }) => {
|
||||
const info = {};
|
||||
|
@ -472,6 +479,13 @@ const TargetingGetters = {
|
|||
);
|
||||
},
|
||||
get searchEngines() {
|
||||
const NONE = { installed: [], current: "" };
|
||||
let bts = Cc["@mozilla.org/backgroundtasks;1"]?.getService(
|
||||
Ci.nsIBackgroundTasks
|
||||
);
|
||||
if (bts?.isBackgroundTaskMode) {
|
||||
return Promise.resolve(NONE);
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
// Note: calling init ensures this code is only executed after Search has been initialized
|
||||
Services.search
|
||||
|
@ -482,7 +496,7 @@ const TargetingGetters = {
|
|||
installed: engines.map(engine => engine.identifier),
|
||||
});
|
||||
})
|
||||
.catch(() => resolve({ installed: [], current: "" }));
|
||||
.catch(() => resolve(NONE));
|
||||
});
|
||||
},
|
||||
get isDefaultBrowser() {
|
||||
|
@ -611,6 +625,12 @@ const TargetingGetters = {
|
|||
return lazy.ClientEnvironment.userId;
|
||||
},
|
||||
get profileRestartCount() {
|
||||
let bts = Cc["@mozilla.org/backgroundtasks;1"]?.getService(
|
||||
Ci.nsIBackgroundTasks
|
||||
);
|
||||
if (bts?.isBackgroundTaskMode) {
|
||||
return 0;
|
||||
}
|
||||
// Counter starts at 1 when a profile is created, substract 1 so the value
|
||||
// returned matches expectations
|
||||
return (
|
||||
|
@ -649,6 +669,15 @@ const TargetingGetters = {
|
|||
);
|
||||
},
|
||||
get activeNotifications() {
|
||||
let bts = Cc["@mozilla.org/backgroundtasks;1"]?.getService(
|
||||
Ci.nsIBackgroundTasks
|
||||
);
|
||||
if (bts?.isBackgroundTaskMode) {
|
||||
// This might need to hook into the alert service to enumerate relevant
|
||||
// persistent native notifications.
|
||||
return false;
|
||||
}
|
||||
|
||||
let window = lazy.BrowserWindowTracker.getTopWindow();
|
||||
|
||||
// Technically this doesn't mean we have active notifications,
|
||||
|
@ -692,6 +721,40 @@ const TargetingGetters = {
|
|||
const ASRouterTargeting = {
|
||||
Environment: TargetingGetters,
|
||||
|
||||
/**
|
||||
* Snapshot the current targeting environment.
|
||||
*
|
||||
* Asynchronous getters are handled. Getters that throw or reject
|
||||
* are ignored.
|
||||
*
|
||||
* @param {object} target - the environment to snapshot.
|
||||
* @return {object} snapshot of target with `environment` object and `version`
|
||||
* integer.
|
||||
*/
|
||||
async getEnvironmentSnapshot(target = ASRouterTargeting.Environment) {
|
||||
// One promise for each named property. Label promises with property name.
|
||||
let promises = Object.keys(target).map(async name => [
|
||||
name,
|
||||
await target[name],
|
||||
]);
|
||||
|
||||
// Ignore properties that are rejected.
|
||||
let results = await Promise.allSettled(promises);
|
||||
|
||||
let environment = {};
|
||||
for (let result of results) {
|
||||
if (result.status === "fulfilled") {
|
||||
let [name, value] = result.value;
|
||||
environment[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Should we need to migrate in the future.
|
||||
const snapshot = { environment, version: 1 };
|
||||
|
||||
return snapshot;
|
||||
},
|
||||
|
||||
isTriggerMatch(trigger = {}, candidateMessageTrigger = {}) {
|
||||
if (trigger.id !== candidateMessageTrigger.id) {
|
||||
return false;
|
||||
|
|
|
@ -39,13 +39,15 @@ const DEFAULT_CONTENT = {
|
|||
data: { url: "https://example.com" },
|
||||
},
|
||||
},
|
||||
secondary: {
|
||||
label: {
|
||||
value: "Not Now",
|
||||
attributes: { accesskey: "N" },
|
||||
secondary: [
|
||||
{
|
||||
label: {
|
||||
value: "Not Now",
|
||||
attributes: { accesskey: "N" },
|
||||
},
|
||||
action: { type: "CANCEL" },
|
||||
},
|
||||
action: { type: "CANCEL" },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -75,10 +77,12 @@ const L10N_CONTENT = {
|
|||
data: { url: "https://example.com" },
|
||||
},
|
||||
},
|
||||
secondary: {
|
||||
label: { string_id: "btn_cancel_id" },
|
||||
action: { type: "CANCEL" },
|
||||
},
|
||||
secondary: [
|
||||
{
|
||||
label: { string_id: "btn_cancel_id" },
|
||||
action: { type: "CANCEL" },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const { ASRouterTargeting } = ChromeUtils.import(
|
||||
"resource://activity-stream/lib/ASRouterTargeting.jsm"
|
||||
);
|
||||
|
||||
add_task(async function should_ignore_rejections() {
|
||||
let target = {
|
||||
get foo() {
|
||||
return new Promise(resolve => resolve(1));
|
||||
},
|
||||
|
||||
get bar() {
|
||||
return new Promise((resolve, reject) => reject(new Error("unspecified")));
|
||||
},
|
||||
};
|
||||
|
||||
let snapshot = await ASRouterTargeting.getEnvironmentSnapshot(target);
|
||||
deepEqual(snapshot, { environment: { foo: 1 }, version: 1 });
|
||||
});
|
|
@ -18,6 +18,7 @@ skip-if =
|
|||
[test_AboutWelcomeAttribution.js]
|
||||
[test_ASRouterTargeting_attribution.js]
|
||||
skip-if = toolkit != "cocoa" # osx specific tests
|
||||
[test_ASRouterTargeting_snapshot.js]
|
||||
[test_AboutWelcomeTelemetry.js]
|
||||
[test_OnboardingMessageProvider.js]
|
||||
[test_PanelTestProvider.js]
|
||||
|
|
|
@ -47,7 +47,10 @@ function Article(props) {
|
|||
openInPocketReader,
|
||||
} = props;
|
||||
|
||||
const url = new URL(article.url || article.resolved_url || "");
|
||||
if (!article.url && !article.resolved_url && !article.given_url) {
|
||||
return null;
|
||||
}
|
||||
const url = new URL(article.url || article.resolved_url || article.given_url);
|
||||
const urlSearchParams = new URLSearchParams(utmParams);
|
||||
|
||||
if (
|
||||
|
@ -67,7 +70,7 @@ function Article(props) {
|
|||
article.thumbnail ||
|
||||
encodeThumbnail(article?.top_image_url || article?.images?.["1"]?.src);
|
||||
const alt = article.alt || "thumbnail image";
|
||||
const title = article.title || article.resolved_title;
|
||||
const title = article.title || article.resolved_title || article.given_title;
|
||||
// Sometimes domain_metadata is not there, depending on the source.
|
||||
const publisher =
|
||||
article.publisher ||
|
||||
|
|
|
@ -173,7 +173,12 @@ function Article(props) {
|
|||
utmParams,
|
||||
openInPocketReader
|
||||
} = props;
|
||||
const url = new URL(article.url || article.resolved_url || "");
|
||||
|
||||
if (!article.url && !article.resolved_url && !article.given_url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const url = new URL(article.url || article.resolved_url || article.given_url);
|
||||
const urlSearchParams = new URLSearchParams(utmParams);
|
||||
|
||||
if (openInPocketReader && article.item_id && !url.href.match(/getpocket\.com\/read/)) {
|
||||
|
@ -187,7 +192,7 @@ function Article(props) {
|
|||
|
||||
const thumbnail = article.thumbnail || encodeThumbnail(article?.top_image_url || article?.images?.["1"]?.src);
|
||||
const alt = article.alt || "thumbnail image";
|
||||
const title = article.title || article.resolved_title; // Sometimes domain_metadata is not there, depending on the source.
|
||||
const title = article.title || article.resolved_title || article.given_title; // Sometimes domain_metadata is not there, depending on the source.
|
||||
|
||||
const publisher = article.publisher || article.domain_metadata?.name || article.resolved_domain;
|
||||
return /*#__PURE__*/react.createElement("li", {
|
||||
|
|
|
@ -636,8 +636,8 @@ Section "Uninstall"
|
|||
${If} ${FileExists} "$INSTDIR\installation_telemetry.json"
|
||||
Delete /REBOOTOK "$INSTDIR\installation_telemetry.json"
|
||||
${EndIf}
|
||||
${If} ${FileExists} "$INSTDIR\postSigningData.json"
|
||||
Delete /REBOOTOK "$INSTDIR\postSigningData.json"
|
||||
${If} ${FileExists} "$INSTDIR\postSigningData"
|
||||
Delete /REBOOTOK "$INSTDIR\postSigningData"
|
||||
${EndIf}
|
||||
|
||||
; Explicitly remove empty webapprt dir in case it exists (bug 757978).
|
||||
|
|
|
@ -383,7 +383,7 @@ const BuiltInThemeConfig = new Map([
|
|||
[
|
||||
"activist-balanced-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.1",
|
||||
version: "1.1.1",
|
||||
path: "resource://builtin-themes/colorways/2022activist/balanced/",
|
||||
collection: "independent-voices",
|
||||
l10nId: {
|
||||
|
@ -425,7 +425,7 @@ const BuiltInThemeConfig = new Map([
|
|||
[
|
||||
"dreamer-balanced-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.1",
|
||||
version: "1.1.1",
|
||||
path: "resource://builtin-themes/colorways/2022dreamer/balanced/",
|
||||
collection: "independent-voices",
|
||||
l10nId: {
|
||||
|
|
|
@ -7,11 +7,15 @@
|
|||
},
|
||||
"name": "Activist – Balanced",
|
||||
"author": "Mozilla",
|
||||
"version": "1.1",
|
||||
"version": "1.1.1",
|
||||
"icons": {
|
||||
"32": "icon.svg"
|
||||
},
|
||||
"theme": {
|
||||
"properties": {
|
||||
"color_scheme": "light",
|
||||
"content_color_scheme": "auto"
|
||||
},
|
||||
"colors": {
|
||||
"tab_background_text": "hsl(0, 0%, 100%)",
|
||||
"tab_text": "hsl(0, 0%, 0%)",
|
||||
|
|
|
@ -7,11 +7,15 @@
|
|||
},
|
||||
"name": "Dreamer – Balanced",
|
||||
"author": "Mozilla",
|
||||
"version": "1.1",
|
||||
"version": "1.1.1",
|
||||
"icons": {
|
||||
"32": "icon.svg"
|
||||
},
|
||||
"theme": {
|
||||
"properties": {
|
||||
"color_scheme": "light",
|
||||
"content_color_scheme": "auto"
|
||||
},
|
||||
"colors": {
|
||||
"tab_background_text": "hsl(0, 0%, 100%)",
|
||||
"tab_text": "hsl(0, 0%, 0%)",
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
#include "mozilla/ContentBlockingAllowList.h"
|
||||
#include "mozilla/ContentBlockingNotifier.h"
|
||||
#include "mozilla/ContentBlockingUserInteraction.h"
|
||||
#include "mozilla/ContentPrincipal.h"
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/DocLoadingTimelineMarker.h"
|
||||
|
@ -16795,7 +16796,14 @@ Document::GetContentBlockingEvents() {
|
|||
RefPtr<MozPromise<int, bool, true>> Document::RequestStorageAccessAsyncHelper(
|
||||
nsPIDOMWindowInner* aInnerWindow, BrowsingContext* aBrowsingContext,
|
||||
nsIPrincipal* aPrincipal, bool aHasUserInteraction,
|
||||
ContentBlockingNotifier::StorageAccessPermissionGrantedReason aNotifier) {
|
||||
ContentBlockingNotifier::StorageAccessPermissionGrantedReason aNotifier,
|
||||
bool requireFinalChecks) {
|
||||
if (!requireFinalChecks) {
|
||||
// Try to allow access for the given principal.
|
||||
return StorageAccessAPIHelper::AllowAccessFor(aPrincipal, aBrowsingContext,
|
||||
aNotifier);
|
||||
}
|
||||
|
||||
RefPtr<Document> self(this);
|
||||
RefPtr<nsPIDOMWindowInner> inner(aInnerWindow);
|
||||
RefPtr<nsIPrincipal> principal(aPrincipal);
|
||||
|
@ -17031,7 +17039,8 @@ already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccess(
|
|||
// on work changing state to reflect the result of the API. If it resolves,
|
||||
// the request was granted. If it rejects it was denied.
|
||||
RequestStorageAccessAsyncHelper(inner, bc, NodePrincipal(), true,
|
||||
ContentBlockingNotifier::eStorageAccessAPI)
|
||||
ContentBlockingNotifier::eStorageAccessAPI,
|
||||
true)
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[self, inner, promise] {
|
||||
|
@ -17186,7 +17195,8 @@ already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccessForOrigin(
|
|||
// typical requestStorageAccess function.
|
||||
return self->RequestStorageAccessAsyncHelper(
|
||||
inner, bc, principal, hasUserActivation,
|
||||
ContentBlockingNotifier::ePrivilegeStorageAccessForOriginAPI);
|
||||
ContentBlockingNotifier::ePrivilegeStorageAccessForOriginAPI,
|
||||
true);
|
||||
},
|
||||
// If the IPC rejects, we should reject our promise here which will
|
||||
// cause a rejection of the promise we already returned
|
||||
|
@ -17212,6 +17222,271 @@ already_AddRefed<mozilla::dom::Promise> Document::RequestStorageAccessForOrigin(
|
|||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> Document::RequestStorageAccessUnderSite(
|
||||
const nsAString& aSerializedSite, ErrorResult& aRv) {
|
||||
nsIGlobalObject* global = GetScopeObject();
|
||||
if (!global) {
|
||||
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
|
||||
return nullptr;
|
||||
}
|
||||
RefPtr<Promise> promise = Promise::Create(global, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Check that we have user activation before proceeding to prevent
|
||||
// rapid calls to the API to leak information.
|
||||
if (!ConsumeTransientUserGestureActivation()) {
|
||||
// Report an error to the console for this case
|
||||
nsContentUtils::ReportToConsole(
|
||||
nsIScriptError::errorFlag, "requestStorageAccess"_ns, this,
|
||||
nsContentUtils::eDOM_PROPERTIES, "RequestStorageAccessUserGesture");
|
||||
promise->MaybeRejectWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
// Check if the provided URI is different-site to this Document
|
||||
nsCOMPtr<nsIURI> siteURI;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(siteURI), aSerializedSite);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
promise->MaybeRejectWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
bool isCrossSiteArgument;
|
||||
rv = NodePrincipal()->IsThirdPartyURI(siteURI, &isCrossSiteArgument);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(rv);
|
||||
return nullptr;
|
||||
}
|
||||
if (!isCrossSiteArgument) {
|
||||
promise->MaybeRejectWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
// Check if this party has broad cookie permissions.
|
||||
Maybe<bool> resultBecauseCookiesApproved =
|
||||
StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI(
|
||||
CookieJarSettings(), NodePrincipal());
|
||||
if (resultBecauseCookiesApproved.isSome()) {
|
||||
if (resultBecauseCookiesApproved.value()) {
|
||||
promise->MaybeResolveWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
promise->MaybeRejectWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
// Check if browser settings preclude this document getting storage
|
||||
// access under the provided site
|
||||
bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(this);
|
||||
Maybe<bool> resultBecauseBrowserSettings =
|
||||
StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
|
||||
CookieJarSettings(), true, isOnRejectForeignAllowList, false, true);
|
||||
if (resultBecauseBrowserSettings.isSome()) {
|
||||
if (resultBecauseBrowserSettings.value()) {
|
||||
promise->MaybeResolveWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
promise->MaybeRejectWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
// Check that this Document is same-site to the top
|
||||
Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
|
||||
CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
|
||||
if (resultBecauseCallContext.isSome()) {
|
||||
if (resultBecauseCallContext.value()) {
|
||||
promise->MaybeResolveWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
promise->MaybeRejectWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
// Set a permission in the parent process that this document wants storage
|
||||
// access under the argument's site, resolving our returned promise on success
|
||||
ContentChild* cc = ContentChild::GetSingleton();
|
||||
if (!cc) {
|
||||
// TODO(bug 1778561): Make this work in non-content processes.
|
||||
promise->MaybeRejectWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
cc->SendSetAllowStorageAccessRequestFlag(NodePrincipal(), siteURI)
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[promise](bool success) {
|
||||
if (success) {
|
||||
promise->MaybeResolveWithUndefined();
|
||||
} else {
|
||||
promise->MaybeRejectWithUndefined();
|
||||
}
|
||||
},
|
||||
[promise](mozilla::ipc::ResponseRejectReason reason) {
|
||||
promise->MaybeRejectWithUndefined();
|
||||
});
|
||||
|
||||
// Return the promise that is resolved in the async handler above
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> Document::CompleteStorageAccessRequestFromSite(
|
||||
const nsAString& aSerializedOrigin, ErrorResult& aRv) {
|
||||
nsIGlobalObject* global = GetScopeObject();
|
||||
if (!global) {
|
||||
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
|
||||
return nullptr;
|
||||
}
|
||||
RefPtr<Promise> promise = Promise::Create(global, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Check that the provided URI is different-site to this Document
|
||||
nsCOMPtr<nsIURI> argumentURI;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(argumentURI), aSerializedOrigin);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
promise->MaybeRejectWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
bool isCrossSiteArgument;
|
||||
rv = NodePrincipal()->IsThirdPartyURI(argumentURI, &isCrossSiteArgument);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(rv);
|
||||
return nullptr;
|
||||
}
|
||||
if (!isCrossSiteArgument) {
|
||||
promise->MaybeRejectWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
// Check if browser settings preclude this document getting storage
|
||||
// access under the provided site
|
||||
bool isOnRejectForeignAllowList = RejectForeignAllowList::Check(argumentURI);
|
||||
Maybe<bool> resultBecauseBrowserSettings =
|
||||
StorageAccessAPIHelper::CheckBrowserSettingsDecidesStorageAccessAPI(
|
||||
CookieJarSettings(), true, isOnRejectForeignAllowList, false, true);
|
||||
if (resultBecauseBrowserSettings.isSome()) {
|
||||
if (resultBecauseBrowserSettings.value()) {
|
||||
promise->MaybeResolveWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
promise->MaybeRejectWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
// Check that this Document is same-site to the top
|
||||
Maybe<bool> resultBecauseCallContext = StorageAccessAPIHelper::
|
||||
CheckSameSiteCallingContextDecidesStorageAccessAPI(this, false);
|
||||
if (resultBecauseCallContext.isSome()) {
|
||||
if (resultBecauseCallContext.value()) {
|
||||
promise->MaybeResolveWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
promise->MaybeRejectWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
// Create principal of the embedded site requesting storage access
|
||||
nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
|
||||
argumentURI, NodePrincipal()->OriginAttributesRef());
|
||||
if (!principal) {
|
||||
promise->MaybeRejectWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
// Get versions of these objects that we can use in lambdas for callbacks
|
||||
RefPtr<Document> self(this);
|
||||
RefPtr<BrowsingContext> bc = GetBrowsingContext();
|
||||
nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
|
||||
|
||||
// Test that the permission was set by a call to RequestStorageAccessUnderSite
|
||||
// from a top level document that is same-site with the argument
|
||||
ContentChild* cc = ContentChild::GetSingleton();
|
||||
if (!cc) {
|
||||
// TODO(bug 1778561): Make this work in non-content processes.
|
||||
promise->MaybeRejectWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
cc->SendTestAllowStorageAccessRequestFlag(NodePrincipal(), argumentURI)
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[inner, bc, self, principal](bool success) {
|
||||
if (success) {
|
||||
// If that resolved with true, check that we don't already have a
|
||||
// permission that gives cookie access.
|
||||
return StorageAccessAPIHelper::
|
||||
AsyncCheckCookiesPermittedDecidesStorageAccessAPI(bc,
|
||||
principal);
|
||||
}
|
||||
return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
|
||||
NS_ERROR_FAILURE, __func__);
|
||||
},
|
||||
[](mozilla::ipc::ResponseRejectReason reason) {
|
||||
return MozPromise<Maybe<bool>, nsresult, true>::CreateAndReject(
|
||||
NS_ERROR_FAILURE, __func__);
|
||||
})
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[inner, bc, principal, self, promise](Maybe<bool> cookieResult) {
|
||||
// Handle the result of the cookie permission check that took place
|
||||
// in the ContentParent.
|
||||
if (cookieResult.isSome()) {
|
||||
if (cookieResult.value()) {
|
||||
return StorageAccessAPIHelper::
|
||||
StorageAccessPermissionGrantPromise::CreateAndResolve(
|
||||
StorageAccessAPIHelper::eAllowAutoGrant, __func__);
|
||||
}
|
||||
return StorageAccessAPIHelper::
|
||||
StorageAccessPermissionGrantPromise::CreateAndReject(
|
||||
false, __func__);
|
||||
}
|
||||
|
||||
// Check for the existing storage access permission
|
||||
nsAutoCString type;
|
||||
bool ok =
|
||||
AntiTrackingUtils::CreateStoragePermissionKey(principal, type);
|
||||
if (!ok) {
|
||||
return StorageAccessAPIHelper::
|
||||
StorageAccessPermissionGrantPromise::CreateAndReject(
|
||||
false, __func__);
|
||||
}
|
||||
if (AntiTrackingUtils::CheckStoragePermission(
|
||||
self->NodePrincipal(), type,
|
||||
nsContentUtils::IsInPrivateBrowsing(self), nullptr, 0)) {
|
||||
return StorageAccessAPIHelper::
|
||||
StorageAccessPermissionGrantPromise::CreateAndResolve(
|
||||
StorageAccessAPIHelper::eAllowAutoGrant, __func__);
|
||||
}
|
||||
|
||||
// Try to request storage access, ignoring the final checks.
|
||||
// We ignore the final checks because this is where the "grant"
|
||||
// either by prompt doorhanger or autogrant takes place. We already
|
||||
// gathered an equivalent grant in requestStorageAccessUnderSite.
|
||||
return self->RequestStorageAccessAsyncHelper(
|
||||
inner, bc, principal, true,
|
||||
ContentBlockingNotifier::eStorageAccessAPI, false);
|
||||
},
|
||||
// If the IPC rejects, we should reject our promise here which will
|
||||
// cause a rejection of the promise we already returned
|
||||
[promise]() {
|
||||
return MozPromise<int, bool, true>::CreateAndReject(false,
|
||||
__func__);
|
||||
})
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
// If the previous handlers resolved, we should reinstate user
|
||||
// activation and resolve the promise we returned in Step 5.
|
||||
[self, inner, promise] {
|
||||
inner->SaveStorageAccessPermissionGranted();
|
||||
promise->MaybeResolveWithUndefined();
|
||||
},
|
||||
// If the previous handler rejected, we should reject the promise
|
||||
// returned by this function.
|
||||
[promise] { promise->MaybeRejectWithUndefined(); });
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
RefPtr<Document::AutomaticStorageAccessPermissionGrantPromise>
|
||||
Document::AutomaticStorageAccessPermissionCanBeGranted(bool hasUserActivation) {
|
||||
// requestStorageAccessForOrigin may not require user activation. If we don't
|
||||
|
|
|
@ -1257,13 +1257,19 @@ class Document : public nsINode,
|
|||
RefPtr<MozPromise<int, bool, true>> RequestStorageAccessAsyncHelper(
|
||||
nsPIDOMWindowInner* aInnerWindow, BrowsingContext* aBrowsingContext,
|
||||
nsIPrincipal* aPrincipal, bool aHasUserInteraction,
|
||||
ContentBlockingNotifier::StorageAccessPermissionGrantedReason aNotifier);
|
||||
ContentBlockingNotifier::StorageAccessPermissionGrantedReason aNotifier,
|
||||
bool performFinalChecks);
|
||||
already_AddRefed<Promise> RequestStorageAccess(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<Promise> RequestStorageAccessForOrigin(
|
||||
const nsAString& aThirdPartyOrigin, const bool aRequireUserInteraction,
|
||||
ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<Promise> RequestStorageAccessUnderSite(
|
||||
const nsAString& aSerializedSite, ErrorResult& aRv);
|
||||
already_AddRefed<Promise> CompleteStorageAccessRequestFromSite(
|
||||
const nsAString& aSerializedOrigin, ErrorResult& aRv);
|
||||
|
||||
bool UseRegularPrincipal() const;
|
||||
|
||||
/**
|
||||
|
|
|
@ -5047,7 +5047,14 @@ Storage* nsGlobalWindowInner::GetLocalStorage(ErrorResult& aError) {
|
|||
return mLocalStorage;
|
||||
}
|
||||
|
||||
IDBFactory* nsGlobalWindowInner::GetIndexedDB(ErrorResult& aError) {
|
||||
IDBFactory* nsGlobalWindowInner::GetIndexedDB(JSContext* aCx,
|
||||
ErrorResult& aError) {
|
||||
if (!IDBFactory::IsEnabled(aCx, AsGlobal()->GetGlobalJSObject())) {
|
||||
// Let window.indexedDB be an attribute with a null value, to prevent
|
||||
// undefined identifier error
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!mIndexedDB) {
|
||||
// This may keep mIndexedDB null without setting an error.
|
||||
auto res = IDBFactory::CreateForWindow(this);
|
||||
|
|
|
@ -744,7 +744,8 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
|
|||
mozilla::dom::Storage* GetSessionStorage(mozilla::ErrorResult& aError);
|
||||
mozilla::dom::Storage* GetLocalStorage(mozilla::ErrorResult& aError);
|
||||
mozilla::dom::Selection* GetSelection(mozilla::ErrorResult& aError);
|
||||
mozilla::dom::IDBFactory* GetIndexedDB(mozilla::ErrorResult& aError);
|
||||
mozilla::dom::IDBFactory* GetIndexedDB(JSContext* aCx,
|
||||
mozilla::ErrorResult& aError);
|
||||
already_AddRefed<nsICSSDeclaration> GetComputedStyle(
|
||||
mozilla::dom::Element& aElt, const nsAString& aPseudoElt,
|
||||
mozilla::ErrorResult& aError) override;
|
||||
|
|
|
@ -2,66 +2,107 @@
|
|||
|
||||
const isWorker = typeof DedicatedWorkerGlobalScope === "function";
|
||||
|
||||
function check(content, expected, item) {
|
||||
const exposed = expected ? "is exposed without" : "is not exposed with";
|
||||
const worker = isWorker ? "in worker" : "in window";
|
||||
is(
|
||||
content.eval(`!!globalThis.${item}`),
|
||||
expected,
|
||||
`${item} ${exposed} pbmode ${worker}`
|
||||
);
|
||||
}
|
||||
|
||||
function checkCaches(content, expected) {
|
||||
check(content, expected, "caches");
|
||||
check(content, expected, "Cache");
|
||||
check(content, expected, "CacheStorage");
|
||||
}
|
||||
|
||||
function checkIDB(content, expected) {
|
||||
check(content, expected, "indexedDB");
|
||||
check(content, expected, "IDBFactory");
|
||||
check(content, expected, "IDBKeyRange");
|
||||
check(content, expected, "IDBOpenDBRequest");
|
||||
check(content, expected, "IDBRequest");
|
||||
check(content, expected, "IDBVersionChangeEvent");
|
||||
|
||||
// These are always accessed by jakearchibald/idb@v3 without existence checks.
|
||||
// https://github.com/jakearchibald/idb/blob/e1c7c44dbba38415745afc782b8e247da8c833f2/lib/idb.mjs#L152
|
||||
check(content, true, "IDBCursor");
|
||||
check(content, true, "IDBDatabase");
|
||||
check(content, true, "IDBIndex");
|
||||
check(content, true, "IDBObjectStore");
|
||||
check(content, true, "IDBTransaction");
|
||||
}
|
||||
|
||||
function checkSW(content, expected) {
|
||||
if (isWorker) {
|
||||
// Currently not supported. Bug 1131324
|
||||
return;
|
||||
function checkAll(content, inPrivateBrowsing) {
|
||||
function check(
|
||||
item,
|
||||
{ valueExpected, enumerationExpected, parent = "globalThis" }
|
||||
) {
|
||||
const exposed = valueExpected ? "is exposed" : "is not exposed";
|
||||
const pbmode = inPrivateBrowsing ? "with pbmode" : "without pbmode";
|
||||
const enumerated = enumerationExpected
|
||||
? "is enumerated"
|
||||
: "is not enumerated";
|
||||
const worker = isWorker ? "in worker" : "in window";
|
||||
is(
|
||||
content.eval(`!!${parent}.${item}`),
|
||||
valueExpected,
|
||||
`${parent}.${item} ${exposed} ${pbmode} ${worker}`
|
||||
);
|
||||
is(
|
||||
content.eval(`"${item}" in ${parent}`),
|
||||
enumerationExpected,
|
||||
`${parent}.${item} ${enumerated} ${pbmode} ${worker}`
|
||||
);
|
||||
}
|
||||
check(content, expected, "navigator.serviceWorker");
|
||||
check(content, expected, "ServiceWorker");
|
||||
check(content, expected, "ServiceWorkerContainer");
|
||||
check(content, expected, "ServiceWorkerRegistration");
|
||||
check(content, expected, "NavigationPreloadManager");
|
||||
check(content, expected, "PushManager");
|
||||
check(content, expected, "PushSubscription");
|
||||
check(content, expected, "PushSubscriptionOptions");
|
||||
}
|
||||
|
||||
function checkAll(content, expected) {
|
||||
checkCaches(content, expected);
|
||||
checkIDB(content, expected);
|
||||
checkSW(content, expected);
|
||||
function checkNotExposedInPBM(item, parent) {
|
||||
check(item, {
|
||||
valueExpected: !inPrivateBrowsing,
|
||||
enumerationExpected: !inPrivateBrowsing,
|
||||
parent,
|
||||
});
|
||||
}
|
||||
|
||||
function checkCaches() {
|
||||
checkNotExposedInPBM("caches");
|
||||
checkNotExposedInPBM("Cache");
|
||||
checkNotExposedInPBM("CacheStorage");
|
||||
}
|
||||
|
||||
function checkIDB() {
|
||||
checkNotExposedInPBM("IDBFactory");
|
||||
checkNotExposedInPBM("IDBKeyRange");
|
||||
checkNotExposedInPBM("IDBOpenDBRequest");
|
||||
checkNotExposedInPBM("IDBRequest");
|
||||
checkNotExposedInPBM("IDBVersionChangeEvent");
|
||||
|
||||
// These are always accessed by jakearchibald/idb@v3 without existence checks.
|
||||
// https://github.com/jakearchibald/idb/blob/e1c7c44dbba38415745afc782b8e247da8c833f2/lib/idb.mjs#L152
|
||||
check("IDBCursor", {
|
||||
valueExpected: true,
|
||||
enumerationExpected: true,
|
||||
});
|
||||
check("IDBDatabase", {
|
||||
valueExpected: true,
|
||||
enumerationExpected: true,
|
||||
});
|
||||
check("IDBIndex", {
|
||||
valueExpected: true,
|
||||
enumerationExpected: true,
|
||||
});
|
||||
check("IDBObjectStore", {
|
||||
valueExpected: true,
|
||||
enumerationExpected: true,
|
||||
});
|
||||
check("IDBTransaction", {
|
||||
valueExpected: true,
|
||||
enumerationExpected: true,
|
||||
});
|
||||
|
||||
// https://www.msn.com/feed accesses indexedDB as a global variable without existence check
|
||||
// We need to always expose the attribute itself
|
||||
check("indexedDB", {
|
||||
valueExpected: !inPrivateBrowsing,
|
||||
enumerationExpected: true,
|
||||
});
|
||||
}
|
||||
|
||||
function checkSW() {
|
||||
if (isWorker) {
|
||||
// Currently not supported. Bug 1131324
|
||||
return;
|
||||
}
|
||||
checkNotExposedInPBM("serviceWorker", "navigator");
|
||||
checkNotExposedInPBM("ServiceWorker");
|
||||
checkNotExposedInPBM("ServiceWorkerContainer");
|
||||
checkNotExposedInPBM("ServiceWorkerRegistration");
|
||||
checkNotExposedInPBM("NavigationPreloadManager");
|
||||
checkNotExposedInPBM("PushManager");
|
||||
checkNotExposedInPBM("PushSubscription");
|
||||
checkNotExposedInPBM("PushSubscriptionOptions");
|
||||
}
|
||||
|
||||
checkCaches();
|
||||
checkIDB();
|
||||
checkSW();
|
||||
}
|
||||
|
||||
if (isWorker) {
|
||||
importScripts("/tests/SimpleTest/WorkerSimpleTest.js");
|
||||
|
||||
globalThis.onmessage = ev => {
|
||||
const { expected } = ev.data;
|
||||
checkAll(globalThis, expected);
|
||||
const { inPrivateBrowsing } = ev.data;
|
||||
checkAll(globalThis, inPrivateBrowsing);
|
||||
postMessage({
|
||||
kind: "info",
|
||||
next: true,
|
||||
|
|
|
@ -27,11 +27,11 @@
|
|||
});
|
||||
}
|
||||
|
||||
function runWorkerTest(content, expected) {
|
||||
function runWorkerTest(content, inPrivateBrowsing) {
|
||||
return new Promise((resolve, reject) => {
|
||||
/** @type {Worker} */
|
||||
const worker = content.eval("new Worker('/chrome/dom/base/test/chrome/file_hide_in_pbmode.js')");
|
||||
worker.postMessage({ expected });
|
||||
worker.postMessage({ inPrivateBrowsing });
|
||||
worker.onerror = reject;
|
||||
listenForTests(worker);
|
||||
worker.addEventListener("message", ev => {
|
||||
|
@ -46,13 +46,13 @@
|
|||
async function runTest() {
|
||||
// sanity check
|
||||
let win = await openBrowserWindow(contentPage, { private: false });
|
||||
checkAll(win.content, true);
|
||||
await runWorkerTest(win.content, true);
|
||||
checkAll(win.content, false);
|
||||
await runWorkerTest(win.content, false);
|
||||
win.close();
|
||||
|
||||
win = await openBrowserWindow(contentPage, { private: true });
|
||||
checkAll(win.content, false);
|
||||
await runWorkerTest(win.content, false);
|
||||
checkAll(win.content, true);
|
||||
await runWorkerTest(win.content, true);
|
||||
win.close();
|
||||
|
||||
SimpleTest.finish();
|
||||
|
|
|
@ -1600,7 +1600,7 @@ DOMInterfaces = {
|
|||
'nativeType': 'nsGlobalWindowInner',
|
||||
'headerFile': 'nsGlobalWindow.h',
|
||||
'implicitJSContext': [
|
||||
'requestIdleCallback'
|
||||
'requestIdleCallback', 'indexedDB'
|
||||
],
|
||||
},
|
||||
|
||||
|
@ -1627,7 +1627,7 @@ DOMInterfaces = {
|
|||
|
||||
'WorkerGlobalScope': {
|
||||
'headerFile': 'mozilla/dom/WorkerScope.h',
|
||||
'implicitJSContext': [ 'importScripts' ],
|
||||
'implicitJSContext': [ 'importScripts', 'indexedDB' ],
|
||||
},
|
||||
|
||||
'Worklet': {
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
#include "OffscreenCanvasRenderingContext2D.h"
|
||||
#include "mozilla/CycleCollectedJSRuntime.h"
|
||||
#include "mozilla/dom/FontFaceSetImpl.h"
|
||||
#include "mozilla/dom/FontFaceSet.h"
|
||||
#include "mozilla/dom/OffscreenCanvasRenderingContext2DBinding.h"
|
||||
#include "mozilla/dom/OffscreenCanvas.h"
|
||||
#include "mozilla/dom/WorkerCommon.h"
|
||||
|
@ -125,6 +127,17 @@ static void SerializeFontForCanvas(const StyleFontFamilyList& aList,
|
|||
|
||||
bool OffscreenCanvasRenderingContext2D::SetFontInternal(const nsACString& aFont,
|
||||
ErrorResult& aError) {
|
||||
nsIGlobalObject* global = GetParentObject();
|
||||
FontFaceSet* fontFaceSet = global ? global->Fonts() : nullptr;
|
||||
FontFaceSetImpl* fontFaceSetImpl =
|
||||
fontFaceSet ? fontFaceSet->GetImpl() : nullptr;
|
||||
RefPtr<URLExtraData> urlExtraData =
|
||||
fontFaceSetImpl ? fontFaceSetImpl->GetURLExtraData() : nullptr;
|
||||
|
||||
if (fontFaceSetImpl) {
|
||||
fontFaceSetImpl->FlushUserFontSet();
|
||||
}
|
||||
|
||||
// In the OffscreenCanvas case we don't have the context necessary to call
|
||||
// GetFontStyleForServo(), as we do in the main-thread canvas context, so
|
||||
// instead we borrow ParseFontShorthandForMatching to parse the attribute.
|
||||
|
@ -134,7 +147,7 @@ bool OffscreenCanvasRenderingContext2D::SetFontInternal(const nsACString& aFont,
|
|||
gfxFontStyle fontStyle;
|
||||
float size = 0.0f;
|
||||
if (!ServoCSSParser::ParseFontShorthandForMatching(
|
||||
aFont, nullptr, list, fontStyle.style, fontStyle.stretch,
|
||||
aFont, urlExtraData, list, fontStyle.style, fontStyle.stretch,
|
||||
fontStyle.weight, &size)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -144,15 +157,15 @@ bool OffscreenCanvasRenderingContext2D::SetFontInternal(const nsACString& aFont,
|
|||
// TODO: Get a userFontSet from the Worker and pass to the fontGroup
|
||||
// TODO: Should we be passing a language? Where from?
|
||||
// TODO: Cache fontGroups in the Worker (use an nsFontCache?)
|
||||
gfxFontGroup* fontGroup =
|
||||
gfxPlatform::GetPlatform()->CreateFontGroup(nullptr, // aPresContext
|
||||
list, // aFontFamilyList
|
||||
&fontStyle, // aStyle
|
||||
nullptr, // aLanguage
|
||||
false, // aExplicitLanguage
|
||||
nullptr, // aTextPerf
|
||||
nullptr, // aUserFontSet
|
||||
1.0); // aDevToCssSize
|
||||
gfxFontGroup* fontGroup = gfxPlatform::GetPlatform()->CreateFontGroup(
|
||||
nullptr, // aPresContext
|
||||
list, // aFontFamilyList
|
||||
&fontStyle, // aStyle
|
||||
nullptr, // aLanguage
|
||||
false, // aExplicitLanguage
|
||||
nullptr, // aTextPerf
|
||||
fontFaceSetImpl, // aUserFontSet
|
||||
1.0); // aDevToCssSize
|
||||
CurrentState().fontGroup = fontGroup;
|
||||
SerializeFontForCanvas(list, fontStyle, CurrentState().font);
|
||||
CurrentState().fontFont = nsFont(StyleFontFamily{list, false, false},
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
#include "mozilla/ipc/URIUtils.h"
|
||||
#include "gfxPlatform.h"
|
||||
#include "gfxPlatformFontList.h"
|
||||
#include "mozilla/AntiTrackingUtils.h"
|
||||
#include "mozilla/AppShutdown.h"
|
||||
#include "mozilla/AutoRestore.h"
|
||||
#include "mozilla/BasePrincipal.h"
|
||||
|
@ -6500,6 +6501,93 @@ mozilla::ipc::IPCResult ContentParent::RecvCompleteAllowAccessFor(
|
|||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult ContentParent::RecvSetAllowStorageAccessRequestFlag(
|
||||
nsIPrincipal* aEmbeddedPrincipal, nsIURI* aEmbeddingOrigin,
|
||||
SetAllowStorageAccessRequestFlagResolver&& aResolver) {
|
||||
MOZ_ASSERT(aEmbeddedPrincipal);
|
||||
MOZ_ASSERT(aEmbeddingOrigin);
|
||||
|
||||
// Get the permission manager and build the key.
|
||||
RefPtr<PermissionManager> permManager = PermissionManager::GetInstance();
|
||||
if (!permManager) {
|
||||
aResolver(false);
|
||||
return IPC_OK();
|
||||
}
|
||||
nsCOMPtr<nsIURI> embeddedURI = aEmbeddedPrincipal->GetURI();
|
||||
nsCString permissionKey;
|
||||
bool success = AntiTrackingUtils::CreateStorageRequestPermissionKey(
|
||||
embeddedURI, permissionKey);
|
||||
if (!success) {
|
||||
aResolver(false);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
// Set the permission to ALLOW for a prefence specified amount of seconds.
|
||||
// Time units are inconsistent, be careful
|
||||
int64_t when = (PR_Now() / PR_USEC_PER_MSEC) +
|
||||
StaticPrefs::dom_storage_access_forward_declared_lifetime() *
|
||||
PR_MSEC_PER_SEC;
|
||||
nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
|
||||
aEmbeddingOrigin, aEmbeddedPrincipal->OriginAttributesRef());
|
||||
nsresult rv = permManager->AddFromPrincipal(
|
||||
principal, permissionKey, nsIPermissionManager::ALLOW_ACTION,
|
||||
nsIPermissionManager::EXPIRE_TIME, when);
|
||||
if (NS_FAILED(rv)) {
|
||||
aResolver(false);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
// Resolve with success if we set the permission.
|
||||
aResolver(true);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult ContentParent::RecvTestAllowStorageAccessRequestFlag(
|
||||
nsIPrincipal* aEmbeddingPrincipal, nsIURI* aEmbeddedOrigin,
|
||||
TestAllowStorageAccessRequestFlagResolver&& aResolver) {
|
||||
MOZ_ASSERT(aEmbeddingPrincipal);
|
||||
MOZ_ASSERT(aEmbeddedOrigin);
|
||||
|
||||
// Get the permission manager and build the key.
|
||||
RefPtr<PermissionManager> permManager = PermissionManager::GetInstance();
|
||||
if (!permManager) {
|
||||
aResolver(false);
|
||||
return IPC_OK();
|
||||
}
|
||||
nsCString requestPermissionKey;
|
||||
bool success = AntiTrackingUtils::CreateStorageRequestPermissionKey(
|
||||
aEmbeddedOrigin, requestPermissionKey);
|
||||
if (!success) {
|
||||
aResolver(false);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
// Get the permission and resolve false if it is not set to ALLOW.
|
||||
uint32_t access = nsIPermissionManager::UNKNOWN_ACTION;
|
||||
nsresult rv = permManager->TestPermissionFromPrincipal(
|
||||
aEmbeddingPrincipal, requestPermissionKey, &access);
|
||||
if (NS_FAILED(rv)) {
|
||||
aResolver(false);
|
||||
return IPC_OK();
|
||||
}
|
||||
if (access != nsIPermissionManager::ALLOW_ACTION) {
|
||||
aResolver(false);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
// Remove the permission, failing if the permission manager fails
|
||||
rv = permManager->RemoveFromPrincipal(aEmbeddingPrincipal,
|
||||
requestPermissionKey);
|
||||
if (NS_FAILED(rv)) {
|
||||
aResolver(false);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
// At this point, signal to our caller that the permission was set
|
||||
aResolver(true);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult ContentParent::RecvStoreUserInteractionAsPermission(
|
||||
const Principal& aPrincipal) {
|
||||
if (!ValidatePrincipal(aPrincipal)) {
|
||||
|
|
|
@ -1259,6 +1259,14 @@ class ContentParent final : public PContentParent,
|
|||
aReason,
|
||||
CompleteAllowAccessForResolver&& aResolver);
|
||||
|
||||
mozilla::ipc::IPCResult RecvSetAllowStorageAccessRequestFlag(
|
||||
nsIPrincipal* aEmbeddedPrincipal, nsIURI* aEmbeddingOrigin,
|
||||
SetAllowStorageAccessRequestFlagResolver&& aResolver);
|
||||
|
||||
mozilla::ipc::IPCResult RecvTestAllowStorageAccessRequestFlag(
|
||||
nsIPrincipal* aEmbeddingPrincipal, nsIURI* aEmbeddedOrigin,
|
||||
TestAllowStorageAccessRequestFlagResolver&& aResolver);
|
||||
|
||||
mozilla::ipc::IPCResult RecvStoreUserInteractionAsPermission(
|
||||
const Principal& aPrincipal);
|
||||
|
||||
|
|
|
@ -1650,6 +1650,16 @@ parent:
|
|||
StorageAccessPermissionGrantedReason aReason)
|
||||
returns (StorageAccessPromptChoices? choice);
|
||||
|
||||
async SetAllowStorageAccessRequestFlag(
|
||||
nsIPrincipal aEmbeddingPrincipal,
|
||||
nsIURI aEmbeddedOrigin)
|
||||
returns (bool success);
|
||||
|
||||
async TestAllowStorageAccessRequestFlag(
|
||||
nsIPrincipal aEmbeddedPrincipal,
|
||||
nsIURI aEmbeddingOrigin)
|
||||
returns (bool success);
|
||||
|
||||
async StoreUserInteractionAsPermission(Principal aPrincipal);
|
||||
|
||||
async TestCookiePermissionDecided(MaybeDiscardedBrowsingContext aContext,
|
||||
|
|
|
@ -305,6 +305,8 @@ var interfaceNamesInGlobalScope = [
|
|||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"TextEncoder",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"TextMetrics",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"TransformStream",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"TransformStreamDefaultController",
|
||||
|
|
|
@ -362,7 +362,7 @@ interface CanvasPattern {
|
|||
void setTransform(optional DOMMatrix2DInit matrix = {});
|
||||
};
|
||||
|
||||
[Exposed=Window]
|
||||
[Exposed=(Window,Worker)]
|
||||
interface TextMetrics {
|
||||
|
||||
// x-direction
|
||||
|
|
|
@ -544,6 +544,11 @@ partial interface Document {
|
|||
Promise<boolean> hasStorageAccess();
|
||||
[Pref="dom.storage_access.enabled", NewObject]
|
||||
Promise<void> requestStorageAccess();
|
||||
// https://github.com/privacycg/storage-access/pull/100
|
||||
[Pref="dom.storage_access.forward_declared.enabled", NewObject]
|
||||
Promise<void> requestStorageAccessUnderSite(DOMString serializedSite);
|
||||
[Pref="dom.storage_access.forward_declared.enabled", NewObject]
|
||||
Promise<void> completeStorageAccessRequestFromSite(DOMString serializedSite);
|
||||
};
|
||||
|
||||
// A privileged API to give chrome privileged code and the content script of the
|
||||
|
|
|
@ -24,7 +24,9 @@ OffscreenCanvasRenderingContext2D includes CanvasFillStrokeStyles;
|
|||
OffscreenCanvasRenderingContext2D includes CanvasShadowStyles;
|
||||
OffscreenCanvasRenderingContext2D includes CanvasRect;
|
||||
OffscreenCanvasRenderingContext2D includes CanvasDrawPath;
|
||||
OffscreenCanvasRenderingContext2D includes CanvasText;
|
||||
OffscreenCanvasRenderingContext2D includes CanvasDrawImage;
|
||||
OffscreenCanvasRenderingContext2D includes CanvasImageData;
|
||||
OffscreenCanvasRenderingContext2D includes CanvasPathDrawingStyles;
|
||||
OffscreenCanvasRenderingContext2D includes CanvasTextDrawingStyles;
|
||||
OffscreenCanvasRenderingContext2D includes CanvasPathMethods;
|
||||
|
|
|
@ -70,7 +70,7 @@ partial interface mixin WindowOrWorkerGlobalScope {
|
|||
// http://w3c.github.io/IndexedDB/#factory-interface
|
||||
partial interface mixin WindowOrWorkerGlobalScope {
|
||||
// readonly attribute IDBFactory indexedDB; // bug 1776789
|
||||
[Throws, Func="IDBFactory::IsEnabled"]
|
||||
[Throws]
|
||||
readonly attribute IDBFactory? indexedDB;
|
||||
};
|
||||
|
||||
|
|
|
@ -676,9 +676,15 @@ already_AddRefed<Promise> WorkerGlobalScope::Fetch(
|
|||
}
|
||||
|
||||
already_AddRefed<IDBFactory> WorkerGlobalScope::GetIndexedDB(
|
||||
ErrorResult& aErrorResult) {
|
||||
JSContext* aCx, ErrorResult& aErrorResult) {
|
||||
AssertIsOnWorkerThread();
|
||||
|
||||
if (!IDBFactory::IsEnabled(aCx, GetGlobalJSObject())) {
|
||||
// Let window.indexedDB be an attribute with a null value, to prevent
|
||||
// undefined identifier error
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<IDBFactory> indexedDB = mIndexedDB;
|
||||
|
||||
if (!indexedDB) {
|
||||
|
|
|
@ -312,7 +312,8 @@ class WorkerGlobalScope : public WorkerGlobalScopeBase {
|
|||
|
||||
bool IsSecureContext() const;
|
||||
|
||||
already_AddRefed<IDBFactory> GetIndexedDB(ErrorResult& aErrorResult);
|
||||
already_AddRefed<IDBFactory> GetIndexedDB(JSContext* aCx,
|
||||
ErrorResult& aErrorResult);
|
||||
|
||||
already_AddRefed<cache::CacheStorage> GetCaches(ErrorResult& aRv);
|
||||
|
||||
|
|
|
@ -289,6 +289,8 @@ var interfaceNamesInGlobalScope = [
|
|||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "TextEncoder", insecureContext: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "TextMetrics", insecureContext: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{ name: "TransformStream", insecureContext: true },
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{
|
||||
|
|
|
@ -169,8 +169,8 @@ bool IsOAForceStripPermission(const nsACString& aType) {
|
|||
// Array of permission prefixes which should be isolated only by site.
|
||||
// These site-scoped permissions are stored under their site's principal.
|
||||
// GetAllForPrincipal also needs to look for these especially.
|
||||
static constexpr std::array<nsLiteralCString, 1> kSiteScopedPermissions = {
|
||||
{"3rdPartyStorage^"_ns}};
|
||||
static constexpr std::array<nsLiteralCString, 2> kSiteScopedPermissions = {
|
||||
{"3rdPartyStorage^"_ns, "AllowStorageAccessRequest^"_ns}};
|
||||
|
||||
bool IsSiteScopedPermission(const nsACString& aType) {
|
||||
if (aType.IsEmpty()) {
|
||||
|
|
|
@ -54,11 +54,24 @@ using namespace mozilla;
|
|||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::unicode;
|
||||
|
||||
void gfxCharacterMap::NotifyReleased() {
|
||||
if (mShared) {
|
||||
gfxPlatformFontList::PlatformFontList()->RemoveCmap(this);
|
||||
nsrefcnt gfxCharacterMap::NotifyMaybeReleased() {
|
||||
auto* pfl = gfxPlatformFontList::PlatformFontList();
|
||||
pfl->Lock();
|
||||
|
||||
// Something may have pulled our raw pointer out of gfxPlatformFontList before
|
||||
// we were able to complete the release.
|
||||
if (mRefCnt > 0) {
|
||||
pfl->Unlock();
|
||||
return mRefCnt;
|
||||
}
|
||||
|
||||
if (mShared) {
|
||||
pfl->RemoveCmap(this);
|
||||
}
|
||||
|
||||
pfl->Unlock();
|
||||
delete this;
|
||||
return 0;
|
||||
}
|
||||
|
||||
gfxFontEntry::gfxFontEntry(const nsACString& aName, bool aIsStandardFace)
|
||||
|
@ -1918,7 +1931,7 @@ void gfxFontFamily::SearchAllFontsForChar(GlobalFontMatch* aMatchData) {
|
|||
gfxFontFamily::~gfxFontFamily() {
|
||||
// Should not be dropped by stylo, but the InitFontList thread might use
|
||||
// a transient gfxFontFamily and that's OK.
|
||||
MOZ_ASSERT(NS_IsMainThread() || gfxPlatformFontList::IsInitFontListThread());
|
||||
MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
|
||||
}
|
||||
|
||||
// returns true if other names were found, false otherwise
|
||||
|
|
|
@ -81,9 +81,9 @@ class gfxCharacterMap : public gfxSparseBitSet {
|
|||
--mRefCnt;
|
||||
NS_LOG_RELEASE(this, mRefCnt, "gfxCharacterMap");
|
||||
if (mRefCnt == 0) {
|
||||
NotifyReleased();
|
||||
// |this| has been deleted.
|
||||
return 0;
|
||||
// Because we have a raw pointer in gfxPlatformFontList that we may race
|
||||
// access with, we may not release here.
|
||||
return NotifyMaybeReleased();
|
||||
}
|
||||
return mRefCnt;
|
||||
}
|
||||
|
@ -112,9 +112,9 @@ class gfxCharacterMap : public gfxSparseBitSet {
|
|||
bool mShared;
|
||||
|
||||
protected:
|
||||
void NotifyReleased();
|
||||
nsrefcnt NotifyMaybeReleased();
|
||||
|
||||
nsAutoRefCnt mRefCnt;
|
||||
mozilla::ThreadSafeAutoRefCnt mRefCnt;
|
||||
|
||||
private:
|
||||
gfxCharacterMap(const gfxCharacterMap&);
|
||||
|
|
|
@ -301,6 +301,20 @@ gfxPlatformFontList::gfxPlatformFontList(bool aNeedFullnamePostscriptNames)
|
|||
}
|
||||
|
||||
RegisterStrongMemoryReporter(new MemoryReporter());
|
||||
|
||||
// initialize lang group pref font defaults (i.e. serif/sans-serif)
|
||||
mDefaultGenericsLangGroup.AppendElements(ArrayLength(gPrefLangNames));
|
||||
for (uint32_t i = 0; i < ArrayLength(gPrefLangNames); i++) {
|
||||
nsAutoCString prefDefaultFontType("font.default.");
|
||||
prefDefaultFontType.Append(GetPrefLangName(eFontPrefLang(i)));
|
||||
nsAutoCString serifOrSans;
|
||||
Preferences::GetCString(prefDefaultFontType.get(), serifOrSans);
|
||||
if (serifOrSans.EqualsLiteral("sans-serif")) {
|
||||
mDefaultGenericsLangGroup[i] = StyleGenericFontFamily::SansSerif;
|
||||
} else {
|
||||
mDefaultGenericsLangGroup[i] = StyleGenericFontFamily::Serif;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gfxPlatformFontList::~gfxPlatformFontList() {
|
||||
|
@ -1870,12 +1884,13 @@ fontlist::Pointer gfxPlatformFontList::GetShmemCharMapLocked(
|
|||
}
|
||||
|
||||
// lookup cmap in the shared cmap set, adding if not already present
|
||||
gfxCharacterMap* gfxPlatformFontList::FindCharMap(gfxCharacterMap* aCmap) {
|
||||
already_AddRefed<gfxCharacterMap> gfxPlatformFontList::FindCharMap(
|
||||
gfxCharacterMap* aCmap) {
|
||||
AutoLock lock(mLock);
|
||||
aCmap->CalcHash();
|
||||
gfxCharacterMap* cmap = mSharedCmaps.PutEntry(aCmap)->GetKey();
|
||||
cmap->mShared = true;
|
||||
return cmap;
|
||||
return do_AddRef(cmap);
|
||||
}
|
||||
|
||||
// remove the cmap from the shared cmap set
|
||||
|
@ -2395,22 +2410,6 @@ StyleGenericFontFamily gfxPlatformFontList::GetDefaultGeneric(
|
|||
|
||||
AutoLock lock(mLock);
|
||||
|
||||
// initialize lang group pref font defaults (i.e. serif/sans-serif)
|
||||
if (MOZ_UNLIKELY(mDefaultGenericsLangGroup.IsEmpty())) {
|
||||
mDefaultGenericsLangGroup.AppendElements(ArrayLength(gPrefLangNames));
|
||||
for (uint32_t i = 0; i < ArrayLength(gPrefLangNames); i++) {
|
||||
nsAutoCString prefDefaultFontType("font.default.");
|
||||
prefDefaultFontType.Append(GetPrefLangName(eFontPrefLang(i)));
|
||||
nsAutoCString serifOrSans;
|
||||
Preferences::GetCString(prefDefaultFontType.get(), serifOrSans);
|
||||
if (serifOrSans.EqualsLiteral("sans-serif")) {
|
||||
mDefaultGenericsLangGroup[i] = StyleGenericFontFamily::SansSerif;
|
||||
} else {
|
||||
mDefaultGenericsLangGroup[i] = StyleGenericFontFamily::Serif;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (uint32_t(aLang) < ArrayLength(gPrefLangNames)) {
|
||||
return mDefaultGenericsLangGroup[uint32_t(aLang)];
|
||||
}
|
||||
|
|
|
@ -497,7 +497,7 @@ class gfxPlatformFontList : public gfxFontInfoLoader {
|
|||
|
||||
// Search for existing cmap that matches the input; return the input if no
|
||||
// match is found.
|
||||
gfxCharacterMap* FindCharMap(gfxCharacterMap* aCmap);
|
||||
already_AddRefed<gfxCharacterMap> FindCharMap(gfxCharacterMap* aCmap);
|
||||
|
||||
// Remove the cmap from the shared cmap set.
|
||||
void RemoveCmap(const gfxCharacterMap* aCharMap);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "gfxUserFontSet.h"
|
||||
#include "gfxPlatform.h"
|
||||
#include "gfxFontConstants.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/FontPropertyTypes.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/ProfilerLabels.h"
|
||||
|
@ -34,7 +35,7 @@ mozilla::LogModule* gfxUserFontSet::GetUserFontsLog() {
|
|||
#define LOG_ENABLED() \
|
||||
MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug)
|
||||
|
||||
static uint64_t sFontSetGeneration = 0;
|
||||
static Atomic<uint64_t> sFontSetGeneration(0);
|
||||
|
||||
gfxUserFontEntry::gfxUserFontEntry(
|
||||
gfxUserFontSet* aFontSet, const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
|
||||
|
@ -250,7 +251,7 @@ size_t gfxUserFontData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
|
|||
/*virtual*/
|
||||
gfxUserFontFamily::~gfxUserFontFamily() {
|
||||
// Should not be dropped by stylo
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!gfxFontUtils::IsInServoTraversal());
|
||||
}
|
||||
|
||||
already_AddRefed<gfxFontSrcPrincipal> gfxFontFaceSrc::LoadPrincipal(
|
||||
|
@ -1039,9 +1040,9 @@ void gfxUserFontSet::AddUserFontEntry(const nsCString& aFamilyName,
|
|||
|
||||
void gfxUserFontSet::IncrementGeneration(bool aIsRebuild) {
|
||||
// add one, increment again if zero
|
||||
++sFontSetGeneration;
|
||||
if (sFontSetGeneration == 0) ++sFontSetGeneration;
|
||||
mGeneration = sFontSetGeneration;
|
||||
do {
|
||||
mGeneration = ++sFontSetGeneration;
|
||||
} while (mGeneration == 0);
|
||||
if (aIsRebuild) {
|
||||
mRebuildGeneration = mGeneration;
|
||||
}
|
||||
|
|
|
@ -1131,7 +1131,9 @@ bool DCSurfaceVideo::CallVideoProcessorBlt() {
|
|||
: DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
|
||||
hr = swapChain3->SetColorSpace1(outputColorSpace);
|
||||
if (FAILED(hr)) {
|
||||
gfxCriticalNote << "SetColorSpace1 failed: " << gfx::hexa(hr);
|
||||
gfxCriticalNoteOnce << "SetColorSpace1 failed: " << gfx::hexa(hr);
|
||||
RenderThread::Get()->NotifyWebRenderError(
|
||||
wr::WebRenderError::VIDEO_OVERLAY);
|
||||
return false;
|
||||
}
|
||||
videoContext1->VideoProcessorSetOutputColorSpace1(videoProcessor,
|
||||
|
|
|
@ -805,28 +805,36 @@ ScriptSourceObject* ModuleObject::scriptSourceObject() const {
|
|||
.as<ScriptSourceObject>();
|
||||
}
|
||||
|
||||
bool ModuleObject::initAsyncSlots(JSContext* cx, bool isAsync,
|
||||
bool ModuleObject::initAsyncSlots(JSContext* cx, bool hasTopLevelAwait,
|
||||
HandleObject asyncParentModulesList) {
|
||||
initReservedSlot(AsyncSlot, BooleanValue(isAsync));
|
||||
initReservedSlot(HasTopLevelAwaitSlot, BooleanValue(hasTopLevelAwait));
|
||||
initReservedSlot(AsyncParentModulesSlot,
|
||||
ObjectValue(*asyncParentModulesList));
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr uint32_t ASYNC_EVALUATING_POST_ORDER_FALSE = 0;
|
||||
constexpr uint32_t ASYNC_EVALUATING_POST_ORDER_INIT = 1;
|
||||
uint32_t AsyncPostOrder = ASYNC_EVALUATING_POST_ORDER_INIT;
|
||||
|
||||
uint32_t nextPostOrder() {
|
||||
uint32_t ordinal = AsyncPostOrder;
|
||||
MOZ_ASSERT(AsyncPostOrder < MAX_UINT32);
|
||||
AsyncPostOrder++;
|
||||
static uint32_t NextPostOrder(JSRuntime* rt) {
|
||||
uint32_t ordinal = rt->moduleAsyncEvaluatingPostOrder;
|
||||
MOZ_ASSERT(ordinal < MAX_UINT32);
|
||||
rt->moduleAsyncEvaluatingPostOrder++;
|
||||
return ordinal;
|
||||
}
|
||||
|
||||
// Reset the runtime's moduleAsyncEvaluatingPostOrder counter when the last
|
||||
// module that was async evaluating is finished.
|
||||
//
|
||||
// The graph is not re-entrant and any future modules will be independent from
|
||||
// this one.
|
||||
static void MaybeResetPostOrderCounter(JSRuntime* rt,
|
||||
uint32_t finishedPostOrder) {
|
||||
if (rt->moduleAsyncEvaluatingPostOrder == finishedPostOrder) {
|
||||
rt->moduleAsyncEvaluatingPostOrder = ASYNC_EVALUATING_POST_ORDER_INIT;
|
||||
}
|
||||
}
|
||||
|
||||
void ModuleObject::setAsyncEvaluating() {
|
||||
initReservedSlot(AsyncEvaluatingPostOrderSlot,
|
||||
PrivateUint32Value(nextPostOrder()));
|
||||
PrivateUint32Value(NextPostOrder(runtimeFromMainThread())));
|
||||
}
|
||||
|
||||
void ModuleObject::initScriptSlots(HandleScript script) {
|
||||
|
@ -953,8 +961,8 @@ void ModuleObject::setStatus(ModuleStatus newStatus) {
|
|||
setReservedSlot(StatusSlot, ModuleStatusValue(newStatus));
|
||||
}
|
||||
|
||||
bool ModuleObject::isAsync() const {
|
||||
return getReservedSlot(AsyncSlot).toBoolean();
|
||||
bool ModuleObject::hasTopLevelAwait() const {
|
||||
return getReservedSlot(HasTopLevelAwaitSlot).toBoolean();
|
||||
}
|
||||
|
||||
bool ModuleObject::isAsyncEvaluating() const {
|
||||
|
@ -968,12 +976,8 @@ bool ModuleObject::wasAsyncEvaluating() const {
|
|||
}
|
||||
|
||||
void ModuleObject::setAsyncEvaluatingFalse() {
|
||||
if (AsyncPostOrder == getAsyncEvaluatingPostOrder()) {
|
||||
// If this condition is true, we can reset postOrder.
|
||||
// Graph is not re-entrant and any future modules will be independent from
|
||||
// this one.
|
||||
AsyncPostOrder = ASYNC_EVALUATING_POST_ORDER_INIT;
|
||||
}
|
||||
JSRuntime* rt = runtimeFromMainThread();
|
||||
MaybeResetPostOrderCounter(rt, getAsyncEvaluatingPostOrder());
|
||||
return setReservedSlot(AsyncEvaluatingPostOrderSlot,
|
||||
PrivateUint32Value(ASYNC_EVALUATING_POST_ORDER_FALSE));
|
||||
}
|
||||
|
@ -1208,7 +1212,7 @@ bool ModuleObject::execute(JSContext* cx, Handle<ModuleObject*> self) {
|
|||
RootedScript script(cx, self->script());
|
||||
|
||||
auto guardA = mozilla::MakeScopeExit([&] {
|
||||
if (self->isAsync()) {
|
||||
if (self->hasTopLevelAwait()) {
|
||||
// Handled in AsyncModuleExecutionFulfilled and
|
||||
// AsyncModuleExecutionRejected.
|
||||
return;
|
||||
|
@ -2068,163 +2072,6 @@ ModuleObject* js::CallModuleResolveHook(JSContext* cx,
|
|||
return &result->as<ModuleObject>();
|
||||
}
|
||||
|
||||
bool js::AsyncModuleExecutionFulfilledHandler(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
JSFunction& func = args.callee().as<JSFunction>();
|
||||
|
||||
Rooted<ModuleObject*> module(
|
||||
cx, &func.getExtendedSlot(FunctionExtended::MODULE_SLOT)
|
||||
.toObject()
|
||||
.as<ModuleObject>());
|
||||
AsyncModuleExecutionFulfilled(cx, module);
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool js::AsyncModuleExecutionRejectedHandler(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
JSFunction& func = args.callee().as<JSFunction>();
|
||||
Rooted<ModuleObject*> module(
|
||||
cx, &func.getExtendedSlot(FunctionExtended::MODULE_SLOT)
|
||||
.toObject()
|
||||
.as<ModuleObject>());
|
||||
AsyncModuleExecutionRejected(cx, module, args.get(0));
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Top Level Await
|
||||
// https://tc39.es/proposal-top-level-await/#sec-asyncmodulexecutionfulfilled
|
||||
void js::AsyncModuleExecutionFulfilled(JSContext* cx,
|
||||
Handle<ModuleObject*> module) {
|
||||
// Step 1.
|
||||
MOZ_ASSERT(module->status() == ModuleStatus::Evaluated);
|
||||
|
||||
// Step 2.
|
||||
MOZ_ASSERT(module->isAsyncEvaluating());
|
||||
|
||||
ModuleObject::onTopLevelEvaluationFinished(module);
|
||||
|
||||
if (module->hasTopLevelCapability()) {
|
||||
MOZ_ASSERT(module->getCycleRoot() == module);
|
||||
if (!ModuleObject::topLevelCapabilityResolve(cx, module)) {
|
||||
// If Resolve fails, there's nothing more we can do here.
|
||||
cx->clearPendingException();
|
||||
}
|
||||
}
|
||||
|
||||
Rooted<ModuleVector> sortedList(cx);
|
||||
if (!GatherAvailableModuleAncestors(cx, module, &sortedList)) {
|
||||
// We have been interrupted or have OOM'd -- all bets are off, reject the
|
||||
// promise. Not much more we can do.
|
||||
if (!cx->isExceptionPending()) {
|
||||
AsyncModuleExecutionRejected(cx, module, UndefinedHandleValue);
|
||||
return;
|
||||
}
|
||||
|
||||
RootedValue exception(cx);
|
||||
if (!cx->getPendingException(&exception)) {
|
||||
return;
|
||||
}
|
||||
cx->clearPendingException();
|
||||
AsyncModuleExecutionRejected(cx, module, exception);
|
||||
}
|
||||
|
||||
// this is out of step with the spec in order to be able to OOM
|
||||
module->setAsyncEvaluatingFalse();
|
||||
|
||||
Rooted<ModuleObject*> m(cx);
|
||||
|
||||
for (ModuleObject* obj : sortedList) {
|
||||
m = obj;
|
||||
|
||||
// Step 2.
|
||||
if (!m->isAsyncEvaluating()) {
|
||||
MOZ_ASSERT(m->hadEvaluationError());
|
||||
return;
|
||||
}
|
||||
|
||||
if (m->isAsync()) {
|
||||
// Steps for ExecuteAsyncModule
|
||||
MOZ_ASSERT(m->status() == ModuleStatus::Evaluating ||
|
||||
m->status() == ModuleStatus::Evaluated);
|
||||
MOZ_ASSERT(m->isAsync());
|
||||
MOZ_ASSERT(m->isAsyncEvaluating());
|
||||
MOZ_ALWAYS_TRUE(ModuleObject::execute(cx, m));
|
||||
} else {
|
||||
if (!ModuleObject::execute(cx, m)) {
|
||||
MOZ_ASSERT(cx->isExceptionPending());
|
||||
RootedValue exception(cx);
|
||||
if (!cx->getPendingException(&exception)) {
|
||||
return;
|
||||
}
|
||||
cx->clearPendingException();
|
||||
AsyncModuleExecutionRejected(cx, m, exception);
|
||||
} else {
|
||||
m->setAsyncEvaluatingFalse();
|
||||
if (m->hasTopLevelCapability()) {
|
||||
MOZ_ASSERT(m->getCycleRoot() == m);
|
||||
if (!ModuleObject::topLevelCapabilityResolve(cx, m)) {
|
||||
// If Resolve fails, there's nothing more we can do here.
|
||||
cx->clearPendingException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6.
|
||||
// Return undefined.
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-top-level-await/#sec-asyncmodulexecutionrejected
|
||||
void js::AsyncModuleExecutionRejected(JSContext* cx,
|
||||
Handle<ModuleObject*> module,
|
||||
HandleValue error) {
|
||||
// Step 1.
|
||||
MOZ_ASSERT(module->status() == ModuleStatus::Evaluated ||
|
||||
module->status() == ModuleStatus::Evaluated_Error);
|
||||
|
||||
// Step 2.
|
||||
if (!module->isAsyncEvaluating()) {
|
||||
MOZ_ASSERT(module->hadEvaluationError());
|
||||
return;
|
||||
}
|
||||
|
||||
ModuleObject::onTopLevelEvaluationFinished(module);
|
||||
|
||||
// Step 3.
|
||||
MOZ_ASSERT(!module->hadEvaluationError());
|
||||
|
||||
// Step 4.
|
||||
module->setEvaluationError(error);
|
||||
|
||||
// Step 5.
|
||||
module->setAsyncEvaluatingFalse();
|
||||
|
||||
// Step 6.
|
||||
uint32_t length = module->asyncParentModules()->length();
|
||||
Rooted<ModuleObject*> parent(cx);
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
parent =
|
||||
&module->asyncParentModules()->get(i).toObject().as<ModuleObject>();
|
||||
AsyncModuleExecutionRejected(cx, parent, error);
|
||||
}
|
||||
|
||||
// Step 7.
|
||||
if (module->hasTopLevelCapability()) {
|
||||
MOZ_ASSERT(module->getCycleRoot() == module);
|
||||
if (!ModuleObject::topLevelCapabilityReject(cx, module, error)) {
|
||||
// If Reject fails, there's nothing more we can do here.
|
||||
cx->clearPendingException();
|
||||
}
|
||||
}
|
||||
|
||||
// Return undefined.
|
||||
}
|
||||
|
||||
bool ModuleObject::topLevelCapabilityResolve(JSContext* cx,
|
||||
Handle<ModuleObject*> module) {
|
||||
RootedValue rval(cx);
|
||||
|
|
|
@ -259,6 +259,28 @@ enum class ModuleStatus : int32_t {
|
|||
Evaluated_Error // Sub-state of Evaluated with error value set.
|
||||
};
|
||||
|
||||
// Special values for ModuleObject's AsyncEvaluatingPostOrderSlot slot, which is
|
||||
// used to implement the AsyncEvaluation field of cyclic module records.
|
||||
//
|
||||
// The spec requires us to distinguish true, false, and 'never previously set to
|
||||
// true', as well as the order in which the field was set to true for async
|
||||
// evaluating modules.
|
||||
//
|
||||
// This is arranged by using an integer to record the order. Both undefined and
|
||||
// ASYNC_EVALUATING_POST_ORDER_FALSE are used to mean false, with undefined also
|
||||
// meaning never previously set to true.
|
||||
//
|
||||
// See https://tc39.es/ecma262/#sec-cyclic-module-records for field defintion.
|
||||
// See https://tc39.es/ecma262/#sec-async-module-execution-fulfilled for sort
|
||||
// requirement.
|
||||
|
||||
// False value that also indicates that the field was previously true.
|
||||
constexpr uint32_t ASYNC_EVALUATING_POST_ORDER_FALSE = 0;
|
||||
|
||||
// Initial value for the runtime's counter used to generate these values; the
|
||||
// first non-false value.
|
||||
constexpr uint32_t ASYNC_EVALUATING_POST_ORDER_INIT = 1;
|
||||
|
||||
class ModuleObject : public NativeObject {
|
||||
public:
|
||||
enum ModuleSlot {
|
||||
|
@ -278,7 +300,7 @@ class ModuleObject : public NativeObject {
|
|||
FunctionDeclarationsSlot,
|
||||
DFSIndexSlot,
|
||||
DFSAncestorIndexSlot,
|
||||
AsyncSlot,
|
||||
HasTopLevelAwaitSlot,
|
||||
AsyncEvaluatingPostOrderSlot,
|
||||
TopLevelCapabilitySlot,
|
||||
AsyncParentModulesSlot,
|
||||
|
@ -339,7 +361,7 @@ class ModuleObject : public NativeObject {
|
|||
|
||||
static PromiseObject* createTopLevelCapability(JSContext* cx,
|
||||
Handle<ModuleObject*> module);
|
||||
bool isAsync() const;
|
||||
bool hasTopLevelAwait() const;
|
||||
bool isAsyncEvaluating() const;
|
||||
bool wasAsyncEvaluating() const;
|
||||
void setAsyncEvaluating();
|
||||
|
@ -381,7 +403,7 @@ class ModuleObject : public NativeObject {
|
|||
|
||||
static bool createEnvironment(JSContext* cx, Handle<ModuleObject*> self);
|
||||
|
||||
bool initAsyncSlots(JSContext* cx, bool isAsync,
|
||||
bool initAsyncSlots(JSContext* cx, bool hasTopLevelAwait,
|
||||
HandleObject asyncParentModulesList);
|
||||
|
||||
static bool GatherAsyncParentCompletions(
|
||||
|
@ -405,21 +427,6 @@ ModuleObject* CallModuleResolveHook(JSContext* cx,
|
|||
HandleValue referencingPrivate,
|
||||
HandleObject moduleRequest);
|
||||
|
||||
// https://tc39.es/proposal-top-level-await/#sec-asyncmodulexecutionfulfilled
|
||||
void AsyncModuleExecutionFulfilled(JSContext* cx, Handle<ModuleObject*> module);
|
||||
|
||||
// https://tc39.es/proposal-top-level-await/#sec-asyncmodulexecutionrejected
|
||||
void AsyncModuleExecutionRejected(JSContext* cx, Handle<ModuleObject*> module,
|
||||
HandleValue error);
|
||||
|
||||
// https://tc39.es/proposal-top-level-await/#sec-asyncmodulexecutionfulfilled
|
||||
bool AsyncModuleExecutionFulfilledHandler(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
|
||||
// https://tc39.es/proposal-top-level-await/#sec-asyncmodulexecutionrejected
|
||||
bool AsyncModuleExecutionRejectedHandler(JSContext* cx, unsigned argc,
|
||||
Value* vp);
|
||||
|
||||
JSObject* StartDynamicModuleImport(JSContext* cx, HandleScript script,
|
||||
HandleValue specifier, HandleValue options);
|
||||
|
||||
|
|
|
@ -163,12 +163,12 @@ assertEq(l.dfsAncestorIndex, 0);
|
|||
// ==== async and promises getters ====
|
||||
const m = parseModule(`
|
||||
`);
|
||||
assertEq(m.async, false);
|
||||
assertEq(m.hasTopLevelAwait, false);
|
||||
assertEq(m.topLevelCapability, undefined);
|
||||
assertEq(m.asyncEvaluatingPostOrder, undefined);
|
||||
assertEq(m.asyncParentModules[0], undefined);
|
||||
assertEq(m.pendingAsyncDependencies, undefined);
|
||||
testGetter(m, "async");
|
||||
testGetter(m, "hasTopLevelAwait");
|
||||
testGetter(m, "topLevelCapability");
|
||||
testGetter(m, "asyncEvaluatingPostOrder");
|
||||
testGetter(m, "asyncParentModules");
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
// |jit-test| skip-if: !('oomTest' in this)
|
||||
|
||||
var dbgGlobal = newGlobal({newCompartment: true});
|
||||
var dbg = new dbgGlobal.Debugger();
|
||||
dbg.addDebuggee(this);
|
||||
|
||||
oomTest(() => {
|
||||
wasmEvalText(`
|
||||
(import "" "" (func $d))
|
||||
(func try call $d end)
|
||||
`);
|
||||
});
|
|
@ -305,7 +305,8 @@ DEFINE_GETTER_FUNCTIONS(ModuleObject, maybeDfsIndex, Uint32OrUndefinedValue,
|
|||
IdentFilter)
|
||||
DEFINE_GETTER_FUNCTIONS(ModuleObject, maybeDfsAncestorIndex,
|
||||
Uint32OrUndefinedValue, IdentFilter)
|
||||
DEFINE_GETTER_FUNCTIONS(ModuleObject, isAsync, BooleanValue, IdentFilter)
|
||||
DEFINE_GETTER_FUNCTIONS(ModuleObject, hasTopLevelAwait, BooleanValue,
|
||||
IdentFilter)
|
||||
DEFINE_GETTER_FUNCTIONS(ModuleObject, maybeTopLevelCapability,
|
||||
ObjectOrUndefinedValue, IdentFilter)
|
||||
DEFINE_GETTER_FUNCTIONS(ModuleObject, maybeAsyncEvaluatingPostOrder,
|
||||
|
@ -332,7 +333,8 @@ static const JSPropertySpec ShellModuleObjectWrapper_accessors[] = {
|
|||
JS_PSG("dfsIndex", ShellModuleObjectWrapper_maybeDfsIndexGetter, 0),
|
||||
JS_PSG("dfsAncestorIndex",
|
||||
ShellModuleObjectWrapper_maybeDfsAncestorIndexGetter, 0),
|
||||
JS_PSG("async", ShellModuleObjectWrapper_isAsyncGetter, 0),
|
||||
JS_PSG("hasTopLevelAwait", ShellModuleObjectWrapper_hasTopLevelAwaitGetter,
|
||||
0),
|
||||
JS_PSG("topLevelCapability",
|
||||
ShellModuleObjectWrapper_maybeTopLevelCapabilityGetter, 0),
|
||||
JS_PSG("asyncEvaluatingPostOrder",
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "vm/GeneratorObject.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/Interpreter.h"
|
||||
#include "vm/Modules.h"
|
||||
#include "vm/NativeObject.h"
|
||||
#include "vm/PromiseObject.h" // js::PromiseObject
|
||||
#include "vm/Realm.h"
|
||||
|
@ -279,6 +280,33 @@ JSFunction* NewHandler(JSContext* cx, Native handler,
|
|||
return handlerFun;
|
||||
}
|
||||
|
||||
static bool AsyncModuleExecutionFulfilledHandler(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
JSFunction& func = args.callee().as<JSFunction>();
|
||||
|
||||
Rooted<ModuleObject*> module(
|
||||
cx, &func.getExtendedSlot(FunctionExtended::MODULE_SLOT)
|
||||
.toObject()
|
||||
.as<ModuleObject>());
|
||||
AsyncModuleExecutionFulfilled(cx, module);
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool AsyncModuleExecutionRejectedHandler(JSContext* cx, unsigned argc,
|
||||
Value* vp) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
JSFunction& func = args.callee().as<JSFunction>();
|
||||
Rooted<ModuleObject*> module(
|
||||
cx, &func.getExtendedSlot(FunctionExtended::MODULE_SLOT)
|
||||
.toObject()
|
||||
.as<ModuleObject>());
|
||||
AsyncModuleExecutionRejected(cx, module, args.get(0));
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
AsyncFunctionGeneratorObject* AsyncFunctionGeneratorObject::create(
|
||||
JSContext* cx, Handle<ModuleObject*> module) {
|
||||
// TODO: Module is currently hitching a ride with
|
||||
|
|
|
@ -2427,7 +2427,7 @@ void DebugEnvironmentProxy::initSnapshot(ArrayObject& o) {
|
|||
} else {
|
||||
auto* moduleEnv = ModuleEnvironmentObject::find(&environment());
|
||||
MOZ_ASSERT(moduleEnv);
|
||||
MOZ_ASSERT(moduleEnv->module().isAsync());
|
||||
MOZ_ASSERT(moduleEnv->module().hasTopLevelAwait());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -37,6 +37,8 @@ using mozilla::Utf8Unit;
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Public API
|
||||
|
||||
using NameList = GCVector<JSAtom*, 0, SystemAllocPolicy>;
|
||||
|
||||
JS_PUBLIC_API JS::SupportedAssertionsHook JS::GetSupportedAssertionsHook(
|
||||
JSRuntime* rt) {
|
||||
AssertHeapIsIdle();
|
||||
|
@ -332,10 +334,9 @@ static ArrayObject* NewList(JSContext* cx) {
|
|||
return NewArrayWithNullProto(cx);
|
||||
}
|
||||
|
||||
static bool ArrayContainsName(Handle<ArrayObject*> array,
|
||||
Handle<JSAtom*> name) {
|
||||
for (uint32_t i = 0; i < array->length(); i++) {
|
||||
if (array->getDenseElement(i) == StringValue(name)) {
|
||||
static bool ContainsElement(Handle<NameList> list, JSAtom* atom) {
|
||||
for (JSAtom* a : list) {
|
||||
if (a == atom) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -368,26 +369,23 @@ static size_t CountElements(Handle<ModuleVector> stack, ModuleObject* module) {
|
|||
|
||||
// https://tc39.es/ecma262/#sec-getexportednames
|
||||
// ES2023 16.2.1.6.2 GetExportedNames
|
||||
static ArrayObject* ModuleGetExportedNames(
|
||||
JSContext* cx, Handle<ModuleObject*> module,
|
||||
MutableHandle<ModuleSet> exportStarSet) {
|
||||
static bool ModuleGetExportedNames(JSContext* cx, Handle<ModuleObject*> module,
|
||||
MutableHandle<ModuleSet> exportStarSet,
|
||||
MutableHandle<NameList> exportedNames) {
|
||||
// Step 4. Let exportedNames be a new empty List.
|
||||
Rooted<ArrayObject*> exportedNames(cx, NewList(cx));
|
||||
if (!exportedNames) {
|
||||
return nullptr;
|
||||
}
|
||||
MOZ_ASSERT(exportedNames.empty());
|
||||
|
||||
// Step 2. If exportStarSet contains module, then:
|
||||
if (exportStarSet.has(module)) {
|
||||
// Step 2.a. We've reached the starting point of an export * circularity.
|
||||
// Step 2.b. Return a new empty List.
|
||||
return exportedNames;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 3. Append module to exportStarSet.
|
||||
if (!exportStarSet.put(module)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5. For each ExportEntry Record e of module.[[LocalExportEntries]], do:
|
||||
|
@ -399,8 +397,9 @@ static ArrayObject* ModuleGetExportedNames(
|
|||
.as<ExportEntryObject>();
|
||||
// Step 5.a. Assert: module provides the direct binding for this export.
|
||||
// Step 5.b. Append e.[[ExportName]] to exportedNames.
|
||||
if (!NewbornArrayPush(cx, exportedNames, StringValue(e->exportName()))) {
|
||||
return nullptr;
|
||||
if (!exportedNames.append(e->exportName())) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -414,8 +413,9 @@ static ArrayObject* ModuleGetExportedNames(
|
|||
.as<ExportEntryObject>();
|
||||
// Step 6.a. Assert: module imports a specific binding for this export.
|
||||
// Step 6.b. Append e.[[ExportName]] to exportedNames.
|
||||
if (!NewbornArrayPush(cx, exportedNames, StringValue(e->exportName()))) {
|
||||
return nullptr;
|
||||
if (!exportedNames.append(e->exportName())) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -436,27 +436,27 @@ static ArrayObject* ModuleGetExportedNames(
|
|||
requestedModule = HostResolveImportedModule(cx, module, moduleRequest,
|
||||
ModuleStatus::Unlinked);
|
||||
if (!requestedModule) {
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 7.b. Let starNames be ?
|
||||
// requestedModule.GetExportedNames(exportStarSet).
|
||||
starNames = ModuleGetExportedNames(cx, requestedModule, exportStarSet);
|
||||
if (!starNames) {
|
||||
return nullptr;
|
||||
Rooted<NameList> starNames(cx);
|
||||
if (!ModuleGetExportedNames(cx, requestedModule, exportStarSet,
|
||||
&starNames)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 7.c. For each element n of starNames, do:
|
||||
for (uint32_t j = 0; j != starNames->length(); j++) {
|
||||
name = &starNames->getDenseElement(j).toString()->asAtom();
|
||||
|
||||
for (JSAtom* name : starNames) {
|
||||
// Step 7.c.i. If SameValue(n, "default") is false, then:
|
||||
if (name != cx->names().default_) {
|
||||
// Step 7.c.i.1. If n is not an element of exportedNames, then:
|
||||
if (!ArrayContainsName(exportedNames, name)) {
|
||||
if (!ContainsElement(exportedNames, name)) {
|
||||
// Step 7.c.i.1.a. Append n to exportedNames.
|
||||
if (!NewbornArrayPush(cx, exportedNames, StringValue(name))) {
|
||||
return nullptr;
|
||||
if (!exportedNames.append(name)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -464,7 +464,7 @@ static ArrayObject* ModuleGetExportedNames(
|
|||
}
|
||||
|
||||
// Step 8. Return exportedNames.
|
||||
return exportedNames;
|
||||
return true;
|
||||
}
|
||||
|
||||
static ModuleObject* HostResolveImportedModule(
|
||||
|
@ -710,9 +710,8 @@ ModuleNamespaceObject* js::GetOrCreateModuleNamespace(
|
|||
if (!ns) {
|
||||
// Step 3.a. Let exportedNames be ? module.GetExportedNames().
|
||||
Rooted<ModuleSet> exportStarSet(cx);
|
||||
Rooted<ArrayObject*> exportedNames(cx);
|
||||
exportedNames = ModuleGetExportedNames(cx, module, &exportStarSet);
|
||||
if (!exportedNames) {
|
||||
Rooted<NameList> exportedNames(cx);
|
||||
if (!ModuleGetExportedNames(cx, module, &exportStarSet, &exportedNames)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -725,8 +724,8 @@ ModuleNamespaceObject* js::GetOrCreateModuleNamespace(
|
|||
// Step 3.c. For each element name of exportedNames, do:
|
||||
Rooted<JSAtom*> name(cx);
|
||||
Rooted<Value> resolution(cx);
|
||||
for (uint32_t i = 0; i != exportedNames->length(); i++) {
|
||||
name = &exportedNames->getDenseElement(i).toString()->asAtom();
|
||||
for (JSAtom* atom : exportedNames) {
|
||||
name = atom;
|
||||
|
||||
// Step 3.c.i. Let resolution be ? module.ResolveExport(name).
|
||||
if (!ModuleResolveExport(cx, module, name, &resolution)) {
|
||||
|
@ -1450,7 +1449,7 @@ static bool InnerModuleEvaluation(JSContext* cx, Handle<ModuleObject*> module,
|
|||
|
||||
// Step 12. If module.[[PendingAsyncDependencies]] > 0 or module.[[HasTLA]] is
|
||||
// true, then:
|
||||
if (module->pendingAsyncDependencies() > 0 || module->isAsync()) {
|
||||
if (module->pendingAsyncDependencies() > 0 || module->hasTopLevelAwait()) {
|
||||
// Step 12.a. Assert: module.[[AsyncEvaluation]] is false and was never
|
||||
// previously set to true.
|
||||
MOZ_ASSERT(!module->isAsyncEvaluating() && !module->wasAsyncEvaluating());
|
||||
|
@ -1522,7 +1521,7 @@ static bool ExecuteAsyncModule(JSContext* cx, Handle<ModuleObject*> module) {
|
|||
module->status() == ModuleStatus::Evaluated);
|
||||
|
||||
// Step 2. Assert: module.[[HasTLA]] is true.
|
||||
MOZ_ASSERT(module->isAsync());
|
||||
MOZ_ASSERT(module->hasTopLevelAwait());
|
||||
|
||||
// Steps 3 - 8 are performed by the AsyncAwait opcode.
|
||||
|
||||
|
@ -1610,7 +1609,7 @@ static bool GatherAvailableModuleAncestors(
|
|||
|
||||
// Step 1.a.vi.2. If m.[[HasTLA]] is false, perform
|
||||
// GatherAvailableAncestors(m, execList).
|
||||
if (!m->isAsync() &&
|
||||
if (!m->hasTopLevelAwait() &&
|
||||
!::GatherAvailableModuleAncestors(cx, m, execList)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1621,3 +1620,127 @@ static bool GatherAvailableModuleAncestors(
|
|||
// Step 2. Return unused.
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-top-level-await/#sec-asyncmodulexecutionfulfilled
|
||||
void js::AsyncModuleExecutionFulfilled(JSContext* cx,
|
||||
Handle<ModuleObject*> module) {
|
||||
// Step 1.
|
||||
MOZ_ASSERT(module->status() == ModuleStatus::Evaluated);
|
||||
|
||||
// Step 2.
|
||||
MOZ_ASSERT(module->isAsyncEvaluating());
|
||||
|
||||
ModuleObject::onTopLevelEvaluationFinished(module);
|
||||
|
||||
if (module->hasTopLevelCapability()) {
|
||||
MOZ_ASSERT(module->getCycleRoot() == module);
|
||||
if (!ModuleObject::topLevelCapabilityResolve(cx, module)) {
|
||||
// If Resolve fails, there's nothing more we can do here.
|
||||
cx->clearPendingException();
|
||||
}
|
||||
}
|
||||
|
||||
Rooted<ModuleVector> sortedList(cx);
|
||||
if (!js::GatherAvailableModuleAncestors(cx, module, &sortedList)) {
|
||||
// We have been interrupted or have OOM'd -- all bets are off, reject the
|
||||
// promise. Not much more we can do.
|
||||
if (!cx->isExceptionPending()) {
|
||||
AsyncModuleExecutionRejected(cx, module, UndefinedHandleValue);
|
||||
return;
|
||||
}
|
||||
|
||||
RootedValue exception(cx);
|
||||
if (!cx->getPendingException(&exception)) {
|
||||
return;
|
||||
}
|
||||
cx->clearPendingException();
|
||||
AsyncModuleExecutionRejected(cx, module, exception);
|
||||
}
|
||||
|
||||
// this is out of step with the spec in order to be able to OOM
|
||||
module->setAsyncEvaluatingFalse();
|
||||
|
||||
Rooted<ModuleObject*> m(cx);
|
||||
|
||||
for (ModuleObject* obj : sortedList) {
|
||||
m = obj;
|
||||
|
||||
// Step 2.
|
||||
if (!m->isAsyncEvaluating()) {
|
||||
MOZ_ASSERT(m->hadEvaluationError());
|
||||
return;
|
||||
}
|
||||
|
||||
if (m->hasTopLevelAwait()) {
|
||||
MOZ_ALWAYS_TRUE(ExecuteAsyncModule(cx, m));
|
||||
} else {
|
||||
if (!ModuleObject::execute(cx, m)) {
|
||||
MOZ_ASSERT(cx->isExceptionPending());
|
||||
RootedValue exception(cx);
|
||||
if (!cx->getPendingException(&exception)) {
|
||||
return;
|
||||
}
|
||||
cx->clearPendingException();
|
||||
AsyncModuleExecutionRejected(cx, m, exception);
|
||||
} else {
|
||||
m->setAsyncEvaluatingFalse();
|
||||
if (m->hasTopLevelCapability()) {
|
||||
MOZ_ASSERT(m->getCycleRoot() == m);
|
||||
if (!ModuleObject::topLevelCapabilityResolve(cx, m)) {
|
||||
// If Resolve fails, there's nothing more we can do here.
|
||||
cx->clearPendingException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6.
|
||||
// Return undefined.
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-top-level-await/#sec-asyncmodulexecutionrejected
|
||||
void js::AsyncModuleExecutionRejected(JSContext* cx,
|
||||
Handle<ModuleObject*> module,
|
||||
HandleValue error) {
|
||||
// Step 1.
|
||||
MOZ_ASSERT(module->status() == ModuleStatus::Evaluated ||
|
||||
module->status() == ModuleStatus::Evaluated_Error);
|
||||
|
||||
// Step 2.
|
||||
if (!module->isAsyncEvaluating()) {
|
||||
MOZ_ASSERT(module->hadEvaluationError());
|
||||
return;
|
||||
}
|
||||
|
||||
ModuleObject::onTopLevelEvaluationFinished(module);
|
||||
|
||||
// Step 3.
|
||||
MOZ_ASSERT(!module->hadEvaluationError());
|
||||
|
||||
// Step 4.
|
||||
module->setEvaluationError(error);
|
||||
|
||||
// Step 5.
|
||||
module->setAsyncEvaluatingFalse();
|
||||
|
||||
// Step 6.
|
||||
uint32_t length = module->asyncParentModules()->length();
|
||||
Rooted<ModuleObject*> parent(cx);
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
parent =
|
||||
&module->asyncParentModules()->get(i).toObject().as<ModuleObject>();
|
||||
AsyncModuleExecutionRejected(cx, parent, error);
|
||||
}
|
||||
|
||||
// Step 7.
|
||||
if (module->hasTopLevelCapability()) {
|
||||
MOZ_ASSERT(module->getCycleRoot() == module);
|
||||
if (!ModuleObject::topLevelCapabilityReject(cx, module, error)) {
|
||||
// If Reject fails, there's nothing more we can do here.
|
||||
cx->clearPendingException();
|
||||
}
|
||||
}
|
||||
|
||||
// Return undefined.
|
||||
}
|
||||
|
|
|
@ -41,6 +41,11 @@ bool ModuleEvaluate(JSContext* cx, Handle<ModuleObject*> module,
|
|||
bool GatherAvailableModuleAncestors(JSContext* cx, Handle<ModuleObject*> module,
|
||||
MutableHandle<ModuleVector> sortedList);
|
||||
|
||||
void AsyncModuleExecutionFulfilled(JSContext* cx, Handle<ModuleObject*> module);
|
||||
|
||||
void AsyncModuleExecutionRejected(JSContext* cx, Handle<ModuleObject*> module,
|
||||
HandleValue error);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // vm_Modules_h
|
||||
|
|
|
@ -158,6 +158,7 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
|
|||
stackFormat_(parentRuntime ? js::StackFormat::Default
|
||||
: js::StackFormat::SpiderMonkey),
|
||||
wasmInstances(mutexid::WasmRuntimeInstances),
|
||||
moduleAsyncEvaluatingPostOrder(ASYNC_EVALUATING_POST_ORDER_INIT),
|
||||
moduleResolveHook(),
|
||||
moduleMetadataHook(),
|
||||
moduleDynamicImportHook(),
|
||||
|
|
|
@ -978,6 +978,15 @@ struct JSRuntime {
|
|||
// threads for purposes of wasm::InterruptRunningCode().
|
||||
js::ExclusiveData<js::wasm::InstanceVector> wasmInstances;
|
||||
|
||||
// A counter used when recording the order in which modules had their
|
||||
// AsyncEvaluating field set to true. This is used to order queued
|
||||
// evaluations. This is reset when the last module that was async evaluating
|
||||
// is finished.
|
||||
//
|
||||
// See https://tc39.es/ecma262/#sec-async-module-execution-fulfilled step 10
|
||||
// for use.
|
||||
js::MainThreadData<uint32_t> moduleAsyncEvaluatingPostOrder;
|
||||
|
||||
// The implementation-defined abstract operation HostResolveImportedModule.
|
||||
js::MainThreadData<JS::ModuleResolveHook> moduleResolveHook;
|
||||
|
||||
|
|
|
@ -651,7 +651,7 @@ void BaseCompiler::insertBreakablePoint(CallSiteDesc::Kind kind) {
|
|||
CodeOffset(masm.currentOffset()));
|
||||
|
||||
// Branch destination
|
||||
MOZ_ASSERT(masm.currentOffset() == uint32_t(L.offset()));
|
||||
MOZ_ASSERT_IF(!masm.oom(), masm.currentOffset() == uint32_t(L.offset()));
|
||||
#elif defined(JS_CODEGEN_X86)
|
||||
// 83 MODRM OFFS IB
|
||||
static_assert(Instance::offsetOfDebugTrapHandler() < 128);
|
||||
|
@ -669,7 +669,7 @@ void BaseCompiler::insertBreakablePoint(CallSiteDesc::Kind kind) {
|
|||
CodeOffset(masm.currentOffset()));
|
||||
|
||||
// Branch destination
|
||||
MOZ_ASSERT(masm.currentOffset() == uint32_t(L.offset()));
|
||||
MOZ_ASSERT_IF(!masm.oom(), masm.currentOffset() == uint32_t(L.offset()));
|
||||
#elif defined(JS_CODEGEN_ARM64)
|
||||
ScratchPtr scratch(*this);
|
||||
ARMRegister tmp(scratch, 64);
|
||||
|
|
|
@ -278,17 +278,22 @@ static void MaybeSanitizeException(JSContext* cx,
|
|||
// to less-privileged code.
|
||||
nsIPrincipal* callerPrincipal = nsContentUtils::SubjectPrincipal(cx);
|
||||
|
||||
// No need to sanitize uncatchable exceptions, just return.
|
||||
if (!JS_IsExceptionPending(cx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-enter the unwrappedFun Realm to do get the current exception, so we
|
||||
// don't end up unnecessarily wrapping exceptions.
|
||||
{ // Scope for JSAutoRealm
|
||||
JSAutoRealm ar(cx, unwrappedFun);
|
||||
|
||||
JS::ExceptionStack exnStack(cx);
|
||||
// If JS::GetPendingExceptionStack returns false, this was an uncatchable
|
||||
// exception, or we somehow failed to wrap the exception into our
|
||||
// compartment. In either case, treating this as uncatchable exception,
|
||||
// by returning without setting any exception on the JSContext,
|
||||
// seems fine.
|
||||
|
||||
// If JS::GetPendingExceptionStack returns false, we somehow failed to wrap
|
||||
// the exception into our compartment. It seems fine to treat this as an
|
||||
// uncatchable exception by returning without setting any exception on the
|
||||
// JS context.
|
||||
if (!JS::GetPendingExceptionStack(cx, &exnStack)) {
|
||||
JS_ClearPendingException(cx);
|
||||
return;
|
||||
|
|
|
@ -121,7 +121,7 @@ class FontFaceSetImpl : public nsISupports, public gfxUserFontSet {
|
|||
*/
|
||||
virtual void DidRefresh() { MOZ_ASSERT_UNREACHABLE("Not implemented!"); }
|
||||
|
||||
virtual void FlushUserFontSet() {}
|
||||
virtual void FlushUserFontSet() = 0;
|
||||
|
||||
static nsPresContext* GetPresContextFor(gfxUserFontSet* aUserFontSet) {
|
||||
const auto* set = static_cast<FontFaceSetImpl*>(aUserFontSet);
|
||||
|
|
|
@ -135,6 +135,36 @@ void FontFaceSetWorkerImpl::InitializeOnMainThread() {
|
|||
|
||||
void FontFaceSetWorkerImpl::Destroy() {
|
||||
RecursiveMutexAutoLock lock(mMutex);
|
||||
|
||||
class DestroyRunnable final : public Runnable {
|
||||
public:
|
||||
DestroyRunnable(FontFaceSetWorkerImpl* aFontFaceSet,
|
||||
nsTHashtable<nsPtrHashKey<nsFontFaceLoader>>&& aLoaders)
|
||||
: Runnable("FontFaceSetWorkerImpl::Destroy"),
|
||||
mFontFaceSet(aFontFaceSet),
|
||||
mLoaders(std::move(aLoaders)) {}
|
||||
|
||||
protected:
|
||||
~DestroyRunnable() override = default;
|
||||
|
||||
NS_IMETHOD Run() override {
|
||||
for (const auto& key : mLoaders.Keys()) {
|
||||
key->Cancel();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// We save a reference to the FontFaceSetWorkerImpl because the loaders
|
||||
// contain a non-owning reference to it.
|
||||
RefPtr<FontFaceSetWorkerImpl> mFontFaceSet;
|
||||
nsTHashtable<nsPtrHashKey<nsFontFaceLoader>> mLoaders;
|
||||
};
|
||||
|
||||
if (!mLoaders.IsEmpty() && !NS_IsMainThread()) {
|
||||
auto runnable = MakeRefPtr<DestroyRunnable>(this, std::move(mLoaders));
|
||||
NS_DispatchToMainThread(runnable);
|
||||
}
|
||||
|
||||
mWorkerRef = nullptr;
|
||||
FontFaceSetImpl::Destroy();
|
||||
}
|
||||
|
@ -191,6 +221,33 @@ uint64_t FontFaceSetWorkerImpl::GetInnerWindowID() {
|
|||
return mWorkerRef->Private()->WindowID();
|
||||
}
|
||||
|
||||
void FontFaceSetWorkerImpl::FlushUserFontSet() {
|
||||
RecursiveMutexAutoLock lock(mMutex);
|
||||
|
||||
// If there was a change to the mNonRuleFaces array, then there could
|
||||
// have been a modification to the user font set.
|
||||
bool modified = mNonRuleFacesDirty;
|
||||
mNonRuleFacesDirty = false;
|
||||
|
||||
for (size_t i = 0, i_end = mNonRuleFaces.Length(); i < i_end; ++i) {
|
||||
InsertNonRuleFontFace(mNonRuleFaces[i].mFontFace, modified);
|
||||
}
|
||||
|
||||
// Remove any residual families that have no font entries.
|
||||
for (auto it = mFontFamilies.Iter(); !it.Done(); it.Next()) {
|
||||
if (!it.Data()->FontListLength()) {
|
||||
it.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
IncrementGeneration(true);
|
||||
mHasLoadingFontFacesIsDirty = true;
|
||||
CheckLoadingStarted();
|
||||
CheckLoadingFinished();
|
||||
}
|
||||
}
|
||||
|
||||
nsresult FontFaceSetWorkerImpl::StartLoad(gfxUserFontEntry* aUserFontEntry,
|
||||
uint32_t aSrcIndex) {
|
||||
RecursiveMutexAutoLock lock(mMutex);
|
||||
|
|
|
@ -28,6 +28,8 @@ class FontFaceSetWorkerImpl final : public FontFaceSetImpl {
|
|||
|
||||
already_AddRefed<URLExtraData> GetURLExtraData() override;
|
||||
|
||||
void FlushUserFontSet() override;
|
||||
|
||||
// gfxUserFontSet
|
||||
|
||||
nsresult StartLoad(gfxUserFontEntry* aUserFontEntry,
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
# include <pthread.h>
|
||||
#endif
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/ThreadSafety.h"
|
||||
|
||||
#if defined(XP_DARWIN)
|
||||
// For information about the following undocumented flags and functions see
|
||||
|
@ -40,11 +41,10 @@ static_assert(sizeof(os_unfair_lock) == sizeof(OSSpinLock),
|
|||
#endif // defined(XP_DARWIN)
|
||||
|
||||
// Mutexes based on spinlocks. We can't use normal pthread spinlocks in all
|
||||
// places, because they require malloc()ed memory, which causes
|
||||
// bootstrapping issues in some cases. We also can't use constructors,
|
||||
// because for statics, they would fire after the first use of malloc,
|
||||
// resetting the locks.
|
||||
struct Mutex {
|
||||
// places, because they require malloc()ed memory, which causes bootstrapping
|
||||
// issues in some cases. We also can't use constructors, because for statics,
|
||||
// they would fire after the first use of malloc, resetting the locks.
|
||||
struct CAPABILITY Mutex {
|
||||
#if defined(XP_WIN)
|
||||
CRITICAL_SECTION mMutex;
|
||||
#elif defined(XP_DARWIN)
|
||||
|
@ -86,7 +86,7 @@ struct Mutex {
|
|||
return true;
|
||||
}
|
||||
|
||||
inline void Lock() {
|
||||
inline void Lock() CAPABILITY_ACQUIRE() {
|
||||
#if defined(XP_WIN)
|
||||
EnterCriticalSection(&mMutex);
|
||||
#elif defined(XP_DARWIN)
|
||||
|
@ -109,7 +109,7 @@ struct Mutex {
|
|||
#endif
|
||||
}
|
||||
|
||||
inline void Unlock() {
|
||||
inline void Unlock() CAPABILITY_RELEASE() {
|
||||
#if defined(XP_WIN)
|
||||
LeaveCriticalSection(&mMutex);
|
||||
#elif defined(XP_DARWIN)
|
||||
|
@ -137,12 +137,14 @@ struct Mutex {
|
|||
// Ideally, we'd use the same type of locks everywhere, but SRWLocks
|
||||
// everywhere incur a performance penalty. See bug 1418389.
|
||||
#if defined(XP_WIN)
|
||||
struct StaticMutex {
|
||||
struct CAPABILITY StaticMutex {
|
||||
SRWLOCK mMutex;
|
||||
|
||||
inline void Lock() { AcquireSRWLockExclusive(&mMutex); }
|
||||
inline void Lock() CAPABILITY_ACQUIRE() { AcquireSRWLockExclusive(&mMutex); }
|
||||
|
||||
inline void Unlock() { ReleaseSRWLockExclusive(&mMutex); }
|
||||
inline void Unlock() CAPABILITY_RELEASE() {
|
||||
ReleaseSRWLockExclusive(&mMutex);
|
||||
}
|
||||
};
|
||||
|
||||
// Normally, we'd use a constexpr constructor, but MSVC likes to create
|
||||
|
@ -166,10 +168,15 @@ typedef Mutex StaticMutex;
|
|||
#endif
|
||||
|
||||
template <typename T>
|
||||
struct MOZ_RAII AutoLock {
|
||||
explicit AutoLock(T& aMutex) : mMutex(aMutex) { mMutex.Lock(); }
|
||||
struct SCOPED_CAPABILITY MOZ_RAII AutoLock {
|
||||
explicit AutoLock(T& aMutex) CAPABILITY_ACQUIRE(aMutex) : mMutex(aMutex) {
|
||||
mMutex.Lock();
|
||||
}
|
||||
|
||||
~AutoLock() { mMutex.Unlock(); }
|
||||
~AutoLock() CAPABILITY_RELEASE() { mMutex.Unlock(); }
|
||||
|
||||
AutoLock(const AutoLock&) = delete;
|
||||
AutoLock(AutoLock&&) = delete;
|
||||
|
||||
private:
|
||||
T& mMutex;
|
||||
|
|
|
@ -4641,11 +4641,8 @@ inline void MozJemalloc::moz_dispose_arena(arena_id_t aArenaId) {
|
|||
// of malloc during fork(). These functions are only called if the program is
|
||||
// running in threaded mode, so there is no need to check whether the program
|
||||
// is threaded here.
|
||||
# ifndef XP_DARWIN
|
||||
static
|
||||
# endif
|
||||
void
|
||||
_malloc_prefork(void) {
|
||||
FORK_HOOK
|
||||
void _malloc_prefork(void) NO_THREAD_SAFETY_ANALYSIS {
|
||||
// Acquire all mutexes in a safe order.
|
||||
gArenas.mLock.Lock();
|
||||
|
||||
|
@ -4658,11 +4655,8 @@ static
|
|||
huge_mtx.Lock();
|
||||
}
|
||||
|
||||
# ifndef XP_DARWIN
|
||||
static
|
||||
# endif
|
||||
void
|
||||
_malloc_postfork_parent(void) {
|
||||
FORK_HOOK
|
||||
void _malloc_postfork_parent(void) NO_THREAD_SAFETY_ANALYSIS {
|
||||
// Release all mutexes, now that fork() has completed.
|
||||
huge_mtx.Unlock();
|
||||
|
||||
|
@ -4675,11 +4669,8 @@ static
|
|||
gArenas.mLock.Unlock();
|
||||
}
|
||||
|
||||
# ifndef XP_DARWIN
|
||||
static
|
||||
# endif
|
||||
void
|
||||
_malloc_postfork_child(void) {
|
||||
FORK_HOOK
|
||||
void _malloc_postfork_child(void) {
|
||||
// Reinitialize all mutexes, now that fork() has completed.
|
||||
huge_mtx.Init();
|
||||
|
||||
|
|
|
@ -27,8 +27,8 @@ static bool sStdoutOrStderr = false;
|
|||
static Mutex sMutex MOZ_UNANNOTATED;
|
||||
|
||||
#ifndef _WIN32
|
||||
static void prefork() { sMutex.Lock(); }
|
||||
static void postfork_parent() { sMutex.Unlock(); }
|
||||
static void prefork() NO_THREAD_SAFETY_ANALYSIS { sMutex.Lock(); }
|
||||
static void postfork_parent() NO_THREAD_SAFETY_ANALYSIS { sMutex.Unlock(); }
|
||||
static void postfork_child() { sMutex.Init(); }
|
||||
#endif
|
||||
|
||||
|
|
|
@ -793,14 +793,16 @@ class GMut {
|
|||
MOZ_RELEASE_ASSERT(page.mBaseAddr == aPtr);
|
||||
|
||||
if (page.mState == AllocPageState::Freed) {
|
||||
LOG("EnsureValidAndInUse(%p), use-after-free\n", aPtr);
|
||||
// An operation on a freed page? This is a particular kind of
|
||||
// use-after-free. Deliberately touch the page in question, in order to
|
||||
// cause a crash that triggers the usual PHC machinery. But unlock sMutex
|
||||
// first, because that self-same PHC machinery needs to re-lock it, and
|
||||
// the crash causes non-local control flow so sMutex won't be unlocked
|
||||
// the normal way in the caller.
|
||||
LOG("EnsureValidAndInUse(%p), use-after-free\n", aPtr);
|
||||
PUSH_IGNORE_THREAD_SAFETY
|
||||
sMutex.Unlock();
|
||||
POP_THREAD_SAFETY
|
||||
*static_cast<uint8_t*>(aPtr) = 0;
|
||||
MOZ_CRASH("unreachable");
|
||||
}
|
||||
|
@ -877,9 +879,11 @@ class GMut {
|
|||
*aInfo = {TagUnknown, nullptr, 0, 0};
|
||||
}
|
||||
|
||||
static void prefork() { sMutex.Lock(); }
|
||||
static void postfork_parent() { sMutex.Unlock(); }
|
||||
#ifndef XP_WIN
|
||||
static void prefork() NO_THREAD_SAFETY_ANALYSIS { sMutex.Lock(); }
|
||||
static void postfork_parent() NO_THREAD_SAFETY_ANALYSIS { sMutex.Unlock(); }
|
||||
static void postfork_child() { sMutex.Init(); }
|
||||
#endif
|
||||
|
||||
void IncPageAllocHits(GMutLock) { mPageAllocHits++; }
|
||||
void IncPageAllocMisses(GMutLock) { mPageAllocMisses++; }
|
||||
|
|
|
@ -3640,6 +3640,19 @@
|
|||
value: false
|
||||
mirror: always
|
||||
|
||||
# Forward-Declared Storage-access API.
|
||||
- name: dom.storage_access.forward_declared.enabled
|
||||
type: bool
|
||||
value: false
|
||||
mirror: always
|
||||
|
||||
# How long the Forward-Declared Storage-access API allows between pair requests
|
||||
# in seconds
|
||||
- name: dom.storage_access.forward_declared.lifetime
|
||||
type: uint32_t
|
||||
value: 15 * 60
|
||||
mirror: always
|
||||
|
||||
# The maximum number of origins that a given third-party tracker is allowed
|
||||
# to have concurrent access to before the user is presented with a storage
|
||||
# access prompt. Only effective when the auto_grants pref is turned on.
|
||||
|
|
|
@ -57,13 +57,15 @@ class ActivationContext:
|
|||
|
||||
def test_new_package_appears_in_pkg_resources():
|
||||
try:
|
||||
# "carrot" was chosen as the package to use because:
|
||||
# "hyperframe" was chosen as the package to use because:
|
||||
# * It has to be a package that doesn't exist in-scope at the start (so,
|
||||
# all vendored modules included in the test virtualenv aren't usage).
|
||||
# all vendored modules included in the test virtualenv aren't usable).
|
||||
# * It must be on our internal PyPI mirror.
|
||||
# Of the options, "carrot" is a small install that fits these requirements.
|
||||
pkg_resources.get_distribution("carrot")
|
||||
assert False, "Expected to not find 'carrot' as the initial state of the test"
|
||||
# Of the options, "hyperframe" is a small install that fits these requirements.
|
||||
pkg_resources.get_distribution("hyperframe")
|
||||
assert (
|
||||
False
|
||||
), "Expected to not find 'hyperframe' as the initial state of the test"
|
||||
except pkg_resources.DistributionNotFound:
|
||||
pass
|
||||
|
||||
|
@ -84,7 +86,7 @@ def test_new_package_appears_in_pkg_resources():
|
|||
)
|
||||
|
||||
venv = PythonVirtualenv(venv_dir)
|
||||
venv.pip_install(["carrot==0.10.7"])
|
||||
venv.pip_install(["hyperframe==5.2.0"])
|
||||
|
||||
initial_metadata = MozSiteMetadata.from_runtime()
|
||||
try:
|
||||
|
@ -92,7 +94,7 @@ def test_new_package_appears_in_pkg_resources():
|
|||
with metadata.update_current_site(venv.python_path):
|
||||
activate_virtualenv(venv)
|
||||
|
||||
assert pkg_resources.get_distribution("carrot").version == "0.10.7"
|
||||
assert pkg_resources.get_distribution("hyperframe").version == "5.2.0"
|
||||
finally:
|
||||
MozSiteMetadata.current = initial_metadata
|
||||
|
||||
|
|
|
@ -431,7 +431,7 @@ fxms-schemas:
|
|||
cwd: '{checkout}'
|
||||
command: >
|
||||
cd browser/components/newtab/content-src/asrouter/schemas &&
|
||||
python3 make-schemas.py --check
|
||||
../../../../../../mach python -- make-schemas.py --check
|
||||
when:
|
||||
files-changed:
|
||||
- 'browser/components/newtab/content-src/asrouter/schemas/make-schemas.py'
|
||||
|
|
|
@ -14,6 +14,8 @@ export MOZCONFIG=$GECKO_PATH/mozconfig
|
|||
cat <<EOT >> $MOZCONFIG
|
||||
# Enable debug mode
|
||||
ac_add_options --enable-debug
|
||||
# Enable clang-plugin in order to have all defines activated for static-analysis
|
||||
ac_add_options --enable-clang-plugin
|
||||
# Enable GC zeal, a testing and debugging feature that helps find GC-related bugs in JSAPI applications.
|
||||
ac_add_options --enable-gczeal
|
||||
# Do not treat warnings as errors
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
[2d.text.draw.generic.family.html]
|
||||
[Test that drawing sans-serif produces the same result between canvas and OffscreenCanvas]
|
||||
expected: FAIL
|
||||
|
||||
[Test that drawing serif produces the same result between canvas and OffscreenCanvas]
|
||||
expected: FAIL
|
||||
|
||||
[Test that drawing cursive produces the same result between canvas and OffscreenCanvas]
|
||||
expected: FAIL
|
||||
|
||||
[Test that drawing fantasy produces the same result between canvas and OffscreenCanvas]
|
||||
expected: FAIL
|
||||
|
||||
[Test that drawing monospace produces the same result between canvas and OffscreenCanvas]
|
||||
expected: FAIL
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
[2d.text.draw.generic.family.w.html]
|
||||
expected: ERROR
|
||||
[Test that drawing sans-serif produces the same result between canvas and OffscreenCanvas in a Worker]
|
||||
expected: TIMEOUT
|
||||
|
||||
[Test that drawing serif produces the same result between canvas and OffscreenCanvas in a Worker]
|
||||
expected: TIMEOUT
|
||||
|
||||
[Test that drawing cursive produces the same result between canvas and OffscreenCanvas in a Worker]
|
||||
expected: TIMEOUT
|
||||
|
||||
[Test that drawing fantasy produces the same result between canvas and OffscreenCanvas in a Worker]
|
||||
expected: TIMEOUT
|
||||
|
||||
[Test that drawing monospace produces the same result between canvas and OffscreenCanvas in a Worker]
|
||||
expected: TIMEOUT
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.align.default.html]
|
||||
[OffscreenCanvas test: 2d.text.align.default]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.align.default.worker.html]
|
||||
[2d]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.align.invalid.html]
|
||||
[OffscreenCanvas test: 2d.text.align.invalid]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.align.invalid.worker.html]
|
||||
[2d]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.baseline.default.html]
|
||||
[OffscreenCanvas test: 2d.text.baseline.default]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.baseline.default.worker.html]
|
||||
[2d]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.baseline.invalid.html]
|
||||
[OffscreenCanvas test: 2d.text.baseline.invalid]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.baseline.invalid.worker.html]
|
||||
[2d]
|
||||
expected: FAIL
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
[2d.text.draw.align.center.html]
|
||||
expected:
|
||||
if (os == "android") and not debug: [OK, TIMEOUT]
|
||||
[textAlign center is the center of the em squares (not the bounding box)]
|
||||
expected:
|
||||
if (os == "android") and not debug: [FAIL, TIMEOUT]
|
||||
FAIL
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.align.center.worker.html]
|
||||
[textAlign center is the center of the em squares (not the bounding box)]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.align.end.ltr.html]
|
||||
[textAlign end with ltr is the right edge]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.align.end.ltr.worker.html]
|
||||
[textAlign end with ltr is the right edge]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.align.end.rtl.html]
|
||||
[textAlign end with rtl is the left edge]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.align.end.rtl.worker.html]
|
||||
[textAlign end with rtl is the left edge]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.align.left.html]
|
||||
[textAlign left is the left of the first em square (not the bounding box)]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.align.left.worker.html]
|
||||
[textAlign left is the left of the first em square (not the bounding box)]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.align.right.html]
|
||||
[textAlign right is the right of the last em square (not the bounding box)]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.align.right.worker.html]
|
||||
[textAlign right is the right of the last em square (not the bounding box)]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.align.start.ltr.html]
|
||||
[textAlign start with ltr is the left edge]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.align.start.ltr.worker.html]
|
||||
[textAlign start with ltr is the left edge]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.align.start.rtl.html]
|
||||
[textAlign start with rtl is the right edge]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.align.start.rtl.worker.html]
|
||||
[textAlign start with rtl is the right edge]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.baseline.alphabetic.html]
|
||||
[OffscreenCanvas test: 2d.text.draw.baseline.alphabetic]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.baseline.alphabetic.worker.html]
|
||||
[2d]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.baseline.bottom.html]
|
||||
[textBaseline bottom is the bottom of the em square (not the bounding box)]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.baseline.bottom.worker.html]
|
||||
[textBaseline bottom is the bottom of the em square (not the bounding box)]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
[2d.text.draw.baseline.hanging.html]
|
||||
[OffscreenCanvas test: 2d.text.draw.baseline.hanging]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if (os == 'android'): FAIL
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
[2d.text.draw.baseline.hanging.worker.html]
|
||||
[2d]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if (os == 'android'): FAIL
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.baseline.middle.html]
|
||||
[textBaseline middle is the middle of the em square (not the bounding box)]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.baseline.middle.worker.html]
|
||||
[textBaseline middle is the middle of the em square (not the bounding box)]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[2d.text.draw.baseline.top.html]
|
||||
[textBaseline top is the top of the em square (not the bounding box)]
|
||||
expected: FAIL
|
||||
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче