Merge autoland to mozilla-central. a=merge

This commit is contained in:
Iulian Moraru 2022-06-28 00:49:18 +03:00
commit b7642d0089
261 changed files with 28623 additions and 35256 deletions

View File

@ -7,12 +7,6 @@ config/msvc-stl-wrapper.template.h
intl/components/src/LocaleGenerated.cpp
js/src/builtin/intl/TimeZoneDataGenerated.h
# Don't want to reformat irregexp (third-party code)
js/src/irregexp/imported/.*
# Don't want to reformat zydis (third-party library subject to occasional updates).
js/src/zydis/.*
# Generated by js/src/util/make_unicode.py
js/src/util/Unicode.cpp
js/src/util/UnicodeNonBMP.h
@ -21,7 +15,6 @@ js/src/util/UnicodeNonBMP.h
layout/style/nsCSSAnonBoxList.h
layout/style/nsCSSCounterDescList.h
layout/style/nsCSSFontDescList.h
layout/style/nsCSSKeywordList.h
layout/style/nsCSSPseudoElementList.h
layout/style/nsCSSVisitedDependentPropList.h
layout/style/nsDOMCSSValueList.h
@ -65,45 +58,66 @@ xpcom/reflect/xptcall/md/unix/.*
# Generated from ./tools/rewriting/ThirdPartyPaths.txt
# awk '{print ""$1".*"}' ./tools/rewriting/ThirdPartyPaths.txt
browser/components/newtab/vendor/.*
browser/components/pocket/content/panels/js/vendor/.*
browser/components/translation/cld2/.*
browser/extensions/mortar/ppapi/.*
browser/extensions/formautofill/content/third-party/.*
browser/extensions/formautofill/test/fixtures/third_party/.*
devtools/client/inspector/markup/test/lib_.*
devtools/client/jsonview/lib/require.js
devtools/client/shared/build/babel.js
devtools/client/shared/source-map/.*
devtools/client/shared/sourceeditor/codemirror/.*
dom/canvas/test/webgl-conf/checkout/closure-library/.*
devtools/client/shared/sourceeditor/test/cm_mode_ruby.js
devtools/client/shared/sourceeditor/test/codemirror/.*
devtools/client/shared/vendor/.*
devtools/shared/jsbeautify/.*
devtools/shared/node-properties/.*
devtools/shared/qrcode/decoder/.*
devtools/shared/qrcode/encoder/.*
devtools/shared/sprintfjs/.*
devtools/shared/storage/vendor/.*
dom/canvas/test/webgl-conf/checkout/.*
dom/imptests/.*
dom/media/gmp/rlz/.*
dom/media/gmp/widevine-adapter/content_decryption_module.h
dom/media/gmp/widevine-adapter/content_decryption_module_export.h
dom/media/gmp/widevine-adapter/content_decryption_module_ext.h
dom/media/gmp/widevine-adapter/content_decryption_module_proxy.h
dom/media/gmp/widevine-adapter/content_decryption_module.h
dom/media/platforms/ffmpeg/ffmpeg57/.*
dom/media/platforms/ffmpeg/ffmpeg58/.*
dom/media/platforms/ffmpeg/ffmpeg59/.*
dom/media/platforms/ffmpeg/libav53/.*
dom/media/platforms/ffmpeg/libav54/.*
dom/media/platforms/ffmpeg/libav55/.*
dom/media/webaudio/test/blink/.*
dom/media/webrtc/transport/third_party/.*
dom/media/webspeech/recognition/endpointer.cc
dom/media/webspeech/recognition/endpointer.h
dom/media/webspeech/recognition/energy_endpointer.cc
dom/media/webspeech/recognition/energy_endpointer.h
dom/media/webspeech/recognition/energy_endpointer_params.cc
dom/media/webspeech/recognition/energy_endpointer_params.h
dom/media/webspeech/recognition/energy_endpointer.cc
dom/media/webspeech/recognition/energy_endpointer.h
dom/media/webvtt/vtt.jsm
dom/tests/mochitest/ajax/.*
dom/tests/mochitest/dom-level1-core/.*
dom/tests/mochitest/dom-level2-core/.*
dom/tests/mochitest/dom-level2-html/.*
dom/u2f/tests/pkijs/.*
dom/webauthn/cbor-cpp/.*
dom/webauthn/winwebauthn/webauthn.h
dom/webauthn/tests/pkijs/.*
editor/libeditor/tests/browserscope/lib/richtext/.*
editor/libeditor/tests/browserscope/lib/richtext2/.*
extensions/spellcheck/hunspell/src/.*
gfx/angle/.*
gfx/angle/checkout/.*
gfx/cairo/.*
gfx/graphite2/.*
gfx/harfbuzz/.*
gfx/ots/src/.*
gfx/ots/include/.*
gfx/ots/tests/.*
gfx/ots/.*
gfx/qcms/.*
gfx/sfntly/.*
gfx/skia/.*
gfx/vr/service/openvr/.*
gfx/vr/service/openvr/headers/openvr.h
gfx/vr/service/openvr/src/README
gfx/vr/service/openvr/src/dirtools_public.cpp
gfx/vr/service/openvr/src/dirtools_public.h
gfx/vr/service/openvr/src/envvartools_public.cpp
@ -114,29 +128,34 @@ gfx/vr/service/openvr/src/ivrclientcore.h
gfx/vr/service/openvr/src/openvr_api_public.cpp
gfx/vr/service/openvr/src/pathtools_public.cpp
gfx/vr/service/openvr/src/pathtools_public.h
gfx/vr/service/openvr/src/README
gfx/vr/service/openvr/src/sharedlibtools_public.cpp
gfx/vr/service/openvr/src/sharedlibtools_public.h
gfx/vr/service/openvr/src/strtools_public.cpp
gfx/vr/service/openvr/src/strtools_public.h
gfx/vr/service/openvr/src/vrpathregistry_public.cpp
gfx/vr/service/openvr/src/vrpathregistry_public.h
gfx/wr/.*
gfx/ycbcr/.*
intl/hyphenation/hyphen/.*
intl/icu/.*
ipc/chromium/src/third_party/.*
js/src/ctypes/libffi/.*
js/src/dtoa.c.*
js/src/dtoa.c
js/src/editline/.*
js/src/irregexp/imported/.*
js/src/jit/arm64/vixl/.*
js/src/octane/.*
js/src/vtune/disable_warnings.h
js/src/vtune/ittnotify.h
js/src/vtune/ittnotify_config.h
js/src/vtune/ittnotify_static.c
js/src/vtune/ittnotify_static.h
js/src/vtune/ittnotify_types.h
js/src/vtune/ittnotify.h
js/src/vtune/jitprofiling.c
js/src/vtune/jitprofiling.h
js/src/vtune/legacy/.*
js/src/zydis/.*
layout/docs/css-gap-decorations/.*
media/ffvpx/.*
media/kiss_fft/.*
media/libaom/.*
@ -156,52 +175,57 @@ media/libvorbis/.*
media/libvpx/.*
media/libwebp/.*
media/libyuv/.*
media/mozva/va/.*
media/mozva/va.*
media/mp4parse-rust/.*
media/openmax_dl/.*
media/openmax_il/.*
media/webrtc/signaling/src/sdp/sipcc/.*
media/webrtc/trunk/.*
media/webrtc/signaling/gtest/MockCall.h
mfbt/double-conversion/double-conversion/.*
mfbt/lz4/.*
mobile/android/geckoview/src/thirdparty/.*
modules/brotli/.*
modules/fdlibm/.*
modules/freetype2/.*
modules/libbz2/.*
modules/pdfium/.*
modules/woff2/include/.*
modules/woff2/src/.*
modules/woff2/.*
modules/xz-embedded/.*
modules/zlib/.*
mozglue/misc/decimal/.*
mozglue/tests/glibc_printf_tests/.*
netwerk/dns/nsIDNKitInterface.h
netwerk/sctp/src/.*
netwerk/srtp/src/.*
nsprpub/.*
other-licenses/.*
parser/expat/.*
remote/cdp/test/browser/chrome-remote-interface.js
remote/test/puppeteer/.*
security/nss/.*
security/sandbox/chromium/.*
security/sandbox/chromium-shim/.*
testing/gtest/gmock/.*
testing/gtest/gtest/.*
security/sandbox/chromium/.*
testing/mochitest/MochiKit/.*
testing/mochitest/pywebsocket3/.*
testing/mochitest/tests/MochiKit-1.4.2/.*
testing/modules/sinon-7.2.7.js
testing/mozbase/mozproxy/mozproxy/backends/mitm/scripts/catapult/.*
testing/talos/talos/tests/dromaeo/.*
testing/talos/talos/tests/kraken/.*
testing/talos/talos/tests/v8_7/.*
testing/web-platform/tests/resources/webidl2/.*
testing/web-platform/tests/tools/third_party/.*
testing/xpcshell/dns-packet/.*
testing/xpcshell/node-http2/.*
testing/xpcshell/node-ip/.*
testing/xpcshell/odoh-wasm/.*
third_party/.*
toolkit/components/certviewer/content/vendor/.*
toolkit/components/jsoncpp/.*
toolkit/components/normandy/vendor/.*
toolkit/components/passwordmgr/PasswordRulesParser.jsm
toolkit/components/protobuf/.*
toolkit/components/url-classifier/chromium/.*
toolkit/components/url-classifier/protobuf/.*
toolkit/components/utils/mozjexl.js
toolkit/crashreporter/breakpad-client/.*
toolkit/crashreporter/google-breakpad/.*
tools/fuzzing/libfuzzer/.*
tools/profiler/core/vtune/.*
# tools/profiler/public/GeckoTraceEvent.h is a modified vendored copy
tools/profiler/public/GeckoTraceEvent.h
xpcom/build/mach_override.c
xpcom/build/mach_override.h
xpcom/io/crc32c.c

View File

@ -35,10 +35,8 @@ https_first_disabled = true
[browser_focus.js]
[browser_text_leaf.js]
[browser_webarea.js]
skip-if = os == 'mac' && !debug #1648813
[browser_text_basics.js]
[browser_text_input.js]
skip-if = os == 'mac' && debug # Bug 1664577
[browser_rotor.js]
[browser_rootgroup.js]
[browser_text_selection.js]

View File

@ -1645,9 +1645,6 @@ pref("security.app_menu.recordEventTelemetry", true);
// Block insecure active content on https pages
pref("security.mixed_content.block_active_content", true);
// Show in-content login form warning UI for insecure login fields
pref("security.insecure_field_warning.contextual.enabled", true);
// Show degraded UI for http pages.
pref("security.insecure_connection_icon.enabled", true);
// Show degraded UI for http pages in private mode.

View File

@ -4740,6 +4740,10 @@
options += "," + name + "=" + aOptions[name];
}
if (PrivateBrowsingUtils.isWindowPrivate(window)) {
options += ",private=1";
}
// Play the tab closing animation to give immediate feedback while
// waiting for the new window to appear.
// content area when the docshells are swapped.
@ -4762,7 +4766,7 @@
* to a new browser window, unless it is (they are) already the only tab(s)
* in the current window, in which case this will do nothing.
*/
replaceTabsWithWindow(contextTab, aOptions) {
replaceTabsWithWindow(contextTab, aOptions = {}) {
let tabs;
if (contextTab.multiselected) {
tabs = this.selectedTabs;

View File

@ -12,11 +12,8 @@ support-files =
[browser_877447_skip_missing_ids.js]
[browser_878452_drag_to_panel.js]
[browser_884402_customize_from_overflow.js]
skip-if = os == "linux"
[browser_885052_customize_mode_observers_disabed.js]
tags = fullscreen
# Bug 951403 - Disabled on OSX for frequent failures
skip-if = os == "mac"
[browser_885530_showInPrivateBrowsing.js]
[browser_886323_buildArea_removable_nodes.js]
@ -28,15 +25,12 @@ skip-if = os == "linux"
[browser_901207_searchbar_in_panel.js]
[browser_913972_currentset_overflow.js]
skip-if = os == "linux"
[browser_914138_widget_API_overflowable_toolbar.js]
skip-if = os == "linux"
[browser_918049_skipintoolbarset_dnd.js]
[browser_923857_customize_mode_event_wrapping_during_reset.js]
[browser_927717_customize_drag_empty_toolbar.js]
[browser_934113_menubar_removable.js]
# Because this test is about the menubar, it can't be run on mac
skip-if = os == "mac"
@ -54,31 +48,20 @@ skip-if = verify
[browser_944887_destroyWidget_should_destroy_in_palette.js]
[browser_945739_showInPrivateBrowsing_customize_mode.js]
[browser_947914_button_addons.js]
skip-if = os == "linux" # Intermittent failures
[browser_947914_button_copy.js]
skip-if = os == "linux" # Intermittent failures on Linux
[browser_947914_button_cut.js]
skip-if = os == "linux" # Intermittent failures on Linux
[browser_947914_button_find.js]
skip-if = os == "linux" # Intermittent failures
[browser_947914_button_history.js]
https_first_disabled = true
support-files = dummy_history_item.html
skip-if = os == "linux" # Intermittent failures
[browser_947914_button_newPrivateWindow.js]
skip-if = os == "linux" # Intermittent failures
[browser_947914_button_newWindow.js]
skip-if = os == "linux" # Intermittent failures
[browser_947914_button_paste.js]
skip-if = os == "linux" # Intermittent failures on Linux
[browser_947914_button_print.js]
skip-if = os == "linux" # Intermittent failures on Linux
[browser_947914_button_zoomIn.js]
skip-if = os == "linux" # Intermittent failures
[browser_947914_button_zoomOut.js]
skip-if = os == "linux" # Intermittent failures
[browser_947914_button_zoomReset.js]
skip-if = os == "linux" # Intermittent failures
skip-if = (os == "linux" && debug) # Intermittent failures
[browser_947987_removable_default.js]
[browser_948985_non_removable_defaultArea.js]
[browser_952963_areaType_getter_no_area.js]
@ -94,8 +77,7 @@ skip-if = verify
[browser_972267_customizationchange_events.js]
[browser_973641_button_addon.js]
[browser_976792_insertNodeInWindow.js]
skip-if =
os == "linux"
skip-if = os == "linux"
[browser_978084_dragEnd_after_move.js]
skip-if = verify
[browser_980155_add_overflow_toolbar.js]
@ -105,7 +87,6 @@ skip-if = verify
skip-if = verify
[browser_982656_restore_defaults_builtin_widgets.js]
[browser_984455_bookmarks_items_reparenting.js]
skip-if = os == "linux"
[browser_985815_propagate_setToolbarVisibility.js]
[browser_987177_destroyWidget_xul.js]
skip-if = verify
@ -142,7 +123,6 @@ skip-if = verify
[browser_ctrl_click_panel_opening.js]
[browser_currentset_post_reset.js]
[browser_customizemode_contextmenu_menubuttonstate.js]
skip-if = os == "win" && bits == 64 # 1526429
[browser_customizemode_lwthemes.js]
[browser_customizemode_uidensity.js]
[browser_disable_commands_customize.js]

View File

@ -1719,7 +1719,6 @@ var Policies = {
"security.default_personal_cert",
"security.insecure_connection_text.enabled",
"security.insecure_connection_text.pbmode.enabled",
"security.insecure_field_warning.contextual.enabled",
"security.mixed_content.block_active_content",
"security.osclientcerts.autoload",
"security.ssl.errorReporting.enabled",

View File

@ -184,8 +184,11 @@ export class BaseContent extends React.PureComponent {
highlightsEnabled: prefs["feeds.section.highlights"],
showSponsoredTopSitesEnabled: prefs.showSponsoredTopSites,
showSponsoredPocketEnabled: prefs.showSponsored,
recentSavesExperiment: prefs["discoverystream.recentSaves.enabled"],
showRecentSavesEnabled: prefs.showRecentSaves,
topSitesRowsCount: prefs.topSitesRows,
};
const pocketRegion = prefs["feeds.system.topstories"];
const { mayHaveSponsoredTopSites } = prefs;

View File

@ -43,6 +43,8 @@ export class ContentSection extends React.PureComponent {
highlightsEnabled,
showSponsoredTopSitesEnabled,
showSponsoredPocketEnabled,
showRecentSavesEnabled,
recentSavesExperiment,
topSitesRowsCount,
} = this.props.enabledSections;
@ -194,6 +196,25 @@ export class ContentSection extends React.PureComponent {
data-l10n-id="newtab-custom-pocket-sponsored"
/>
</div>
{recentSavesExperiment && (
<div className="check-wrapper" role="presentation">
<input
id="recent-saves-pocket"
className="sponsored-checkbox"
disabled={!pocketEnabled}
checked={showRecentSavesEnabled}
type="checkbox"
onChange={this.onPreferenceSelect}
preference="showRecentSaves"
eventSource="POCKET_RECENT_SAVES"
/>
<label
className="sponsored"
htmlFor="recent-saves-pocket"
data-l10n-id="newtab-custom-pocket-show-recent-saves"
/>
</div>
)}
</div>
</div>
)}

View File

@ -1,4 +1,3 @@
@media (max-height: 701px) {
.personalize-button {
position: absolute;
@ -250,10 +249,10 @@
.more-info-pocket-wrapper {
margin-inline-start: -2px;
overflow: hidden;
transition: max-height 250ms $customize-menu-expand-bezier;
transition: height 250ms $customize-menu-expand-bezier;
&.shrink {
max-height: 0;
height: 0;
}
.more-information {
@ -268,8 +267,6 @@
}
.more-info-top-wrapper {
max-height: 78px;
.more-information {
top: -77px;
}
@ -280,8 +277,6 @@
}
.more-info-pocket-wrapper {
max-height: 35px;
.more-information {
top: -35px;
}

View File

@ -1824,11 +1824,11 @@ main.has-snippet {
.home-section .section .more-info-pocket-wrapper {
margin-inline-start: -2px;
overflow: hidden;
transition: max-height 250ms cubic-bezier(0.82, 0.085, 0.395, 0.895);
transition: height 250ms cubic-bezier(0.82, 0.085, 0.395, 0.895);
}
.home-section .section .more-info-top-wrapper.shrink,
.home-section .section .more-info-pocket-wrapper.shrink {
max-height: 0;
height: 0;
}
.home-section .section .more-info-top-wrapper .more-information,
.home-section .section .more-info-pocket-wrapper .more-information {
@ -1840,18 +1840,12 @@ main.has-snippet {
.home-section .section .more-info-pocket-wrapper .more-information.expand {
top: 0;
}
.home-section .section .more-info-top-wrapper {
max-height: 78px;
}
.home-section .section .more-info-top-wrapper .more-information {
top: -77px;
}
.home-section .section .more-info-top-wrapper .check-wrapper {
margin-top: 10px;
}
.home-section .section .more-info-pocket-wrapper {
max-height: 35px;
}
.home-section .section .more-info-pocket-wrapper .more-information {
top: -35px;
}

View File

@ -1828,11 +1828,11 @@ main.has-snippet {
.home-section .section .more-info-pocket-wrapper {
margin-inline-start: -2px;
overflow: hidden;
transition: max-height 250ms cubic-bezier(0.82, 0.085, 0.395, 0.895);
transition: height 250ms cubic-bezier(0.82, 0.085, 0.395, 0.895);
}
.home-section .section .more-info-top-wrapper.shrink,
.home-section .section .more-info-pocket-wrapper.shrink {
max-height: 0;
height: 0;
}
.home-section .section .more-info-top-wrapper .more-information,
.home-section .section .more-info-pocket-wrapper .more-information {
@ -1844,18 +1844,12 @@ main.has-snippet {
.home-section .section .more-info-pocket-wrapper .more-information.expand {
top: 0;
}
.home-section .section .more-info-top-wrapper {
max-height: 78px;
}
.home-section .section .more-info-top-wrapper .more-information {
top: -77px;
}
.home-section .section .more-info-top-wrapper .check-wrapper {
margin-top: 10px;
}
.home-section .section .more-info-pocket-wrapper {
max-height: 35px;
}
.home-section .section .more-info-pocket-wrapper .more-information {
top: -35px;
}

View File

@ -1824,11 +1824,11 @@ main.has-snippet {
.home-section .section .more-info-pocket-wrapper {
margin-inline-start: -2px;
overflow: hidden;
transition: max-height 250ms cubic-bezier(0.82, 0.085, 0.395, 0.895);
transition: height 250ms cubic-bezier(0.82, 0.085, 0.395, 0.895);
}
.home-section .section .more-info-top-wrapper.shrink,
.home-section .section .more-info-pocket-wrapper.shrink {
max-height: 0;
height: 0;
}
.home-section .section .more-info-top-wrapper .more-information,
.home-section .section .more-info-pocket-wrapper .more-information {
@ -1840,18 +1840,12 @@ main.has-snippet {
.home-section .section .more-info-pocket-wrapper .more-information.expand {
top: 0;
}
.home-section .section .more-info-top-wrapper {
max-height: 78px;
}
.home-section .section .more-info-top-wrapper .more-information {
top: -77px;
}
.home-section .section .more-info-top-wrapper .check-wrapper {
margin-top: 10px;
}
.home-section .section .more-info-pocket-wrapper {
max-height: 35px;
}
.home-section .section .more-info-pocket-wrapper .more-information {
top: -35px;
}

View File

@ -14137,6 +14137,8 @@ class ContentSection extends (external_React_default()).PureComponent {
highlightsEnabled,
showSponsoredTopSitesEnabled,
showSponsoredPocketEnabled,
showRecentSavesEnabled,
recentSavesExperiment,
topSitesRowsCount
} = this.props.enabledSections;
return /*#__PURE__*/external_React_default().createElement("div", {
@ -14260,6 +14262,22 @@ class ContentSection extends (external_React_default()).PureComponent {
className: "sponsored",
htmlFor: "sponsored-pocket",
"data-l10n-id": "newtab-custom-pocket-sponsored"
})), recentSavesExperiment && /*#__PURE__*/external_React_default().createElement("div", {
className: "check-wrapper",
role: "presentation"
}, /*#__PURE__*/external_React_default().createElement("input", {
id: "recent-saves-pocket",
className: "sponsored-checkbox",
disabled: !pocketEnabled,
checked: showRecentSavesEnabled,
type: "checkbox",
onChange: this.onPreferenceSelect,
preference: "showRecentSaves",
eventSource: "POCKET_RECENT_SAVES"
}), /*#__PURE__*/external_React_default().createElement("label", {
className: "sponsored",
htmlFor: "recent-saves-pocket",
"data-l10n-id": "newtab-custom-pocket-show-recent-saves"
})))))), /*#__PURE__*/external_React_default().createElement("div", {
id: "recent-section",
className: "section"
@ -14773,6 +14791,8 @@ class BaseContent extends (external_React_default()).PureComponent {
highlightsEnabled: prefs["feeds.section.highlights"],
showSponsoredTopSitesEnabled: prefs.showSponsoredTopSites,
showSponsoredPocketEnabled: prefs.showSponsored,
recentSavesExperiment: prefs["discoverystream.recentSaves.enabled"],
showRecentSavesEnabled: prefs.showRecentSaves,
topSitesRowsCount: prefs.topSitesRows
};
const pocketRegion = prefs["feeds.system.topstories"];

View File

@ -456,6 +456,20 @@ const PREFS_CONFIG = new Map([
value: "{}",
},
],
[
"discoverystream.recentSaves.enabled",
{
title: "Control whether recent saves are available to display on Newtab",
value: false,
},
],
[
"showRecentSaves",
{
title: "Control whether a user wants recent saves visible on Newtab",
value: true,
},
],
]);
// Array of each feed's FEEDS_CONFIG factory and values to add to PREFS_CONFIG

View File

@ -1,5 +1,4 @@
[DEFAULT]
skip-if = tsan #Bug 1677049
support-files =
head.js
../ds_layout.json
@ -23,7 +22,7 @@ prefs =
# Bug 1694957 is why we need dom.ipc.processPrelaunch.delayMs=0
[browser_basic_endtoend.js]
skip-if = asan || tsan || debug #Bug 1651277
skip-if = os == 'linux' && (asan || debug) # Bug 1651277
[browser_bump_version.js]
[browser_disabled.js]
[browser_experiments_api_control.js]

View File

@ -35,11 +35,15 @@ XPCOMUtils.defineLazyGetter(lazy, "logConsole", function() {
* The recommended snapshot.
* @property {number} score
* The score for this snapshot.
* @property {string | undefined} source
* The source that provided the largest score for this snapshot.
*/
/**
* @typedef {object} RecommendationGroup
* A set of recommendations with an associated weight to apply to their scores.
* @property {string} source
* The source of the group.
* @property {Recommendation[]} recommendations
* The recommended snapshot.
* @property {number} weight
@ -104,8 +108,8 @@ const SnapshotScorer = new (class SnapshotScorer {
* The snapshot.
* @property {number} snapshotScore
* The score generated from this snapshot.
* @property {number} sourceScore
* The score from the source of the recommendation.
* @property {Map<string, number>} sourceScore
* The score from the sources of the recommendation.
*/
/**
@ -117,7 +121,7 @@ const SnapshotScorer = new (class SnapshotScorer {
let currentDate = this.#dateOverride ?? Date.now();
let currentSessionUrls = selectionContext.getCurrentSessionUrls();
for (let { recommendations, weight } of recommendationGroups) {
for (let { source, recommendations, weight } of recommendationGroups) {
for (let { snapshot, score } of recommendations) {
if (
selectionContext.filterAdult &&
@ -129,8 +133,8 @@ const SnapshotScorer = new (class SnapshotScorer {
let currentScore = combined.get(snapshot.url);
if (currentScore) {
// We've already generated the snapshot specific score, update the
// source specific score.
currentScore.sourceScore += score * weight;
// source specific scores.
currentScore.sourceScore.set(source, score * weight);
} else {
currentScore = {
snapshot,
@ -139,7 +143,7 @@ const SnapshotScorer = new (class SnapshotScorer {
currentDate,
currentSessionUrls
),
sourceScore: score * weight,
sourceScore: new Map([[source, score * weight]]),
};
combined.set(snapshot.url, currentScore);
@ -151,11 +155,26 @@ const SnapshotScorer = new (class SnapshotScorer {
for (let currentScore of combined.values()) {
let recommendation = {
snapshot: currentScore.snapshot,
score: currentScore.snapshotScore + currentScore.sourceScore,
score: currentScore.snapshotScore,
source: undefined,
};
// Add up all the source scores and identify the largest.
let maxScore = null;
let source = null;
for (let [id, score] of currentScore.sourceScore) {
recommendation.score += score;
if (maxScore === null || maxScore < score) {
maxScore = score;
source = id;
}
}
recommendation.source = source;
lazy.logConsole.debug(
`Scored ${recommendation.score} for ${recommendation.snapshot.url}`
`Scored ${recommendation.score} for ${recommendation.snapshot.url} from source ${recommendation.source}`
);
if (recommendation.score >= this.snapshotThreshold) {

View File

@ -60,6 +60,8 @@ XPCOMUtils.defineLazyGetter(lazy, "logConsole", function() {
* The snapshot this recommendation relates to.
* @property {number} score
* The score for the snapshot.
* @property {string | undefined} source
* The source that provided the largest score for this snapshot.
*/
/**
@ -185,19 +187,19 @@ class SnapshotSelector extends EventEmitter {
/**
* Called internally when the set of snapshots has been generated.
*
* @param {Snapshot[]} snapshots
* @param {Recommendation[]} recommendations
*/
#snapshotsGenerated(snapshots) {
#snapshotsGenerated(recommendations) {
// If this instance has been destroyed then do nothing.
if (!this.#task) {
return;
}
lazy.logConsole.debug(
"Generated snapshots",
snapshots.map(s => s.url)
"Generated recommendations",
recommendations.map(s => s.snapshot.url)
);
this.emit("snapshots-updated", snapshots);
this.emit("snapshots-updated", recommendations);
}
/**
@ -234,18 +236,21 @@ class SnapshotSelector extends EventEmitter {
return !context.filterAdult || !lazy.FilterAdult.isAdultUrl(snapshot.url);
});
snapshots = lazy.SnapshotScorer.dedupeSnapshots(
snapshots.map(s => ({
snapshot: s,
}))
)
.slice(0, context.count)
.map(s => s.snapshot)
.slice();
let recommendations = snapshots.map((snapshot, index) => ({
source: "recent",
score: snapshots.length - index,
snapshot,
}));
lazy.PlacesUIUtils.insertTitleStartDiffs(snapshots);
recommendations = lazy.SnapshotScorer.dedupeSnapshots(
recommendations
).slice(0, context.count);
this.#snapshotsGenerated(snapshots);
lazy.PlacesUIUtils.insertTitleStartDiffs(
recommendations.map(s => s.snapshot)
);
this.#snapshotsGenerated(recommendations);
}
/**
@ -284,7 +289,7 @@ class SnapshotSelector extends EventEmitter {
)
);
return { recommendations, weight };
return { source: key, recommendations, weight };
}
)
);
@ -294,13 +299,13 @@ class SnapshotSelector extends EventEmitter {
...recommendationGroups
);
let snapshots = recommendations
.slice(0, context.count)
.map(r => r.snapshot);
recommendations = recommendations.slice(0, context.count);
lazy.PlacesUIUtils.insertTitleStartDiffs(snapshots);
lazy.PlacesUIUtils.insertTitleStartDiffs(
recommendations.map(r => r.snapshot)
);
this.#snapshotsGenerated(snapshots);
this.#snapshotsGenerated(recommendations);
}
/**

View File

@ -172,8 +172,10 @@ function assertRecentDate(date, threshold = 1) {
function assertSnapshot(actual, expected) {
// This may be a recommendation.
let score = 0;
let source = null;
if ("snapshot" in actual) {
score = actual.score;
source = actual.source;
actual = actual.snapshot;
}
@ -218,6 +220,13 @@ function assertSnapshot(actual, expected) {
"Should have the Snapshot URL's common name."
);
}
if (expected.source) {
Assert.equal(
source,
expected.source,
"Should have the correct recommendation source."
);
}
if (expected.scoreEqualTo != null) {
Assert.equal(
score,
@ -432,6 +441,14 @@ function assertRecommendations(recommendations, expected) {
expected[i].score,
`Should have set the expected score for ${expected[i].url}`
);
if (expected[i].source) {
Assert.equal(
recommendations[i].source,
expected[i].source,
`Should have set the correct source for ${expected[i].url}`
);
}
}
}

View File

@ -213,6 +213,7 @@ add_task(async function test_scores() {
let snapshots = await SnapshotScorer.combineAndScore(
{ getCurrentSessionUrls: () => sessionUrls },
{
source: "foo",
recommendations: [{ snapshot, score: data.sourceScore ?? 0 }],
weight: 3.0,
}
@ -221,6 +222,7 @@ add_task(async function test_scores() {
assertRecommendations(snapshots, [
{
url,
source: "foo",
score: data.score,
},
]);
@ -247,7 +249,7 @@ add_task(async function test_score_threshold() {
let snapshots = await SnapshotScorer.combineAndScore(
{ getCurrentSessionUrls: () => sessionUrls },
{ recommendations: sourceRecommendations, weight: 3.0 }
{ source: "bar", recommendations: sourceRecommendations, weight: 3.0 }
);
assertRecommendations(
@ -256,6 +258,7 @@ add_task(async function test_score_threshold() {
THRESHOLD_TESTS.map((t, i) => {
return {
url: `https://example.com/${i + SCORE_TESTS.length}`,
source: "bar",
score: t.score,
};
}).filter(t => t.score > THRESHOLD)

View File

@ -33,10 +33,12 @@ add_task(async function test_combining_throw_away_first() {
let combined = SnapshotScorer.combineAndScore(
{ getCurrentSessionUrls: () => new Set([TEST_URL1, TEST_URL2]) },
{
source: "foo",
recommendations: [{ snapshot: snapshot1, score: 0.5 }],
weight: 3.0,
},
{
source: "bar",
recommendations: [
{ snapshot: snapshot2, score: 0.5 },
{ snapshot: snapshot1, score: 1 },
@ -49,10 +51,12 @@ add_task(async function test_combining_throw_away_first() {
{
url: TEST_URL1,
score: 7.5,
source: "bar",
},
{
url: TEST_URL2,
score: 4.5,
source: "bar",
},
]);
});
@ -67,6 +71,7 @@ add_task(async function test_combining_throw_away_second_and_sort() {
{
recommendations: [{ snapshot: snapshot2, score: 1 }],
weight: 3.0,
source: "foo",
},
{
recommendations: [
@ -74,6 +79,7 @@ add_task(async function test_combining_throw_away_second_and_sort() {
{ snapshot: snapshot2, score: 0.5 },
],
weight: 3.0,
source: "bar",
}
);
@ -81,10 +87,12 @@ add_task(async function test_combining_throw_away_second_and_sort() {
{
url: TEST_URL2,
score: 7.5,
source: "foo",
},
{
url: TEST_URL1,
score: 4.5,
source: "bar",
},
]);
});

View File

@ -68,7 +68,9 @@ add_task(async function test_enable_overlapping() {
snapshots = await snapshotPromise;
// Only snapshots with overlapping interactions should be selected
await assertSnapshotList(snapshots, [{ url: TEST_URL2 }]);
await assertSnapshotList(snapshots, [
{ url: TEST_URL2, source: "Overlapping" },
]);
});
add_task(async function test_overlapping_with_scoring() {
@ -89,5 +91,7 @@ add_task(async function test_overlapping_with_scoring() {
selector.rebuild();
snapshots = await snapshotPromise;
await assertSnapshotList(snapshots, [{ url: TEST_URL2 }]);
await assertSnapshotList(snapshots, [
{ url: TEST_URL2, source: "Overlapping" },
]);
});

View File

@ -31,7 +31,7 @@ add_task(async function test_interactions_recent() {
await Snapshots.add({ url: TEST_URL1 });
snapshots = await snapshotPromise;
await assertSnapshotList(snapshots, [{ url: TEST_URL1 }]);
await assertSnapshotList(snapshots, [{ url: TEST_URL1, source: "recent" }]);
// Changing the url should generate new snapshots and should exclude the
// current url.
@ -45,32 +45,41 @@ add_task(async function test_interactions_recent() {
selector.updateDetailsAndRebuild({ url: TEST_URL2 });
snapshots = await snapshotPromise;
await assertSnapshotList(snapshots, [{ url: TEST_URL1 }]);
await assertSnapshotList(snapshots, [{ url: TEST_URL1, source: "recent" }]);
snapshotPromise = selector.once("snapshots-updated");
await Snapshots.add({ url: TEST_URL2 });
snapshots = await snapshotPromise;
await assertSnapshotList(snapshots, [{ url: TEST_URL1 }]);
await assertSnapshotList(snapshots, [{ url: TEST_URL1, source: "recent" }]);
snapshotPromise = selector.once("snapshots-updated");
await Snapshots.add({ url: TEST_URL3 });
snapshots = await snapshotPromise;
await assertSnapshotList(snapshots, [{ url: TEST_URL1 }, { url: TEST_URL3 }]);
await assertSnapshotList(snapshots, [
{ url: TEST_URL1, source: "recent" },
{ url: TEST_URL3, source: "recent" },
]);
snapshotPromise = selector.once("snapshots-updated");
selector.updateDetailsAndRebuild({ url: TEST_URL3 });
snapshots = await snapshotPromise;
await assertSnapshotList(snapshots, [{ url: TEST_URL2 }, { url: TEST_URL1 }]);
await assertSnapshotList(snapshots, [
{ url: TEST_URL2, source: "recent" },
{ url: TEST_URL1, source: "recent" },
]);
snapshotPromise = selector.once("snapshots-updated");
selector.updateDetailsAndRebuild({ url: TEST_URL4 });
snapshots = await snapshotPromise;
// The snapshot count is limited to 2.
await assertSnapshotList(snapshots, [{ url: TEST_URL2 }, { url: TEST_URL1 }]);
await assertSnapshotList(snapshots, [
{ url: TEST_URL2, source: "recent" },
{ url: TEST_URL1, source: "recent" },
]);
await reset();
});

View File

@ -31,7 +31,7 @@ skip-if =
[browser_pdf_disabled.js]
[browser_search_no_results_change_category.js]
[browser_search_within_preferences_1.js]
skip-if = (os == 'win' && (processor == "x86_64" || processor == "aarch64")) # Bug 1480314, aarch64 due to 1536560
skip-if = (os == 'win' && processor == "aarch64") # Bug 1536560
[browser_search_within_preferences_2.js]
[browser_search_within_preferences_command.js]
[browser_search_subdialog_tooltip_saved_addresses.js]
@ -71,8 +71,6 @@ skip-if = socketprocess_networking
[browser_contentblocking_standard_tcp_toggle.js]
[browser_cookies_exceptions.js]
[browser_defaultbrowser_alwayscheck.js]
[browser_healthreport.js]
skip-if = true || !healthreport # Bug 1185403 for the "true"
[browser_homepages_filter_aboutpreferences.js]
[browser_homepages_use_bookmark.js]
[browser_homepage_default.js]
@ -112,8 +110,6 @@ support-files =
engine1/manifest.json
engine2/manifest.json
[browser_searchRestoreDefaults.js]
skip-if =
apple_catalina && debug # high frequency intermittent - crashing
[browser_searchShowSuggestionsFirst.js]
[browser_searchsuggestions.js]
[browser_security-1.js]
@ -133,13 +129,8 @@ support-files =
[browser_sync_chooseWhatToSync.js]
[browser_sync_disabled.js]
[browser_sync_pairing.js]
[browser_telemetry.js]
# Skip this test on Android as FHR and Telemetry are separate systems there.
skip-if = !telemetry || (os == 'linux' && debug)
[browser_warning_permanent_private_browsing.js]
[browser_containers_name_input.js]
run-if = nightly_build # Containers is enabled only on Nightly
[browser_fluent.js]
[browser_hometab_restore_defaults.js]
https_first_disabled = true
skip-if = debug #Bug 1517966

View File

@ -1,81 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
function runPaneTest(fn) {
open_preferences(async win => {
let doc = win.document;
await win.gotoPref("paneAdvanced");
let advancedPrefs = doc.getElementById("advancedPrefs");
let tab = doc.getElementById("dataChoicesTab");
advancedPrefs.selectedTab = tab;
fn(win, doc);
});
}
function test() {
waitForExplicitFinish();
resetPreferences();
registerCleanupFunction(resetPreferences);
runPaneTest(testBasic);
}
function testBasic(win, doc) {
is(
Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED),
true,
"Health Report upload enabled on app first run."
);
let checkbox = doc.getElementById("submitHealthReportBox");
ok(checkbox);
is(
checkbox.checked,
true,
"Health Report checkbox is checked on app first run."
);
checkbox.checked = false;
checkbox.doCommand();
is(
Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED),
false,
"Unchecking checkbox opts out of FHR upload."
);
checkbox.checked = true;
checkbox.doCommand();
is(
Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED),
true,
"Checking checkbox allows FHR upload."
);
win.close();
Services.prefs.lockPref(FHR_UPLOAD_ENABLED);
runPaneTest(testUploadDisabled);
}
function testUploadDisabled(win, doc) {
ok(
Services.prefs.prefIsLocked(FHR_UPLOAD_ENABLED),
"Upload enabled flag is locked."
);
let checkbox = doc.getElementById("submitHealthReportBox");
is(
checkbox.getAttribute("disabled"),
"true",
"Checkbox is disabled if upload flag is locked."
);
Services.prefs.unlockPref(FHR_UPLOAD_ENABLED);
win.close();
finish();
}
function resetPreferences() {
Services.prefs.clearUserPref(FHR_UPLOAD_ENABLED);
}

View File

@ -1,62 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
function runPaneTest(fn) {
open_preferences(async win => {
let doc = win.document;
await win.gotoPref("paneAdvanced");
let advancedPrefs = doc.getElementById("advancedPrefs");
let tab = doc.getElementById("dataChoicesTab");
advancedPrefs.selectedTab = tab;
fn(win, doc);
});
}
function test() {
waitForExplicitFinish();
resetPreferences();
registerCleanupFunction(resetPreferences);
runPaneTest(testTelemetryState);
}
function testTelemetryState(win, doc) {
let fhrCheckbox = doc.getElementById("submitHealthReportBox");
Assert.ok(
fhrCheckbox.checked,
"Health Report checkbox is checked on app first run."
);
let telmetryCheckbox = doc.getElementById("submitTelemetryBox");
Assert.ok(
!telmetryCheckbox.disabled,
"Telemetry checkbox must be enabled if FHR is checked."
);
Assert.ok(
Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED),
"Telemetry must be enabled if the checkbox is ticked."
);
// Uncheck the FHR checkbox and make sure that Telemetry checkbox gets disabled.
fhrCheckbox.click();
Assert.ok(
telmetryCheckbox.disabled,
"Telemetry checkbox must be disabled if FHR is unchecked."
);
Assert.ok(
!Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED),
"Telemetry must be disabled if the checkbox is unticked."
);
win.close();
finish();
}
function resetPreferences() {
Services.prefs.clearUserPref("datareporting.healthreport.uploadEnabled");
Services.prefs.clearUserPref(PREF_TELEMETRY_ENABLED);
}

View File

@ -9,9 +9,8 @@ support-files =
[browser_clearSiteData.js]
[browser_siteData.js]
skip-if = debug && ((os == 'mac') || (os == 'linux')) || os == "win" #Bug 1533681
[browser_siteData2.js]
skip-if = win10_2004 # Bug 1723573
skip-if = win10_2004 && (!debug || !asan) # Bug 1669937
[browser_siteData3.js]
[browser_siteData_multi_select.js]
skip-if = tsan # Bug 1683730

View File

@ -55,7 +55,6 @@ support-files =
telemetrySearchSuggestions.sjs
telemetrySearchSuggestions.xml
[browser_search_telemetry_sources_ads.js]
skip-if = !debug && (os == 'linux') # Bug 1515466
tags = search-telemetry
support-files =
searchTelemetry.html
@ -75,7 +74,6 @@ support-files =
[browser_searchbar_enter.js]
[browser_searchbar_default.js]
[browser_searchbar_openpopup.js]
skip-if = os == "linux" # Linux has different focus behaviours.
[browser_searchbar_keyboard_navigation.js]
[browser_searchbar_results.js]
[browser_searchbar_smallpanel_keyboard_navigation.js]

View File

@ -32,6 +32,7 @@
#include "mozilla/dom/Promise.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/intl/Localization.h"
#include "WindowsDefaultBrowser.h"
#include "WindowsUserChoice.h"
#include "nsLocalFile.h"
@ -95,6 +96,7 @@ PSSTDAPI PropVariantToString(REFPROPVARIANT propvar, PWSTR psz, UINT cch);
using mozilla::IsWin8OrLater;
using namespace mozilla;
using mozilla::intl::Localization;
struct SysFreeStringDeleter {
void operator()(BSTR aPtr) { ::SysFreeString(aPtr); }
@ -893,7 +895,12 @@ nsWindowsShellService::CreateShortcut(
// Constructs a path to an installer-created shortcut, under a directory
// specified by a CSIDL.
static nsresult GetShortcutPath(int aCSIDL, bool aPrivateBrowsing,
// Earlier versions of this code generated the shortcut name themselves.
// This is no longer possible because the Private Browsing shortcut name
// is localized, the Localization class does not work off main thread,
// and this code is called off main thread in some contexts -- therefore
// it must be passed through by any callers.
static nsresult GetShortcutPath(int aCSIDL, const nsAString& aShortcutName,
/* out */ nsAutoString& aPath) {
wchar_t folderPath[MAX_PATH] = {};
HRESULT hr = SHGetFolderPathW(nullptr, aCSIDL, nullptr, SHGFP_TYPE_CURRENT,
@ -909,36 +916,7 @@ static nsresult GetShortcutPath(int aCSIDL, bool aPrivateBrowsing,
if (aPath[aPath.Length() - 1] != '\\') {
aPath.AppendLiteral("\\");
}
// NOTE: In the installer, non-private shortcuts are named
// "${BrandShortName}.lnk". This is set from MOZ_APP_DISPLAYNAME in
// defines.nsi.in. (Except in dev edition where it's explicitly set to
// "Firefox Developer Edition" in branding.nsi, which matches
// MOZ_APP_DISPLAYNAME in aurora/configure.sh.)
//
// If this changes, we could expand this to check shortcuts_log.ini,
// which records the name of the shortcuts as created by the installer.
//
// Private shortcuts are not created by the installer (they're created
// upon user request, ultimately by CreateShortcutImpl, and recorded in
// a separate shortcuts log. As with non-private shortcuts they have a known
// name - so there's no need to look through logs to find them.
if (aPrivateBrowsing) {
// This is explicitly not localized until we finalize the English string.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1758961 tracks following
// up on this before it becomes user visible.
/*nsTArray<nsCString> resIds{
"branding/brand.ftl"_ns,
"browser/browser.ftl"_ns,
};
RefPtr<Localization> l10n = Localization::Create(resIds, true);
nsAutoCString pbStr;
IgnoredErrorResult rv;
l10n->FormatValueSync("private-browsing-shortcut-text"_ns, {}, pbStr, rv);
aPath.Append(NS_ConvertUTF8toUTF16(pbStr));*/
aPath.AppendLiteral(MOZ_APP_DISPLAYNAME " Private Browsing.lnk");
} else {
aPath.AppendLiteral(MOZ_APP_DISPLAYNAME ".lnk");
}
aPath.Append(aShortcutName);
return NS_OK;
}
@ -959,6 +937,8 @@ static nsresult GetShortcutPath(int aCSIDL, bool aPrivateBrowsing,
// aAUMID the AUMID to check for
// aExePath the target exe path to check for, should be a long path where
// possible
// aShortcutName the filename portion (excluding extension) of the shortcut
// to look for.
// aShortcutPath outparam, set to matching shortcut path if NS_OK is returned.
//
// Returns
@ -969,12 +949,12 @@ static nsresult GetShortcutPath(int aCSIDL, bool aPrivateBrowsing,
// NS_OK if the shortcut matches
static nsresult GetMatchingShortcut(int aCSIDL, const nsAString& aAUMID,
const wchar_t aExePath[MAXPATHLEN],
bool aPrivateBrowsing,
const nsAString& aShortcutName,
/* out */ nsAutoString& aShortcutPath) {
nsresult result = NS_ERROR_FAILURE;
nsAutoString path;
nsresult rv = GetShortcutPath(aCSIDL, aPrivateBrowsing, path);
nsresult rv = GetShortcutPath(aCSIDL, aShortcutName, path);
if (NS_WARN_IF(NS_FAILED(rv))) {
return result;
}
@ -1050,7 +1030,7 @@ static nsresult GetMatchingShortcut(int aCSIDL, const nsAString& aAUMID,
}
static nsresult FindMatchingShortcut(const nsAString& aAppUserModelId,
const bool aPrivateBrowsing,
const nsAString& aShortcutName,
nsAutoString& aShortcutPath) {
wchar_t exePath[MAXPATHLEN] = {};
if (NS_WARN_IF(NS_FAILED(BinaryPath::GetLong(exePath)))) {
@ -1065,7 +1045,7 @@ static nsresult FindMatchingShortcut(const nsAString& aAppUserModelId,
// if it refers to the same file. This should be rare, and the worst
// outcome would be failure to pin, so the risk is acceptable.
nsresult rv = GetMatchingShortcut(shortcutCSIDL, aAppUserModelId, exePath,
aPrivateBrowsing, aShortcutPath);
aShortcutName, aShortcutPath);
if (NS_SUCCEEDED(rv)) {
return NS_OK;
}
@ -1078,9 +1058,38 @@ NS_IMETHODIMP
nsWindowsShellService::HasMatchingShortcut(const nsAString& aAppUserModelId,
const bool aPrivateBrowsing,
bool* aHasMatch) {
// NOTE: In the installer, non-private shortcuts are named
// "${BrandShortName}.lnk". This is set from MOZ_APP_DISPLAYNAME in
// defines.nsi.in. (Except in dev edition where it's explicitly set to
// "Firefox Developer Edition" in branding.nsi, which matches
// MOZ_APP_DISPLAYNAME in aurora/configure.sh.)
//
// If this changes, we could expand this to check shortcuts_log.ini,
// which records the name of the shortcuts as created by the installer.
//
// Private shortcuts are not created by the installer (they're created
// upon user request, ultimately by CreateShortcutImpl, and recorded in
// a separate shortcuts log. As with non-private shortcuts they have a known
// name - so there's no need to look through logs to find them.
nsAutoString shortcutName;
if (aPrivateBrowsing) {
nsTArray<nsCString> resIds = {
"branding/brand.ftl"_ns,
"browser/browser.ftl"_ns,
};
RefPtr<Localization> l10n = Localization::Create(resIds, true);
nsAutoCString pbStr;
IgnoredErrorResult rv;
l10n->FormatValueSync("private-browsing-shortcut-text"_ns, {}, pbStr, rv);
shortcutName.Append(NS_ConvertUTF8toUTF16(pbStr));
shortcutName.AppendLiteral(".lnk");
} else {
shortcutName.AppendLiteral(MOZ_APP_DISPLAYNAME ".lnk");
}
nsAutoString shortcutPath;
nsresult rv =
FindMatchingShortcut(aAppUserModelId, aPrivateBrowsing, shortcutPath);
FindMatchingShortcut(aAppUserModelId, shortcutName, shortcutPath);
if (SUCCEEDED(rv)) {
*aHasMatch = true;
} else {
@ -1293,14 +1302,15 @@ static nsresult PinCurrentAppToTaskbarWin10(bool aCheckOnly,
static nsresult PinCurrentAppToTaskbarImpl(bool aCheckOnly,
bool aPrivateBrowsing,
const nsAString& aAppUserModelId) {
const nsAString& aAppUserModelId,
const nsAString& aShortcutName) {
MOZ_DIAGNOSTIC_ASSERT(
!NS_IsMainThread(),
"PinCurrentAppToTaskbarImpl should be called off main thread only");
nsAutoString shortcutPath;
nsresult rv =
FindMatchingShortcut(aAppUserModelId, aPrivateBrowsing, shortcutPath);
FindMatchingShortcut(aAppUserModelId, aShortcutName, shortcutPath);
if (NS_FAILED(rv)) {
shortcutPath.Truncate();
}
@ -1312,32 +1322,15 @@ static nsresult PinCurrentAppToTaskbarImpl(bool aCheckOnly,
return NS_OK;
}
nsAutoString desc;
nsTArray<nsString> arguments;
if (aPrivateBrowsing) {
nsAutoString arg;
// Localization disabled until we finalize the English string.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1758961 tracks following
// up on this before it becomes user visible.
/*nsAutoCString pbStr;
IgnoredErrorResult rv;
nsTArray<nsCString> resIds{
"branding/brand.ftl"_ns,
"browser/browser.ftl"_ns,
};
RefPtr<Localization> l10n = Localization::Create(resIds, true);
l10n->FormatValueSync("private-browsing-shortcut-text"_ns, {}, pbStr, rv);
desc.Assign(NS_ConvertUTF8toUTF16(pbStr));*/
desc.AssignLiteral(MOZ_APP_DISPLAYNAME " Private Browsing");
arg.AssignLiteral("-private-window");
arguments.AppendElement(arg);
} else {
desc.AssignLiteral(MOZ_APP_DISPLAYNAME);
}
nsAutoString linkName(desc);
nsAutoString linkName(aShortcutName);
linkName.AppendLiteral(".lnk");
wchar_t exePath[MAXPATHLEN] = {};
@ -1358,9 +1351,9 @@ static nsresult PinCurrentAppToTaskbarImpl(bool aCheckOnly,
// needs an index.
iconIndex--;
rv = CreateShortcutImpl(exeFile, arguments, desc, exeFile, iconIndex,
aAppUserModelId, FOLDERID_Programs, linkName,
shortcutPath);
rv = CreateShortcutImpl(exeFile, arguments, aShortcutName, exeFile,
iconIndex, aAppUserModelId, FOLDERID_Programs,
linkName, shortcutPath);
if (!NS_SUCCEEDED(rv)) {
return NS_ERROR_FILE_NOT_FOUND;
}
@ -1400,20 +1393,49 @@ static nsresult PinCurrentAppToTaskbarAsyncImpl(bool aCheckOnly,
return NS_ERROR_FAILURE;
}
// NOTE: In the installer, non-private shortcuts are named
// "${BrandShortName}.lnk". This is set from MOZ_APP_DISPLAYNAME in
// defines.nsi.in. (Except in dev edition where it's explicitly set to
// "Firefox Developer Edition" in branding.nsi, which matches
// MOZ_APP_DISPLAYNAME in aurora/configure.sh.)
//
// If this changes, we could expand this to check shortcuts_log.ini,
// which records the name of the shortcuts as created by the installer.
//
// Private shortcuts are not created by the installer (they're created
// upon user request, ultimately by CreateShortcutImpl, and recorded in
// a separate shortcuts log. As with non-private shortcuts they have a known
// name - so there's no need to look through logs to find them.
nsAutoString shortcutName;
if (aPrivateBrowsing) {
nsTArray<nsCString> resIds = {
"branding/brand.ftl"_ns,
"browser/browser.ftl"_ns,
};
RefPtr<Localization> l10n = Localization::Create(resIds, true);
nsAutoCString pbStr;
IgnoredErrorResult rv;
l10n->FormatValueSync("private-browsing-shortcut-text"_ns, {}, pbStr, rv);
shortcutName.Append(NS_ConvertUTF8toUTF16(pbStr));
shortcutName.AppendLiteral(".lnk");
} else {
shortcutName.AppendLiteral(MOZ_APP_DISPLAYNAME ".lnk");
}
auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>(
"CheckPinCurrentAppToTaskbarAsync promise", promise);
NS_DispatchBackgroundTask(
NS_NewRunnableFunction(
"CheckPinCurrentAppToTaskbarAsync",
[aCheckOnly, aPrivateBrowsing, aumid = nsString{aumid},
[aCheckOnly, aPrivateBrowsing, shortcutName, aumid = nsString{aumid},
promiseHolder = std::move(promiseHolder)] {
nsresult rv = NS_ERROR_FAILURE;
HRESULT hr = CoInitialize(nullptr);
if (SUCCEEDED(hr)) {
rv = PinCurrentAppToTaskbarImpl(aCheckOnly, aPrivateBrowsing,
aumid);
aumid, shortcutName);
CoUninitialize();
}
@ -1439,15 +1461,15 @@ NS_IMETHODIMP
nsWindowsShellService::PinCurrentAppToTaskbarAsync(bool aPrivateBrowsing,
JSContext* aCx,
dom::Promise** aPromise) {
return PinCurrentAppToTaskbarAsyncImpl(/* aCheckOnly */ false,
aPrivateBrowsing, aCx, aPromise);
return PinCurrentAppToTaskbarAsyncImpl(
/* aCheckOnly */ false, aPrivateBrowsing, aCx, aPromise);
}
NS_IMETHODIMP
nsWindowsShellService::CheckPinCurrentAppToTaskbarAsync(
bool aPrivateBrowsing, JSContext* aCx, dom::Promise** aPromise) {
return PinCurrentAppToTaskbarAsyncImpl(/* aCheckOnly = */ true,
aPrivateBrowsing, aCx, aPromise);
return PinCurrentAppToTaskbarAsyncImpl(
/* aCheckOnly = */ true, aPrivateBrowsing, aCx, aPromise);
}
static bool IsCurrentAppPinnedToTaskbarSync(const nsAutoString& aumid) {
@ -1652,10 +1674,16 @@ nsWindowsShellService::ClassifyShortcut(const nsAString& aPath,
if (wcsnicmp(shortcutPath.get(), knownPath.get(), knownPath.Length()) ==
0) {
aResult.Assign(folders[i].classification);
// This is explicitly not localized until we finalize the English string.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1758961 tracks following
// up on this before it becomes user visible.
if (wcsstr(shortcutPath.get(), L"Private Browsing")) {
nsTArray<nsCString> resIds = {
"branding/brand.ftl"_ns,
"browser/browser.ftl"_ns,
};
RefPtr<Localization> l10n = Localization::Create(resIds, true);
nsAutoCString pbStr;
IgnoredErrorResult rv;
l10n->FormatValueSync("private-browsing-shortcut-text"_ns, {}, pbStr, rv);
NS_ConvertUTF8toUTF16 widePbStr(pbStr);
if (wcsstr(shortcutPath.get(), widePbStr.get())) {
aResult.AppendLiteral("Private");
}
return NS_OK;

View File

@ -10,7 +10,5 @@ support-files =
[browser_translation_yandex.js]
[browser_translation_telemetry.js]
[browser_translation_infobar.js]
skip-if = debug || (os == 'mac') || (os == 'linux') # Bug 1316953
[browser_translation_exceptions.js]
https_first_disabled = true
skip-if = debug || (os == 'mac') || (os == 'linux') # Bug 1387666

View File

@ -9,26 +9,16 @@ support-files =
[browser_closeTab.js]
skip-if = (verify && !debug && (os == 'linux'))
[browser_fxa.js]
skip-if =
debug || asan # updateUI leaks
[browser_fxa_config.js]
skip-if = win10_2004 && !debug # Bug 1727171 and Bug 1723573
[browser_openPreferences.js]
skip-if =
win10_2004 && !fission # Bug 1723573
[browser_openSearchPanel.js]
skip-if = true # Bug 1113038 - Intermittent "Popup was opened"
[browser_UITour.js]
skip-if = os == "linux" || verify # Intermittent failures, bug 951965
[browser_UITour2.js]
skip-if =
os == 'linux' && bits == 64 && !debug #Bug 1678630
[browser_UITour3.js]
skip-if =
os == "linux" && bits == 64 && !debug #Bug 1624291
[browser_UITour4.js]
[browser_UITour5.js]
skip-if = os == "linux" # Linux: Bug 986760, Bug 989101.
[browser_UITour_availableTargets.js]
[browser_UITour_annotation_size_attributes.js]
[browser_UITour_colorway.js]
@ -39,7 +29,7 @@ skip-if = os == "linux" # Linux: Bug 986760, Bug 989101.
skip-if = os != "mac" # modal dialog disabling only working on OS X.
[browser_UITour_observe.js]
[browser_UITour_panel_close_annotation.js]
skip-if = true # Disabled due to frequent failures, bugs 1026310 and 1032137
skip-if = true # Bug 1026310
[browser_UITour_pocket.js]
skip-if = true # Disabled pending removal of pocket UI Tour
[browser_UITour_resetProfile.js]
@ -47,8 +37,8 @@ skip-if = (verify && !debug && (os == 'linux'))
[browser_UITour_showNewTab.js]
skip-if = (verify && !debug && (os == 'linux'))
[browser_UITour_showProtectionReport.js]
skip-if = os == 'linux' # Bug 1579831
skip-if = os == "linux" && (asan || debug || tsan) # Bug 1697217
[browser_UITour_sync.js]
skip-if = os == "linux" && bits == 64 && os_version == '18.04' # Bug 1548677
skip-if = os == "linux" # Bug 1678417
[browser_UITour_toggleReaderMode.js]
skip-if = (verify && !debug && (os == 'linux'))

View File

@ -32,9 +32,15 @@ const TIMESTAMP_TEMPLATE = "%YYYYMMDDHH%";
const TIMESTAMP_LENGTH = 10;
const TIMESTAMP_REGEXP = /^\d{10}$/;
const MERINO_ENDPOINT_PARAM_QUERY = "q";
const MERINO_ENDPOINT_PARAM_CLIENT_VARIANTS = "client_variants";
const MERINO_ENDPOINT_PARAM_PROVIDERS = "providers";
const MERINO_PARAMS = {
CLIENT_VARIANTS: "client_variants",
PROVIDERS: "providers",
QUERY: "q",
SEQUENCE_NUMBER: "seq",
SESSION_ID: "sid",
};
const MERINO_SESSION_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
const IMPRESSION_COUNTERS_RESET_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
@ -152,21 +158,31 @@ class ProviderQuickSuggest extends UrlbarProvider {
/**
* @returns {string} The timestamp template string used in quick suggest URLs.
*/
get timestampTemplate() {
get TIMESTAMP_TEMPLATE() {
return TIMESTAMP_TEMPLATE;
}
/**
* @returns {number} The length of the timestamp in quick suggest URLs.
*/
get timestampLength() {
get TIMESTAMP_LENGTH() {
return TIMESTAMP_LENGTH;
}
get telemetryScalars() {
/**
* @returns {object} An object mapping from mnemonics to scalar names.
*/
get TELEMETRY_SCALARS() {
return { ...TELEMETRY_SCALARS };
}
/**
* @returns {object} An object mapping from mnemonics to Merino search params.
*/
get MERINO_PARAMS() {
return { ...MERINO_PARAMS };
}
/**
* Whether this provider should be invoked for the given context.
* If this method returns false, the providers manager won't start a query
@ -462,15 +478,20 @@ class ProviderQuickSuggest extends UrlbarProvider {
* it describes the search string and picked result.
*/
onEngagement(isPrivate, state, queryContext, details) {
if (!this._resultFromLastQuery) {
return;
}
let result = this._resultFromLastQuery;
this._resultFromLastQuery = null;
// Reset the Merino session ID when an engagement ends. Per spec, for the
// user's privacy, we don't keep it around between engagements. It wouldn't
// hurt to do this on start too, it's just not necessary if we always do it
// on end.
if (this._merinoSessionID && state != "start") {
this._resetMerinoSessionID();
}
// Per spec, we count impressions only when the user picks a result, i.e.,
// when `state` is "engagement".
if (state == "engagement") {
if (result && state == "engagement") {
this._recordEngagementTelemetry(
result,
isPrivate,
@ -820,6 +841,22 @@ class ProviderQuickSuggest extends UrlbarProvider {
async _fetchMerinoSuggestions(queryContext, searchString) {
let instance = this.queryInstance;
// Set up the Merino session ID and related state.
if (!this._merinoSessionID) {
this._merinoSessionID = Services.uuid.generateUUID();
this._merinoSequenceNumber = 0;
this._merinoSessionTimer?.cancel();
// Per spec, for the user's privacy, the session should time out and a new
// session ID should be used if the engagement does not end soon.
this._merinoSessionTimer = new SkippableTimer({
name: "Merino session timeout",
time: this._merinoSessionTimeoutMs,
logger: this.logger,
callback: () => this._resetMerinoSessionID(),
});
}
// Get the endpoint URL. It's empty by default when running tests so they
// don't hit the network.
let endpointString = UrlbarPrefs.get("merino.endpointURL");
@ -833,26 +870,28 @@ class ProviderQuickSuggest extends UrlbarProvider {
this.logger.error("Could not make Merino endpoint URL: " + error);
return null;
}
url.searchParams.set(MERINO_ENDPOINT_PARAM_QUERY, searchString);
url.searchParams.set(MERINO_PARAMS.QUERY, searchString);
url.searchParams.set(MERINO_PARAMS.SESSION_ID, this._merinoSessionID);
url.searchParams.set(
MERINO_PARAMS.SEQUENCE_NUMBER,
this._merinoSequenceNumber
);
let clientVariants = UrlbarPrefs.get("merino.clientVariants");
if (clientVariants) {
url.searchParams.set(
MERINO_ENDPOINT_PARAM_CLIENT_VARIANTS,
clientVariants
);
url.searchParams.set(MERINO_PARAMS.CLIENT_VARIANTS, clientVariants);
}
let providers = UrlbarPrefs.get("merino.providers");
if (providers) {
url.searchParams.set(MERINO_ENDPOINT_PARAM_PROVIDERS, providers);
url.searchParams.set(MERINO_PARAMS.PROVIDERS, providers);
} else if (
!UrlbarPrefs.get("suggest.quicksuggest.nonsponsored") &&
!UrlbarPrefs.get("suggest.quicksuggest.sponsored")
) {
// Data collection is enabled but suggestions are not. Set the `providers`
// Data collection is enabled but suggestions are not. Set the providers
// param to an empty string to tell Merino not to fetch any suggestions.
url.searchParams.set(MERINO_ENDPOINT_PARAM_PROVIDERS, "");
url.searchParams.set(MERINO_PARAMS.PROVIDERS, "");
}
let responseHistogram = Services.telemetry.getHistogramById(
@ -904,6 +943,15 @@ class ProviderQuickSuggest extends UrlbarProvider {
response = await fetch(url, { signal: controller.signal });
TelemetryStopwatch.finish(TELEMETRY_MERINO_LATENCY, queryContext);
maybeRecordResponse(response.ok ? "success" : "http_error");
// Increment the sequence number only after the fetch successfully
// completes. It should not be incremented if the fetch is aborted or
// fails due to a network error. The server should not see gaps in
// sequence numbers for searches it never received. In particular, as
// the user quickly types a search string and we start a search after
// each new character, some of those searches may cancel previous ones
// before their fetches complete or even start.
this._merinoSequenceNumber++;
} catch (error) {
TelemetryStopwatch.cancel(TELEMETRY_MERINO_LATENCY, queryContext);
if (error.name != "AbortError") {
@ -956,6 +1004,16 @@ class ProviderQuickSuggest extends UrlbarProvider {
}));
}
/**
* Resets the Merino session ID and related state.
*/
_resetMerinoSessionID() {
this._merinoSessionID = null;
this._merinoSequenceNumber = 0;
this._merinoSessionTimer?.cancel();
this._merinoSessionTimer = null;
}
/**
* Returns whether a given suggestion can be added for a query, assuming the
* provider itself should be active.
@ -1560,6 +1618,12 @@ class ProviderQuickSuggest extends UrlbarProvider {
// Whether blocked digests are currently being updated.
_updatingBlockedDigests = false;
// State related to the current Merino session.
_merinoSessionID = null;
_merinoSequenceNumber = 0;
_merinoSessionTimer = null;
_merinoSessionTimeoutMs = MERINO_SESSION_TIMEOUT_MS;
}
var UrlbarProviderQuickSuggest = new ProviderQuickSuggest();

View File

@ -265,7 +265,6 @@ support-files =
searchSuggestionEngine.sjs
[browser_speculative_connect_not_with_client_cert.js]
[browser_stop.js]
skip-if = os == 'mac' # macosx1014 fails due to 1485288
[browser_stop_pending.js]
https_first_disabled = true
support-files =

View File

@ -107,7 +107,7 @@ class QSTestUtils {
}
get SCALARS() {
return UrlbarProviderQuickSuggest.telemetryScalars;
return UrlbarProviderQuickSuggest.TELEMETRY_SCALARS;
}
get TELEMETRY_EVENT_CATEGORY() {
@ -619,20 +619,20 @@ class QSTestUtils {
* }
*/
assertTimestampsReplaced(result, urls) {
let { timestampTemplate, timestampLength } = UrlbarProviderQuickSuggest;
let { TIMESTAMP_TEMPLATE, TIMESTAMP_LENGTH } = UrlbarProviderQuickSuggest;
// Parse the timestamp strings from each payload property and save them in
// `urls[key].timestamp`.
urls = { ...urls };
for (let [key, url] of Object.entries(urls)) {
let index = url.indexOf(timestampTemplate);
let index = url.indexOf(TIMESTAMP_TEMPLATE);
this.Assert.ok(
index >= 0,
`Timestamp template ${timestampTemplate} is in URL ${url} for key ${key}`
`Timestamp template ${TIMESTAMP_TEMPLATE} is in URL ${url} for key ${key}`
);
let value = result.payload[key];
this.Assert.ok(value, "Key is in result payload: " + key);
let timestamp = value.substring(index, index + timestampLength);
let timestamp = value.substring(index, index + TIMESTAMP_LENGTH);
// Set `urls[key]` to an object that's helpful in the logged info message
// below.

View File

@ -14,6 +14,7 @@ support-files =
[browser_quicksuggest_block.js]
[browser_quicksuggest_configuration.js]
[browser_quicksuggest_indexes.js]
[browser_quicksuggest_merinoSessions.js]
[browser_quicksuggest_onboardingDialog.js]
[browser_quicksuggest_telemetry.js]
tags = search-telemetry

View File

@ -0,0 +1,296 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// End-to-end browser smoke test for Merino sessions. More comprehensive tests
// are in test_quicksuggest_merinoSessions.js. This test essentially makes sure
// engagements occur as expected when interacting with the urlbar. If you need
// to add tests that do not depend on a new definition of "engagement", consider
// adding them to test_quicksuggest_merinoSessions.js instead.
"use strict";
const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
const { MERINO_PARAMS } = UrlbarProviderQuickSuggest;
// We set the Merino timeout to a large value to avoid intermittent failures in
// CI, especially TV tests, where the Merino fetch unexpectedly doesn't finish
// before the default timeout.
const TEST_MERINO_TIMEOUT_MS = 30000;
const MERINO_RESPONSE = {
request_id: "request_id",
suggestions: [
{
full_keyword: "full_keyword",
title: "title",
url: "url",
icon: null,
impression_url: "impression_url",
click_url: "click_url",
block_id: 1,
advertiser: "advertiser",
is_sponsored: true,
score: 1,
},
],
};
let gMerinoHandler;
add_task(async function init() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.urlbar.merino.enabled", true],
["browser.urlbar.quicksuggest.remoteSettings.enabled", false],
["browser.urlbar.quicksuggest.dataCollection.enabled", true],
["browser.urlbar.merino.timeoutMs", TEST_MERINO_TIMEOUT_MS],
],
});
await PlacesUtils.history.clear();
await PlacesUtils.bookmarks.eraseEverything();
await UrlbarTestUtils.formHistory.clear();
// Install a mock default engine so we don't hit the network.
await SearchTestUtils.installSearchExtension();
let originalEngine = await Services.search.getDefault();
let engine = Services.search.getEngineByName("Example");
await Services.search.setDefault(engine);
// Set up the mock Merino server.
let path = "/merino";
let server = makeMerinoServer(path);
server = makeMerinoServer(path);
server.start(-1);
// Set up the mock endpoint URL.
let url = new URL("http://localhost/");
url.pathname = path;
url.port = server.identity.primaryPort;
await SpecialPowers.pushPrefEnv({
set: [["browser.urlbar.merino.endpointURL", url.toString()]],
});
registerCleanupFunction(async () => {
server.stop();
gMerinoHandler = null;
await Services.search.setDefault(originalEngine);
});
});
// In a single engagement, all requests should use the same session ID and the
// sequence number should be incremented.
add_task(async function singleEngagement() {
let requests = [];
setMerinoResponse(req => {
requests.push({ params: new URLSearchParams(req.queryString) });
return MERINO_RESPONSE;
});
for (let i = 0; i < 3; i++) {
let searchString = "search" + i;
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
value: searchString,
fireInputEvent: true,
});
checkRequests(requests, {
count: i + 1,
areSessionIDsUnique: false,
params: {
[MERINO_PARAMS.QUERY]: searchString,
[MERINO_PARAMS.SEQUENCE_NUMBER]: i,
},
});
}
await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
});
// In a single engagement, all requests should use the same session ID and the
// sequence number should be incremented. This task closes the panel between
// searches but keeps the input focused, so the engagement should not end.
add_task(async function singleEngagement_panelClosed() {
let requests = [];
setMerinoResponse(req => {
requests.push({ params: new URLSearchParams(req.queryString) });
return MERINO_RESPONSE;
});
for (let i = 0; i < 3; i++) {
let searchString = "search" + i;
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
value: searchString,
fireInputEvent: true,
});
checkRequests(requests, {
count: i + 1,
areSessionIDsUnique: false,
params: {
[MERINO_PARAMS.QUERY]: searchString,
[MERINO_PARAMS.SEQUENCE_NUMBER]: i,
},
});
EventUtils.synthesizeKey("KEY_Escape");
Assert.ok(!UrlbarTestUtils.isPopupOpen(window), "Panel is closed");
Assert.ok(gURLBar.focused, "Input remains focused");
}
// End the engagement to reset the session for the next test.
gURLBar.blur();
});
// New engagements should not use the same session ID as previous engagements
// and the sequence number should be reset. This task completes each engagement
// successfully.
add_task(async function manyEngagements_engagement() {
let requests = [];
setMerinoResponse(req => {
requests.push({ params: new URLSearchParams(req.queryString) });
return MERINO_RESPONSE;
});
for (let i = 0; i < 3; i++) {
// Open a new tab since we'll load the mock default search engine page.
await BrowserTestUtils.withNewTab("about:blank", async () => {
let searchString = "search" + i;
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
value: searchString,
fireInputEvent: true,
});
checkRequests(requests, {
count: i + 1,
areSessionIDsUnique: true,
params: {
[MERINO_PARAMS.QUERY]: searchString,
[MERINO_PARAMS.SEQUENCE_NUMBER]: 0,
},
});
// Press enter on the heuristic result to load the search engine page and
// complete the engagement.
let loadPromise = BrowserTestUtils.browserLoaded(
gBrowser.selectedBrowser
);
EventUtils.synthesizeKey("KEY_Enter");
await loadPromise;
});
}
});
// New engagements should not use the same session ID as previous engagements
// and the sequence number should be reset. This task abandons each engagement.
add_task(async function manyEngagements_abandonment() {
let requests = [];
setMerinoResponse(req => {
requests.push({ params: new URLSearchParams(req.queryString) });
return MERINO_RESPONSE;
});
for (let i = 0; i < 3; i++) {
let searchString = "search" + i;
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
value: searchString,
fireInputEvent: true,
});
checkRequests(requests, {
count: i + 1,
areSessionIDsUnique: true,
params: {
[MERINO_PARAMS.QUERY]: searchString,
[MERINO_PARAMS.SEQUENCE_NUMBER]: 0,
},
});
// Blur the urlbar to abandon the engagement.
await UrlbarTestUtils.promisePopupClose(window, () => gURLBar.blur());
}
});
/**
* Asserts a given number of requests have been received and checks the last
* request's search params.
*
* @param {array} requests
* An array of objects that look like `{ params }`.
* @param {number} count
* The expected number of requests.
* @param {object} params
* The expected search params of the last request. An object that maps param
* names to expected values.
* @param {boolean} areSessionIDsUnique
* Whether each session ID param is expected to be unique.
*/
function checkRequests(requests, { count, params, areSessionIDsUnique }) {
// Check the request count.
Assert.equal(
requests.length,
count,
"Expected request count for the current search"
);
// Check search params.
let request = requests[count - 1];
Assert.equal(
request.params.get(MERINO_PARAMS.QUERY),
params[MERINO_PARAMS.QUERY],
"Query is correct"
);
let sessionID = request.params.get(MERINO_PARAMS.SESSION_ID);
Assert.ok(sessionID, "Session ID was specified");
Assert.ok(
/^\{[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}\}$/i.test(sessionID),
"Session ID is a UUID"
);
let sequenceNumber = request.params.get(MERINO_PARAMS.SEQUENCE_NUMBER);
Assert.ok(sequenceNumber, "Sequence number was specified");
Assert.equal(
parseInt(sequenceNumber),
params[MERINO_PARAMS.SEQUENCE_NUMBER],
"Sequence number is correct"
);
// Check the uniqueness of the session ID.
for (let i = 0; i < count - 1; i++) {
if (areSessionIDsUnique) {
Assert.notEqual(
request.params.get(MERINO_PARAMS.SESSION_ID),
requests[i].params.get(MERINO_PARAMS.SESSION_ID),
`Session ID is unique (comparing to index ${i})`
);
} else {
Assert.equal(
request.params.get(MERINO_PARAMS.SESSION_ID),
requests[i].params.get(MERINO_PARAMS.SESSION_ID),
`Session ID is same (comparing to index ${i})`
);
}
}
}
function setMerinoResponse(callback) {
gMerinoHandler = callback;
}
function makeMerinoServer(endpointPath) {
let server = new HttpServer();
server.registerPathHandler(endpointPath, async (req, resp) => {
resp.processAsync();
let merinoResponse = await gMerinoHandler(req);
resp.setHeader("Content-Type", "application/json", false);
resp.write(JSON.stringify(merinoResponse));
resp.finish();
});
return server;
}

View File

@ -17,10 +17,10 @@ const HTTP_SEARCH_STRING = "http prefix";
const HTTPS_SEARCH_STRING = "https prefix";
const PREFIX_SUGGESTIONS_STRIPPED_URL = "example.com/prefix-test";
const { timestampTemplate, timestampLength } = UrlbarProviderQuickSuggest;
const { TIMESTAMP_TEMPLATE, TIMESTAMP_LENGTH } = UrlbarProviderQuickSuggest;
const TIMESTAMP_SEARCH_STRING = "timestamp";
const TIMESTAMP_SUGGESTION_URL = `http://example.com/timestamp-${timestampTemplate}`;
const TIMESTAMP_SUGGESTION_CLICK_URL = `http://click.reporting.test.com/timestamp-${timestampTemplate}-foo`;
const TIMESTAMP_SUGGESTION_URL = `http://example.com/timestamp-${TIMESTAMP_TEMPLATE}`;
const TIMESTAMP_SUGGESTION_CLICK_URL = `http://click.reporting.test.com/timestamp-${TIMESTAMP_TEMPLATE}-foo`;
const REMOTE_SETTINGS_DATA = [
{
@ -852,7 +852,7 @@ add_task(async function dedupeAgainstURL_timestamps() {
// Add a visit that will match the query below and dupe the quick suggest.
let dupeURL = TIMESTAMP_SUGGESTION_URL.replace(
timestampTemplate,
TIMESTAMP_TEMPLATE,
"2013051113"
);
@ -860,14 +860,14 @@ add_task(async function dedupeAgainstURL_timestamps() {
// suggest but not quite because they have invalid timestamps.
let badTimestamps = [
// not numeric digits
"x".repeat(timestampLength),
"x".repeat(TIMESTAMP_LENGTH),
// too few digits
"5".repeat(timestampLength - 1),
"5".repeat(TIMESTAMP_LENGTH - 1),
// empty string, too few digits
"",
];
let badTimestampURLs = badTimestamps.map(str =>
TIMESTAMP_SUGGESTION_URL.replace(timestampTemplate, str)
TIMESTAMP_SUGGESTION_URL.replace(TIMESTAMP_TEMPLATE, str)
);
await PlacesTestUtils.addVisits(

View File

@ -1017,10 +1017,10 @@ add_task(async function timestamps() {
// Set up the Merino response with template URLs.
let resp = setMerinoResponse();
let suggestion = resp.body.suggestions[0];
let { timestampTemplate } = UrlbarProviderQuickSuggest;
let { TIMESTAMP_TEMPLATE } = UrlbarProviderQuickSuggest;
suggestion.url = `http://example.com/time-${timestampTemplate}`;
suggestion.click_url = `http://example.com/time-${timestampTemplate}-foo`;
suggestion.url = `http://example.com/time-${TIMESTAMP_TEMPLATE}`;
suggestion.click_url = `http://example.com/time-${TIMESTAMP_TEMPLATE}-foo`;
// Do a search.
let context = createContext("test", {

View File

@ -0,0 +1,602 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Test for Merino sessions.
"use strict";
const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
// We set the Merino timeout to a large value to avoid intermittent failures in
// CI, especially TV tests, where the Merino fetch unexpectedly doesn't finish
// before the default timeout.
const TEST_MERINO_TIMEOUT_MS = 1000;
const { MERINO_PARAMS } = UrlbarProviderQuickSuggest;
const MERINO_RESPONSE = {
contentType: "application/json",
body: {
request_id: "request_id",
suggestions: [
{
full_keyword: "full_keyword",
title: "title",
url: "url",
icon: null,
impression_url: "impression_url",
click_url: "click_url",
block_id: 1,
advertiser: "advertiser",
is_sponsored: true,
score: 1,
},
],
},
};
let gMerinoResponse;
add_task(async function init() {
UrlbarPrefs.set("quicksuggest.enabled", true);
UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
UrlbarPrefs.set("merino.enabled", true);
UrlbarPrefs.set("quicksuggest.remoteSettings.enabled", false);
UrlbarPrefs.set("quicksuggest.dataCollection.enabled", true);
// Set up the Merino server.
let path = "/merino";
let server = makeMerinoServer(path);
let url = new URL("http://localhost/");
url.pathname = path;
url.port = server.identity.primaryPort;
UrlbarPrefs.set("merino.endpointURL", url.toString());
UrlbarPrefs.set("merino.timeoutMs", TEST_MERINO_TIMEOUT_MS);
});
// In a single engagement, all requests should use the same session ID and the
// sequence number should be incremented.
add_task(async function singleEngagement() {
let requests = [];
setMerinoResponse(req => {
requests.push({ params: new URLSearchParams(req.queryString) });
return MERINO_RESPONSE;
});
let controller = UrlbarTestUtils.newMockController();
for (let i = 0; i < 3; i++) {
let searchString = "search" + i;
await controller.startQuery(
createContext(searchString, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
})
);
checkRequests(requests, {
count: i + 1,
areSessionIDsUnique: false,
params: {
[MERINO_PARAMS.QUERY]: searchString,
[MERINO_PARAMS.SEQUENCE_NUMBER]: i,
},
});
}
// End the engagement to reset the session for the next test.
endEngagement();
});
// New engagements should not use the same session ID as previous engagements
// and the sequence number should be reset. This task completes each engagement
// successfully.
add_task(async function manyEngagements_engagement() {
await doManyEngagementsTest("engagement");
});
// New engagements should not use the same session ID as previous engagements
// and the sequence number should be reset. This task abandons each engagement.
add_task(async function manyEngagements_abandonment() {
await doManyEngagementsTest("abandonment");
});
async function doManyEngagementsTest(state) {
let requests = [];
setMerinoResponse(req => {
requests.push({ params: new URLSearchParams(req.queryString) });
return MERINO_RESPONSE;
});
let controller = UrlbarTestUtils.newMockController();
for (let i = 0; i < 3; i++) {
let searchString = "search" + i;
let context = createContext(searchString, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
});
await controller.startQuery(context);
checkRequests(requests, {
count: i + 1,
areSessionIDsUnique: true,
params: {
[MERINO_PARAMS.QUERY]: searchString,
[MERINO_PARAMS.SEQUENCE_NUMBER]: 0,
},
});
endEngagement(context, state);
}
}
// When a search is canceled before the Merino response is received, the
// sequence number should not be incremented.
add_task(async function canceledQueries() {
let requests = [];
setMerinoResponse(req => {
requests.push({ params: new URLSearchParams(req.queryString) });
return MERINO_RESPONSE;
});
let controller = UrlbarTestUtils.newMockController();
for (let i = 0; i < 3; i++) {
// Start a search but don't wait for it to finish.
controller.startQuery(
createContext("search" + i, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
})
);
// Do another search that cancels the first.
let searchString = "search" + i + "again";
let context = createContext(searchString, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
});
await controller.startQuery(context);
// Only one request should have been received and the sequence number should
// be incremented only once compared to the previous successful search.
checkRequests(requests, {
count: i + 1,
areSessionIDsUnique: false,
params: {
[MERINO_PARAMS.QUERY]: searchString,
[MERINO_PARAMS.SEQUENCE_NUMBER]: i,
},
});
}
// End the engagement to reset the session for the next test.
endEngagement();
});
// When a network error occurs, the sequence number should not be incremented.
add_task(async function networkError() {
let requests = [];
setMerinoResponse(req => {
requests.push({ params: new URLSearchParams(req.queryString) });
return MERINO_RESPONSE;
});
let controller = UrlbarTestUtils.newMockController();
for (let i = 0; i < 3; i++) {
// Do a search that fails with a network error.
await withNetworkError(async () => {
await controller.startQuery(
createContext("search" + i, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
})
);
});
// Do another search that successfully finishes.
let searchString = "search" + i + "again";
await controller.startQuery(
createContext(searchString, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
})
);
// Only one request should have been received and the sequence number should
// be incremented only once compared to the previous successful search.
checkRequests(requests, {
count: i + 1,
areSessionIDsUnique: false,
params: {
[MERINO_PARAMS.QUERY]: searchString,
[MERINO_PARAMS.SEQUENCE_NUMBER]: i,
},
});
}
// End the engagement to reset the session for the next test.
endEngagement();
});
// When the server returns a response with an HTTP error, the sequence number
// should be incremented.
add_task(async function httpError() {
let status;
let requests = [];
setMerinoResponse(req => {
requests.push({ status, params: new URLSearchParams(req.queryString) });
let resp = deepCopy(MERINO_RESPONSE);
resp.status = status;
return resp;
});
let controller = UrlbarTestUtils.newMockController();
for (let i = 0; i < 6; i++) {
status = i % 2 ? 200 : 500;
let searchString = "search" + i;
await controller.startQuery(
createContext(searchString, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
})
);
Assert.equal(
requests[i].status,
status,
"The request was answered with the expected status"
);
checkRequests(requests, {
count: i + 1,
areSessionIDsUnique: false,
params: {
[MERINO_PARAMS.QUERY]: searchString,
[MERINO_PARAMS.SEQUENCE_NUMBER]: i,
},
});
}
// End the engagement to reset the session for the next test.
endEngagement();
});
// When the server "times out" but returns a response and no other search
// happens in the meantime, the sequence number should be incremented.
add_task(async function merinoTimeout_wait() {
let delay = 0;
let requests = [];
setMerinoResponse(req => {
requests.push({ delay, params: new URLSearchParams(req.queryString) });
let resp = deepCopy(MERINO_RESPONSE);
resp.delay = delay;
return resp;
});
let controller = UrlbarTestUtils.newMockController();
for (let i = 0; i < 2; i++) {
// Don't delay the response to the first search. Delay the response to the
// second search enough that it times out.
delay = i == 1 ? 2 * UrlbarPrefs.get("merinoTimeoutMs") : 0;
let searchString = "search" + i;
await controller.startQuery(
createContext(searchString, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
})
);
Assert.equal(
requests[i].delay,
delay,
"The request was answered with the expected delay"
);
checkRequests(requests, {
count: i + 1,
areSessionIDsUnique: false,
params: {
[MERINO_PARAMS.QUERY]: searchString,
[MERINO_PARAMS.SEQUENCE_NUMBER]: i,
},
});
}
// End the engagement to reset the session for the next test.
endEngagement();
});
// When the server "times out" and a second search starts before the first
// search receives a response, the first search should be canceled and the
// sequence number should not be incremented.
add_task(async function merinoTimeout_canceled() {
let delay = 0;
let requests = [];
setMerinoResponse(req => {
requests.push({ delay, params: new URLSearchParams(req.queryString) });
let resp = deepCopy(MERINO_RESPONSE);
resp.delay = delay;
return resp;
});
let controller = UrlbarTestUtils.newMockController();
for (let i = 0; i < 3; i++) {
// Don't delay the response to the first and third searches. Delay the
// response to the second search enough that it times out.
delay = i == 1 ? 3 * UrlbarPrefs.get("merinoTimeoutMs") : 0;
let searchString = "search" + i;
let queryPromise = controller.startQuery(
createContext(searchString, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
})
);
if (i == 1) {
// For the second search, wait long enough for it to time out but not so
// long that it completes, and then continue to the third search, which
// should cancel it.
await new Promise(resolve =>
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
setTimeout(resolve, 2 * UrlbarPrefs.get("merinoTimeoutMs"))
);
continue;
}
await queryPromise;
Assert.equal(
requests[i].delay,
delay,
"The request was answered with the expected delay"
);
checkRequests(requests, {
count: i + 1,
areSessionIDsUnique: false,
params: {
[MERINO_PARAMS.QUERY]: searchString,
[MERINO_PARAMS.SEQUENCE_NUMBER]: i == 0 ? 0 : 1,
},
});
}
// End the engagement to reset the session for the next test.
endEngagement();
});
// When the session times out due to no engagement, the next search should use a
// new session ID and the sequence number should be reset.
add_task(async function sessionTimeout() {
// Set the session timeout to something reasonable to test.
let timeoutMs = 500;
let originalTimeoutMs = UrlbarProviderQuickSuggest._merinoSessionTimeoutMs;
UrlbarProviderQuickSuggest._merinoSessionTimeoutMs = timeoutMs;
let requests = [];
setMerinoResponse(req => {
requests.push({ params: new URLSearchParams(req.queryString) });
return MERINO_RESPONSE;
});
let controller = UrlbarTestUtils.newMockController();
// Do a search.
let context = createContext("search 0", {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
});
await controller.startQuery(context);
checkRequests(requests, {
count: 1,
areSessionIDsUnique: true,
params: {
[MERINO_PARAMS.QUERY]: "search 0",
[MERINO_PARAMS.SEQUENCE_NUMBER]: 0,
},
});
// Wait for the session to time out.
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(resolve => setTimeout(resolve, 2 * timeoutMs));
Assert.strictEqual(
UrlbarProviderQuickSuggest._merinoSessionID,
null,
"_merinoSessionID is null after session timeout"
);
Assert.strictEqual(
UrlbarProviderQuickSuggest._merinoSessionTimer,
null,
"_merinoSessionTimer is null after session timeout"
);
// Do another search.
await controller.startQuery(
createContext("search 1", {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
})
);
checkRequests(requests, {
count: 2,
areSessionIDsUnique: true,
params: {
[MERINO_PARAMS.QUERY]: "search 1",
[MERINO_PARAMS.SEQUENCE_NUMBER]: 0,
},
});
UrlbarProviderQuickSuggest._merinoSessionTimeoutMs = originalTimeoutMs;
// End the engagement to reset the session for the next test.
endEngagement();
});
/**
* Asserts a given number of requests have been received and checks the last
* request's search params.
*
* @param {array} requests
* An array of objects that look like `{ params }`.
* @param {number} count
* The expected number of requests.
* @param {object} params
* The expected search params of the last request. An object that maps param
* names to expected values.
* @param {boolean} areSessionIDsUnique
* Whether each session ID param is expected to be unique.
*/
function checkRequests(requests, { count, params, areSessionIDsUnique }) {
// Check the request count.
Assert.equal(
requests.length,
count,
"Expected request count for the current search"
);
// Check last request's search params.
let request = requests[count - 1];
Assert.equal(
request.params.get(MERINO_PARAMS.QUERY),
params[MERINO_PARAMS.QUERY],
"Query is correct"
);
let sessionID = request.params.get(MERINO_PARAMS.SESSION_ID);
Assert.ok(sessionID, "Session ID was specified");
Assert.ok(
/^\{[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}\}$/i.test(sessionID),
"Session ID is a UUID"
);
let sequenceNumber = request.params.get(MERINO_PARAMS.SEQUENCE_NUMBER);
Assert.ok(sequenceNumber, "Sequence number was specified");
Assert.equal(
parseInt(sequenceNumber),
params[MERINO_PARAMS.SEQUENCE_NUMBER],
"Sequence number is correct"
);
// Check the uniqueness of the last request's session ID.
for (let i = 0; i < count - 1; i++) {
if (areSessionIDsUnique) {
Assert.notEqual(
request.params.get(MERINO_PARAMS.SESSION_ID),
requests[i].params.get(MERINO_PARAMS.SESSION_ID),
`Session ID is unique (comparing to index ${i})`
);
} else {
Assert.equal(
request.params.get(MERINO_PARAMS.SESSION_ID),
requests[i].params.get(MERINO_PARAMS.SESSION_ID),
`Session ID is same (comparing to index ${i})`
);
}
}
}
function endEngagement(context = null, state = "engagement") {
UrlbarProviderQuickSuggest.onEngagement(
false,
state,
context ||
createContext("endEngagement", {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
{ selIndex: -1 }
);
Assert.strictEqual(
UrlbarProviderQuickSuggest._merinoSessionID,
null,
"_merinoSessionID is null after engagement"
);
Assert.strictEqual(
UrlbarProviderQuickSuggest._merinoSessionTimer,
null,
"_merinoSessionTimer is null after engagement"
);
}
async function withNetworkError(callback) {
// Set the endpoint to a valid, unreachable URL.
let originalURL = UrlbarPrefs.get("merino.endpointURL");
UrlbarPrefs.set(
"merino.endpointURL",
"http://localhost/test_quicksuggest_merinoSessions"
);
// Set the timeout high enough that the network error exception will happen
// first. On Mac and Linux the fetch naturally times out fairly quickly but on
// Windows it seems to take 5s, so set our artificial timeout to 10s.
UrlbarPrefs.set("merino.timeoutMs", 10000);
await callback();
UrlbarPrefs.set("merino.endpointURL", originalURL);
UrlbarPrefs.set("merino.timeoutMs", TEST_MERINO_TIMEOUT_MS);
}
function setMerinoResponse(resp = MERINO_RESPONSE) {
if (typeof resp == "function") {
info("Setting Merino response to a callback function");
gMerinoResponse = resp;
} else {
info("Setting Merino response: " + JSON.stringify(resp));
gMerinoResponse = deepCopy(resp);
}
return gMerinoResponse;
}
function makeMerinoServer(endpointPath) {
let server = makeTestServer();
server.registerPathHandler(endpointPath, async (req, resp) => {
resp.processAsync();
let merinoResponse;
if (typeof gMerinoResponse == "function") {
merinoResponse = await gMerinoResponse(req);
} else {
merinoResponse = gMerinoResponse;
}
if (typeof merinoResponse.delay == "number") {
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(resolve => setTimeout(resolve, merinoResponse.delay));
}
if (typeof merinoResponse.status == "number") {
resp.setStatusLine("", merinoResponse.status, merinoResponse.status);
}
resp.setHeader("Content-Type", merinoResponse.contentType, false);
if (typeof merinoResponse.body == "string") {
resp.write(merinoResponse.body);
} else if (merinoResponse.body) {
resp.write(JSON.stringify(merinoResponse.body));
}
resp.finish();
});
return server;
}
function deepCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}

View File

@ -8,6 +8,7 @@ firefox-appdir = browser
[test_quicksuggest_bestMatch.js]
[test_quicksuggest_impressionCaps.js]
[test_quicksuggest_merino.js]
[test_quicksuggest_merinoSessions.js]
[test_quicksuggest_migrate_v1.js]
[test_quicksuggest_migrate_v2.js]
[test_quicksuggest_nonUniqueKeywords.js]

View File

@ -1461,11 +1461,7 @@ Function leaveShortcuts
${EndIf}
${MUI_INSTALLOPTIONS_READ} $AddDesktopSC "shortcuts.ini" "Field 2" "State"
${MUI_INSTALLOPTIONS_READ} $AddStartMenuSC "shortcuts.ini" "Field 3" "State"
; Don't install the quick launch shortcut on Windows 7
${Unless} ${AtLeastWin7}
${MUI_INSTALLOPTIONS_READ} $AddQuickLaunchSC "shortcuts.ini" "Field 4" "State"
${EndUnless}
${MUI_INSTALLOPTIONS_READ} $AddTaskbarSC "shortcuts.ini" "Field 4" "State"
${If} $InstallType == ${INSTALLTYPE_CUSTOM}
Call CheckExistingInstall
@ -1913,13 +1909,7 @@ Function .onInit
WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Top "67"
WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Bottom "87"
; Setup the shortcuts.ini file for the Custom Shortcuts Page
; Don't offer to install the quick launch shortcut on Windows 7
${If} ${AtLeastWin7}
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Settings" NumFields "3"
${Else}
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Settings" NumFields "4"
${EndIf}
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Settings" NumFields "4"
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Type "label"
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Text "$(CREATE_ICONS_DESC)"
@ -1945,16 +1935,13 @@ Function .onInit
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Bottom "50"
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" State "1"
; Don't offer to install the quick launch shortcut on Windows 7
${Unless} ${AtLeastWin7}
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Type "checkbox"
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Text "$(ICONS_QUICKLAUNCH)"
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Left "0"
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Right "-1"
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Top "60"
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Bottom "70"
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" State "1"
${EndUnless}
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Type "checkbox"
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Text "$(ICONS_TASKBAR)"
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Left "0"
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Right "-1"
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Top "60"
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Bottom "70"
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" State "1"
; Setup the components.ini file for the Components Page
WriteINIStr "$PLUGINSDIR\components.ini" "Settings" NumFields "2"

View File

@ -40,7 +40,16 @@
${EndIf}
; Adds a pinned Task Bar shortcut (see MigrateTaskBarShortcut for details).
${MigrateTaskBarShortcut}
; When we enabled this feature for Windows 10 & 11 we decided _not_ to pin
; during an update (even once) because we already offered to do when the
; the user originally installed, and we don't want to go against their
; explicit wishes.
; For Windows 7 and 8, we've been doing this ~forever, and those users may
; not have experienced the onboarding offer to pin to taskbar, so we're
; leaving it enabled there.
${If} ${AtMostWin2012R2}
${MigrateTaskBarShortcut}
${EndIf}
; Update the name/icon/AppModelID of our shortcuts as needed, then update the
; lastwritetime of the Start Menu shortcut to clear the tile icon cache.
@ -1292,28 +1301,6 @@ ${RemoveDefaultBrowserAgentShortcut}
${PinToTaskBar}
${EndIf}
${EndIf}
${ElseIf} ${AtLeastWin10}
${GetInstallerRegistryPref} "Software\Mozilla\${AppName}" \
"installer.taskbarpin.win10.enabled" $2
${If} $2 == "true"
; On Windows 10, we may have previously tried to make a taskbar pin
; and failed because the API we tried to use was blocked by the OS.
; We have an option that works in more cases now, so we're going to try
; again, but also record that we've done so by writing a particular
; registry value, so that we don't continue to do this repeatedly.
ClearErrors
ReadRegDWORD $2 HKCU \
"Software\Mozilla\${AppName}\Installer\$AppUserModelID" \
"WasPinnedToTaskbar"
${If} ${Errors}
WriteRegDWORD HKCU \
"Software\Mozilla\${AppName}\Installer\$AppUserModelID" \
"WasPinnedToTaskbar" 1
${If} $AddTaskbarSC != "0"
${PinToTaskBar}
${EndIf}
${EndIf}
${EndIf}
${EndIf}
${EndIf}
!macroend
@ -1380,17 +1367,22 @@ ${RemoveDefaultBrowserAgentShortcut}
; Pin the shortcut to the TaskBar. 5386 is the shell32.dll
; resource id for the "Pin to Taskbar" string.
InvokeShellVerb::DoIt "$SMPROGRAMS" "$1" "5386"
${Else}
${ElseIf} ${AtMostWaaS} 1809
; In Windows 10 the "Pin to Taskbar" resource was removed, so we
; can't access the verb that way anymore. We have a create a
; command key using the GUID that's assigned to this action and
; then invoke that as a verb.
; then invoke that as a verb. This works up until build 1809
ReadRegStr $R9 HKLM \
"Software\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\Windows.taskbarpin" \
"ExplorerCommandHandler"
WriteRegStr HKCU "Software\Classes\*\shell\${AppRegName}-$AppUserModelID" "ExplorerCommandHandler" $R9
InvokeShellVerb::DoIt "$SMPROGRAMS" "$1" "${AppRegName}-$AppUserModelID"
DeleteRegKey HKCU "Software\Classes\*\shell\${AppRegName}-$AppUserModelID"
${Else}
; In the Windows 10 1903 and up (and Windows 11) the above no
; longer works. We have yet another method for these versions
; which is detailed in the PinToTaskbar plugin code.
PinToTaskbar::Pin "$SMPROGRAMS\$1"
${EndIf}
; Delete the shortcut if it was created

View File

@ -270,6 +270,7 @@ newtab-custom-sponsored-sites = Sponsored shortcuts
newtab-custom-pocket-title = Recommended by { -pocket-brand-name }
newtab-custom-pocket-subtitle = Exceptional content curated by { -pocket-brand-name }, part of the { -brand-product-name } family
newtab-custom-pocket-sponsored = Sponsored stories
newtab-custom-pocket-show-recent-saves = Show recent saves
newtab-custom-recent-title = Recent activity
newtab-custom-recent-subtitle = A selection of recent sites and content
newtab-custom-close-button = Close

View File

@ -41,9 +41,9 @@ SUMMARY_UPGRADE_CLICK=Click Upgrade to continue.
SURVEY_TEXT=&Tell us what you thought of $BrandShortName
LAUNCH_TEXT=&Launch $BrandShortName now
CREATE_ICONS_DESC=Create icons for $BrandShortName:
ICONS_DESKTOP=On my &Desktop
ICONS_STARTMENU=In my &Start Menu Programs folder
ICONS_QUICKLAUNCH=In my &Quick Launch bar
ICONS_DESKTOP=On my &desktop
ICONS_STARTMENU=In my &Start menu Programs folder
ICONS_TASKBAR=On my &taskbar
WARN_MANUALLY_CLOSE_APP_INSTALL=$BrandShortName must be closed to proceed with the installation.\n\nPlease close $BrandShortName to continue.
WARN_MANUALLY_CLOSE_APP_UNINSTALL=$BrandShortName must be closed to proceed with the uninstall.\n\nPlease close $BrandShortName to continue.
WARN_MANUALLY_CLOSE_APP_REFRESH=$BrandShortName must be closed to proceed with the refresh.\n\nPlease close $BrandShortName to continue.

View File

@ -1,6 +1,17 @@
#!/usr/bin/env python3
import glob
import json
import sys
# Import buildconfig if available, otherwise set has_buildconfig to False so
# we skip the check which relies on it.
try:
import buildconfig
except ImportError:
has_buildconfig = False
else:
has_buildconfig = True
def generate(output, *input_paths):
@ -11,6 +22,7 @@ def generate(output, *input_paths):
"""
tpp_list = []
lines = set()
path_found = True
for path in input_paths:
with open(path) as f:
@ -20,7 +32,25 @@ def generate(output, *input_paths):
line = line.strip()
if line.endswith("/"):
line = line[:-1]
tpp_list.append(line)
if has_buildconfig:
# Ignore lines starting with $UNVALIDATED
# These should only be coming from Unvalidated.txt
if line.startswith("$UNVALIDATED"):
line = line[13:]
elif not glob.glob(buildconfig.topsrcdir + "/" + line):
path_found = False
if path_found:
tpp_list.append(line)
else:
print(
"Third-party path "
+ line
+ " does not exist. Remove it from Generated.txt or "
+ "ThirdPartyPaths.txt and try again."
)
sys.exit(1)
tpp_strings = ",\n ".join([json.dumps(tpp) for tpp in sorted(tpp_list)])
output.write(

View File

@ -95,8 +95,14 @@ def add_moz_module(cmake_path):
def write_third_party_paths(mozilla_path, module_path):
tpp_txt = os.path.join(mozilla_path, "../../tools/rewriting/ThirdPartyPaths.txt")
generated_txt = os.path.join(mozilla_path, "../../tools/rewriting/Generated.txt")
# Unvalidated.txt is used for rare cases where we don't want to validate that a given
# path exists but still want it included in the ThirdPartyPaths check in the plugin.
# For example, headers exported to dist/include that live elsewhere.
unvalidated_txt = os.path.join(
mozilla_path, "../../tools/rewriting/Unvalidated.txt"
)
with open(os.path.join(module_path, "ThirdPartyPaths.cpp"), "w") as f:
ThirdPartyPaths.generate(f, tpp_txt, generated_txt)
ThirdPartyPaths.generate(f, tpp_txt, generated_txt, unvalidated_txt)
def generate_thread_allows(mozilla_path, module_path):

View File

@ -70,8 +70,9 @@ GeneratedFile(
script="ThirdPartyPaths.py",
entry_point="generate",
inputs=[
"/tools/rewriting/ThirdPartyPaths.txt",
"/tools/rewriting/Generated.txt",
"/tools/rewriting/ThirdPartyPaths.txt",
"/tools/rewriting/Unvalidated.txt",
],
)

View File

@ -2445,7 +2445,7 @@
"byName": {},
"byBlocks": {},
"usedIds": {
"0": 0
"1": 1
}
}
}
@ -2466,7 +2466,7 @@
"byName": {},
"byBlocks": {},
"usedIds": {
"0": 0
"1": 1
}
}
}

View File

@ -14695,7 +14695,6 @@ function extractSymbols(sourceId) {
literals: [],
hasJsx: false,
hasTypes: false,
loading: false,
framework: undefined
};
const state = {

View File

@ -241,14 +241,6 @@ class DebuggerPanel {
this._actions.selectThread(cx, threadActorID);
}
previewPausedLocation(location) {
return this._actions.previewPausedLocation(location);
}
clearPreviewPausedLocation() {
return this._actions.clearPreviewPausedLocation();
}
async selectSource(sourceId, line, column) {
const cx = this._selectors.getContext(this._getState());
const location = { sourceId, line, column };

View File

@ -53,7 +53,7 @@ export function highlightCalls(cx) {
const symbols = getSymbols(getState(), source);
if (!symbols || symbols.loading) {
if (!symbols) {
return;
}

View File

@ -30,7 +30,3 @@ export { toggleMapScopes } from "./mapScopes";
export { setExpandedScope } from "./expandScopes";
export { generateInlinePreview } from "./inlinePreview";
export { highlightCalls, unhighlightCalls } from "./highlightCalls";
export {
previewPausedLocation,
clearPreviewPausedLocation,
} from "./previewPausedLocation";

View File

@ -19,7 +19,6 @@ CompiledModules(
"mapScopes.js",
"paused.js",
"pauseOnExceptions.js",
"previewPausedLocation.js",
"resumed.js",
"selectFrame.js",
"highlightCalls.js",

View File

@ -1,34 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
import { selectLocation } from "../sources";
import { getContext, getSourceByURL } from "../../selectors";
export function previewPausedLocation(location) {
return ({ dispatch, getState }) => {
const cx = getContext(getState());
const source = getSourceByURL(getState(), location.sourceUrl);
if (!source) {
return;
}
const sourceLocation = {
line: location.line,
column: location.column,
sourceId: source.id,
};
dispatch(selectLocation(cx, sourceLocation));
dispatch({
type: "PREVIEW_PAUSED_LOCATION",
location: sourceLocation,
});
};
}
export function clearPreviewPausedLocation() {
return {
type: "CLEAR_PREVIEW_PAUSED_LOCATION",
};
}

View File

@ -31,7 +31,7 @@ function findExpressionMatch(state, codeMirror, tokenPos) {
const symbols = getSymbols(state, source);
let match;
if (!symbols || symbols.loading) {
if (!symbols) {
match = getExpressionFromCoords(codeMirror, tokenPos);
} else {
match = findBestMatchExpression(symbols, tokenPos);

View File

@ -36,7 +36,7 @@ export const setSymbols = memoizeableAction("setSymbols", {
}
const symbols = getSymbols(getState(), source);
if (!symbols || symbols.loading) {
if (!symbols) {
return null;
}

View File

@ -1,9 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sources - new sources sources - sources with querystrings should find two sources when same source with
querystring 1`] = `
Array [
"http://localhost:8000/examples/base.js?v=1",
"http://localhost:8000/examples/base.js?v=2",
]
`;

View File

@ -76,7 +76,6 @@ Object {
],
"imports": Array [],
"literals": Array [],
"loading": false,
"memberExpressions": Array [],
"objectProperties": Array [],
}

View File

@ -12,7 +12,7 @@ import {
} from "../../utils/test-head";
import readFixture from "./helpers/readFixture";
const { getSymbols, isSymbolsLoading } = selectors;
const { getSymbols } = selectors;
const mockCommandClient = {
sourceContents: async ({ source }) => ({
@ -52,7 +52,7 @@ describe("ast", () => {
const loadedSource = selectors.getSourceFromId(getState(), base.id);
await dispatch(actions.setSymbols({ cx, source: loadedSource }));
await waitForState(store, state => !isSymbolsLoading(state, base));
await waitForState(store, state => getSymbols(state, base));
const baseSymbols = getSymbols(getState(), base);
expect(baseSymbols).toMatchSnapshot();

View File

@ -60,7 +60,7 @@ async function pause(store, client) {
);
await dispatch(actions.selectSource(cx, base.id));
await waitForState(store, state => selectors.hasSymbols(state, base));
await waitForState(store, state => selectors.getSymbols(state, base));
const { thread } = cx;
const frames = [makeFrame({ id: "frame1", sourceId: base.id, thread })];
@ -85,7 +85,7 @@ describe("preview", () => {
);
await dispatch(actions.selectSource(cx, base.id));
await waitForState(store, state => selectors.hasSymbols(state, base));
await waitForState(store, state => selectors.getSymbols(state, base));
const frames = [makeFrame({ id: "f1", sourceId: base.id })];
await dispatch(

View File

@ -20,49 +20,39 @@ import {
getPauseReason,
getSourceTextContent,
getCurrentThread,
getPausePreviewLocation,
} from "../../selectors";
function isDocumentReady(location, sourceTextContent) {
return location && sourceTextContent && hasDocument(location.sourceId);
}
export class DebugLine extends PureComponent {
debugExpression;
static get propTypes() {
return {
location: PropTypes.object,
sourceTextContent: PropTypes.object,
why: PropTypes.object,
};
}
componentDidMount() {
const { why, location, sourceTextContent } = this.props;
this.setDebugLine(why, location, sourceTextContent);
const { why, location } = this.props;
this.setDebugLine(why, location);
}
componentWillUnmount() {
const { why, location, sourceTextContent } = this.props;
this.clearDebugLine(why, location, sourceTextContent);
const { why, location } = this.props;
this.clearDebugLine(why, location);
}
componentDidUpdate(prevProps) {
const { why, location, sourceTextContent } = this.props;
const { why, location } = this.props;
startOperation();
this.clearDebugLine(
prevProps.why,
prevProps.location,
prevProps.sourceTextContent
);
this.setDebugLine(why, location, sourceTextContent);
this.clearDebugLine(prevProps.why, prevProps.location);
this.setDebugLine(why, location);
endOperation();
}
setDebugLine(why, location, sourceTextContent) {
if (!location || !isDocumentReady(location, sourceTextContent)) {
setDebugLine(why, location) {
if (!location) {
return;
}
const { sourceId } = location;
@ -90,8 +80,10 @@ export class DebugLine extends PureComponent {
);
}
clearDebugLine(why, location, sourceTextContent) {
if (!location || !isDocumentReady(location, sourceTextContent)) {
clearDebugLine(why, location) {
// Avoid clearing the line if we didn't set a debug line before,
// or, if the document is no longer available
if (!location || !hasDocument(location.sourceId)) {
return;
}
@ -121,15 +113,24 @@ export class DebugLine extends PureComponent {
}
}
function isDocumentReady(location, sourceTextContent) {
return location && sourceTextContent && hasDocument(location.sourceId);
}
const mapStateToProps = state => {
// Avoid unecessary intermediate updates when there is no location
// or the source text content isn't yet fully loaded
const frame = getVisibleSelectedFrame(state);
const previewLocation = getPausePreviewLocation(state);
const location = previewLocation || frame?.location;
const location = frame?.location;
if (!location) {
return {};
}
const sourceTextContent = getSourceTextContent(state, location.sourceId);
if (!isDocumentReady(location, sourceTextContent)) {
return {};
}
return {
frame,
location,
sourceTextContent:
location && getSourceTextContent(state, location.sourceId),
why: getPauseReason(state, getCurrentThread(state)),
};
};

View File

@ -159,11 +159,33 @@ class Editor extends PureComponent {
editor = this.setupEditor();
}
startOperation();
this.setText(nextProps, editor);
this.setSize(nextProps, editor);
this.scrollToLocation(nextProps, editor);
endOperation();
const shouldUpdateText =
nextProps.selectedSource !== this.props.selectedSource ||
nextProps.selectedSourceTextContent !==
this.props.selectedSourceTextContent ||
nextProps.symbols !== this.props.symbols;
const shouldUpdateSize =
nextProps.startPanelSize !== this.props.startPanelSize ||
nextProps.endPanelSize !== this.props.endPanelSize;
const shouldScroll =
nextProps.selectedLocation &&
this.shouldScrollToLocation(nextProps, editor);
if (shouldUpdateText || shouldUpdateSize || shouldScroll) {
startOperation();
if (shouldUpdateText) {
this.setText(nextProps, editor);
}
if (shouldUpdateSize) {
editor.codeMirror.setSize();
}
if (shouldScroll) {
this.scrollToLocation(nextProps, editor);
}
endOperation();
}
if (this.props.selectedSource != nextProps.selectedSource) {
this.props.updateViewport();
@ -555,30 +577,15 @@ class Editor extends PureComponent {
scrollToLocation(nextProps, editor) {
const { selectedLocation, selectedSource } = nextProps;
if (selectedLocation && this.shouldScrollToLocation(nextProps, editor)) {
let { line, column } = toEditorPosition(selectedLocation);
let { line, column } = toEditorPosition(selectedLocation);
if (selectedSource && hasDocument(selectedSource.id)) {
const doc = getDocument(selectedSource.id);
const lineText = doc.getLine(line);
column = Math.max(column, getIndentation(lineText));
}
scrollToColumn(editor.codeMirror, line, column);
}
}
setSize(nextProps, editor) {
if (!editor) {
return;
if (selectedSource && hasDocument(selectedSource.id)) {
const doc = getDocument(selectedSource.id);
const lineText = doc.getLine(line);
column = Math.max(column, getIndentation(lineText));
}
if (
nextProps.startPanelSize !== this.props.startPanelSize ||
nextProps.endPanelSize !== this.props.endPanelSize
) {
editor.codeMirror.setSize();
}
scrollToColumn(editor.codeMirror, line, column);
}
setText(props, editor) {

View File

@ -101,7 +101,7 @@ export class Outline extends Component {
let classes = [];
let functions = [];
if (symbols && !symbols.loading) {
if (symbols) {
({ classes, functions } = symbols);
}
@ -221,7 +221,7 @@ export class Outline extends Component {
renderClassFunctions(klass, functions) {
const { symbols } = this.props;
if (!symbols || symbols.loading || klass == null || functions.length == 0) {
if (!symbols || klass == null || functions.length == 0) {
return null;
}
@ -310,7 +310,7 @@ export class Outline extends Component {
return this.renderPlaceholder();
}
if (!symbols || symbols.loading) {
if (!symbols) {
return this.renderLoading();
}

View File

@ -20,7 +20,6 @@ import {
getSourceContent,
getSymbols,
getTabs,
isSymbolsLoading,
getContext,
} from "../selectors";
import { memoizeLast } from "../utils/memoizeLast";
@ -442,6 +441,7 @@ function mapStateToProps(state) {
const selectedSource = getSelectedSource(state);
const displayedSources = getDisplayedSourcesList(state);
const tabs = getTabs(state);
const symbols = getSymbols(state, selectedSource);
return {
cx: getContext(state),
@ -451,8 +451,8 @@ function mapStateToProps(state) {
selectedContentLoaded: selectedSource
? !!getSourceContent(state, selectedSource.id)
: undefined,
symbols: formatSymbols(getSymbols(state, selectedSource)),
symbolsLoading: isSymbolsLoading(state, selectedSource),
symbols: formatSymbols(symbols),
symbolsLoading: !symbols,
query: getQuickOpenQuery(state),
searchType: getQuickOpenType(state),
tabs,

View File

@ -81,15 +81,6 @@ describe("Outline", () => {
});
expect(component).toMatchSnapshot();
});
it("if symbols are loading", () => {
const { component } = render({
symbols: {
loading: true,
},
});
expect(component).toMatchSnapshot();
});
});
it("renders ignore anonymous functions", async () => {

View File

@ -384,14 +384,6 @@ exports[`Outline renders outline renders ignore anonymous functions 1`] = `
</div>
`;
exports[`Outline renders outline renders loading if symbols are loading 1`] = `
<div
className="outline-pane-info"
>
Loading…
</div>
`;
exports[`Outline renders outline renders loading if symbols is not defined 1`] = `
<div
className="outline-pane-info"

View File

@ -21,10 +21,7 @@ function update(state = initialASTState(), action) {
case "SET_SYMBOLS": {
const { sourceId } = action;
if (action.status === "start") {
return {
...state,
symbols: { ...state.symbols, [sourceId]: { loading: true } },
};
return state;
}
const value = action.value;

View File

@ -28,7 +28,6 @@ export function initialPauseState(thread = "UnknownThread") {
thread,
pauseCounter: 0,
},
previewLocation: null,
highlightedCalls: null,
threads: {},
skipPausing: prefs.skipPausing,
@ -107,7 +106,6 @@ function update(state = initialPauseState(), action) {
const { thread, frame, why } = action;
state = {
...state,
previewLocation: null,
threadcx: {
...state.threadcx,
pauseCounter: state.threadcx.pauseCounter + 1,
@ -127,17 +125,23 @@ function update(state = initialPauseState(), action) {
case "FETCHED_FRAMES": {
const { frames } = action;
// We typically receive a PAUSED action before this one,
// with only the first frame. Here, we avoid replacing it
// with a copy of it in order to avoid triggerring selectors
// uncessarily
// (note that in jest, action's frames might be empty)
// (and if we resume in between PAUSED and FETCHED_FRAMES
// threadState().frames might be null)
if (threadState().frames) {
const previousFirstFrame = threadState().frames[0];
if (previousFirstFrame.id == frames[0]?.id) {
frames.splice(0, 1, previousFirstFrame);
}
}
return updateThreadState({ frames, framesLoading: false });
}
case "PREVIEW_PAUSED_LOCATION": {
return { ...state, previewLocation: action.location };
}
case "CLEAR_PREVIEW_PAUSED_LOCATION": {
return { ...state, previewLocation: null };
}
case "MAP_FRAMES": {
const { selectedFrameId, frames } = action;
return updateThreadState({ frames, selectedFrameId });

View File

@ -12,25 +12,6 @@ export function getSymbols(state, source) {
return state.ast.symbols[source.id] || null;
}
export function hasSymbols(state, source) {
const symbols = getSymbols(state, source);
if (!symbols) {
return false;
}
return !symbols.loading;
}
export function isSymbolsLoading(state, source) {
const symbols = getSymbols(state, source);
if (!symbols) {
return false;
}
return symbols.loading;
}
export function getInScopeLines(state, location) {
return state.ast.inScopeLines[makeBreakpointId(location)];
}

View File

@ -11,34 +11,22 @@ import { isGeneratedId } from "devtools-source-map";
import { getSelectedLocation as _getSelectedLocation } from "../utils/selected-location";
import { createSelector } from "reselect";
const getSelectedFrames = createSelector(
state => state.pause.threads,
export const getSelectedFrame = createSelector(
(state, thread) => state.pause.threads[thread],
threadPauseState => {
const selectedFrames = {};
for (const thread in threadPauseState) {
const pausedThread = threadPauseState[thread];
const { selectedFrameId, frames } = pausedThread;
if (frames) {
selectedFrames[thread] = frames.find(
frame => frame.id == selectedFrameId
);
}
if (!threadPauseState) return null;
const { selectedFrameId, frames } = threadPauseState;
if (frames) {
return frames.find(frame => frame.id == selectedFrameId);
}
return selectedFrames;
return null;
}
);
export function getSelectedFrame(state, thread) {
const selectedFrames = getSelectedFrames(state);
return selectedFrames[thread];
}
export const getVisibleSelectedFrame = createSelector(
getSelectedLocation,
getSelectedFrames,
getCurrentThread,
(selectedLocation, selectedFrames, thread) => {
const selectedFrame = selectedFrames[thread];
state => getSelectedFrame(state, getCurrentThread(state)),
(selectedLocation, selectedFrame) => {
if (!selectedFrame) {
return null;
}
@ -272,7 +260,3 @@ export function getSelectedInlinePreviews(state) {
export function getLastExpandedScopes(state, thread) {
return getThreadPauseState(state.pause, thread).lastExpandedScopes;
}
export function getPausePreviewLocation(state) {
return state.pause.previewLocation;
}

View File

@ -3,7 +3,7 @@
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
export function findBestMatchExpression(symbols, tokenPos) {
if (symbols.loading) {
if (!symbols) {
return null;
}
@ -81,7 +81,7 @@ function findClosestofSymbol(declarations, location) {
}
export function findClosestFunction(symbols, location) {
if (!symbols || symbols.loading) {
if (!symbols) {
return null;
}
@ -89,7 +89,7 @@ export function findClosestFunction(symbols, location) {
}
export function findClosestClass(symbols, location) {
if (!symbols || symbols.loading) {
if (!symbols) {
return null;
}

View File

@ -80,7 +80,7 @@ export function formatSymbol(symbol) {
}
export function formatSymbols(symbols) {
if (!symbols || symbols.loading) {
if (!symbols) {
return { functions: [] };
}

View File

@ -500,7 +500,7 @@ export function getSourceClassnames(source, symbols) {
return "blackBox";
}
if (symbols && !symbols.loading && symbols.framework) {
if (symbols && symbols.framework) {
return symbols.framework.toLowerCase();
}

View File

@ -40,7 +40,6 @@ const defaultSymbolDeclarations = {
literals: [],
hasJsx: false,
hasTypes: false,
loading: false,
framework: undefined,
};

View File

@ -107,7 +107,6 @@ function extractSymbols(sourceId) {
literals: [],
hasJsx: false,
hasTypes: false,
loading: false,
framework: undefined,
};

View File

@ -85,8 +85,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;
@ -153,8 +151,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;
@ -201,8 +197,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;
@ -271,8 +265,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;
@ -435,8 +427,6 @@ hasJsx: true
hasTypes: false
loading: false
framework: undefined"
`;
@ -526,8 +516,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;
@ -566,8 +554,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;
@ -749,8 +735,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;
@ -831,8 +815,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;
@ -873,8 +855,6 @@ hasJsx: false
hasTypes: true
loading: false
framework: undefined"
`;
@ -958,8 +938,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;
@ -1085,8 +1063,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;
@ -1125,8 +1101,6 @@ hasJsx: true
hasTypes: false
loading: false
framework: undefined"
`;
@ -1180,8 +1154,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;
@ -1264,8 +1236,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;
@ -1308,8 +1278,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;
@ -1385,8 +1353,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;
@ -1446,8 +1412,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;
@ -1486,8 +1450,6 @@ hasJsx: false
hasTypes: false
loading: false
framework: React"
`;
@ -1556,7 +1518,5 @@ hasJsx: false
hasTypes: false
loading: false
framework: undefined"
`;

View File

@ -34,7 +34,7 @@ function summarize(symbol) {
return `${loc} ${expression} ${name}${params} ${klass} ${names} ${values} ${index}`.trim(); // eslint-disable-line max-len
}
const bools = ["hasJsx", "hasTypes", "loading"];
const bools = ["hasJsx", "hasTypes"];
const strings = ["framework"];
function formatBool(name, symbols) {
return `${name}: ${symbols[name] ? "true" : "false"}`;

View File

@ -109,7 +109,7 @@ add_task(async function() {
dbg.wasmOffsetToLine(binarySource.id, virtualBinaryLine) + 1;
// We can't use selectSource here because binary source won't have symbols loaded
// (hasSymbols(source) selector will be false)
// (getSymbols(source) selector will be false)
await dbg.actions.selectLocation(
getContext(dbg),
{ sourceId: binarySource.id },

View File

@ -199,7 +199,7 @@ function waitForSelectedSource(dbg, sourceOrUrl) {
const {
getSelectedSource,
getSelectedSourceTextContent,
hasSymbols,
getSymbols,
getBreakableLines,
} = dbg.selectors;
@ -224,7 +224,7 @@ function waitForSelectedSource(dbg, sourceOrUrl) {
}
}
return hasSymbols(source) && getBreakableLines(source.id);
return getSymbols(source) && getBreakableLines(source.id);
},
"selected source"
);

View File

@ -48,7 +48,7 @@ class Range extends PureComponent {
const rangeValue = scale.fromValueToFraction(value) * max;
return div(
{ className: "perf-settings-row" },
{ className: "perf-settings-range-row" },
label(
{
className: "perf-settings-label",
@ -56,25 +56,19 @@ class Range extends PureComponent {
},
labelText
),
div(
{ className: "perf-settings-value" },
div(
{ className: "perf-settings-range-input" },
input({
type: "range",
className: `perf-settings-range-input-el`,
min,
"aria-valuemin": scale.fromFractionToValue(0),
max,
"aria-valuemax": scale.fromFractionToValue(1),
value: rangeValue,
"aria-valuenow": value,
onChange: this.handleInput,
id,
})
),
div({ className: `perf-settings-range-value` }, display(value))
)
input({
type: "range",
className: `perf-settings-range-input`,
min,
"aria-valuemin": scale.fromFractionToValue(0),
max,
"aria-valuemax": scale.fromFractionToValue(1),
value: rangeValue,
"aria-valuenow": value,
onChange: this.handleInput,
id,
}),
div({ className: `perf-settings-range-value` }, display(value))
);
}
}

View File

@ -80,26 +80,18 @@ h2 {
line-height: 1.8;
}
.perf-settings-label {
height: 30px;
min-width: 140px;
}
.perf-settings-value {
display: flex;
flex: 1;
.perf-settings-range-row {
display: grid;
grid-template-columns: 140px 1fr 90px;
align-items: center;
min-height: 30px;
}
.perf-settings-range-input {
flex: 1;
}
.perf-settings-range-input-el {
width: 100%;
margin: 0;
}
.perf-settings-range-value {
min-width: 90px;
text-align: end;
}

View File

@ -73,6 +73,7 @@ const NetworkEventActor = protocol.ActorClassWithSpec(networkEventSpec, {
blockedReason: this._blockedReason,
blockingExtension: this._blockingExtension,
channelId: this._channelId,
chromeContext: this._isFromSystemPrincipal,
};
},
@ -116,7 +117,7 @@ const NetworkEventActor = protocol.ActorClassWithSpec(networkEventSpec, {
this._referrerPolicy = networkEvent.referrerPolicy;
this._priority = networkEvent.priority;
this._channelId = networkEvent.channelId;
this._isFromSystemPrincipal = networkEvent.isFromSystemPrincipal;
// Stack trace info isn't sent automatically. The client
// needs to request it explicitly using getStackTrace
// packet. NetmonitorActor may pass just a boolean instead of the stack

View File

@ -67,6 +67,7 @@ module.exports = async function({
actor.cause.type == "websocket"
? actor.url.replace(/^http/, "ws")
: actor.channelId,
chromeContext: actor.chromeContext,
},
]);
}

View File

@ -2276,10 +2276,14 @@ void CanonicalBrowsingContext::SynchronizeLayoutHistoryState() {
} else if (ContentParent* cp = GetContentParent()) {
cp->SendGetLayoutHistoryState(this)->Then(
GetCurrentSerialEventTarget(), __func__,
[activeEntry =
mActiveEntry](const RefPtr<nsILayoutHistoryState>& aState) {
if (aState) {
activeEntry->SetLayoutHistoryState(aState);
[activeEntry = mActiveEntry](
const Tuple<RefPtr<nsILayoutHistoryState>, Maybe<Wireframe>>&
aResult) {
if (mozilla::Get<0>(aResult)) {
activeEntry->SetLayoutHistoryState(mozilla::Get<0>(aResult));
}
if (mozilla::Get<1>(aResult)) {
activeEntry->SetWireframe(mozilla::Get<1>(aResult));
}
},
[]() {});

View File

@ -11154,32 +11154,41 @@ bool nsDocShell::OnNewURI(nsIURI* aURI, nsIChannel* aChannel,
return onLocationChangeNeeded;
}
bool nsDocShell::CollectWireframe() {
Maybe<Wireframe> nsDocShell::GetWireframe() {
const bool collectWireFrame =
mozilla::SessionHistoryInParent() &&
StaticPrefs::browser_history_collectWireframes() &&
mBrowsingContext->IsTopContent() && mActiveEntry;
if (!collectWireFrame) {
return false;
return Nothing();
}
RefPtr<Document> doc = mContentViewer->GetDocument();
Nullable<Wireframe> wireframe;
doc->GetWireframeWithoutFlushing(false, wireframe);
if (wireframe.IsNull()) {
return Nothing();
}
return Some(wireframe.Value());
}
bool nsDocShell::CollectWireframe() {
Maybe<Wireframe> wireframe = GetWireframe();
if (wireframe.isNothing()) {
return false;
}
if (XRE_IsParentProcess()) {
SessionHistoryEntry* entry =
mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
if (entry) {
entry->SetWireframe(Some(wireframe.Value()));
entry->SetWireframe(wireframe);
}
} else {
mozilla::Unused
<< ContentChild::GetSingleton()->SendSessionHistoryEntryWireframe(
mBrowsingContext, wireframe.Value());
mBrowsingContext, wireframe.ref());
}
return true;
@ -12113,8 +12122,6 @@ nsDocShell::PersistLayoutHistoryState() {
if (scrollRestorationIsManual && layoutState) {
layoutState->ResetScrollState();
}
CollectWireframe();
}
return rv;

View File

@ -50,6 +50,7 @@ class ClientSource;
class EventTarget;
class SessionHistoryInfo;
struct LoadingSessionHistoryInfo;
struct Wireframe;
} // namespace dom
namespace net {
class LoadInfo;
@ -737,6 +738,10 @@ class nsDocShell final : public nsDocLoader,
bool aAddToGlobalHistory, bool aCloneSHChildren);
public:
// If wireframe collection is enabled, will attempt to gather the
// wireframe for the document.
mozilla::Maybe<mozilla::dom::Wireframe> GetWireframe();
// If wireframe collection is enabled, will attempt to gather the
// wireframe for the document and stash it inside of the active history
// entry. Returns true if wireframes were collected.

View File

@ -1274,6 +1274,10 @@ static void FinishRestore(CanonicalBrowsingContext* aBrowsingContext,
});
}
if (aEntry) {
aEntry->SetWireframe(Nothing());
}
// ReplacedBy will swap the entry back.
aBrowsingContext->SetActiveSessionHistoryEntry(aEntry);
loadingBC->SetActiveSessionHistoryEntry(nullptr);

View File

@ -20,38 +20,45 @@ add_setup(async function() {
add_task(async function() {
await BrowserTestUtils.withNewTab(PAGE_1, async browser => {
let sh = browser.browsingContext.sessionHistory;
Assert.equal(sh.count, 1, "Got the right SessionHistory entry count.");
Assert.equal(
sh.count,
1,
"Got the right SessionHistory entry count after initial tab load."
);
Assert.ok(
!sh.getEntryAtIndex(0).wireframe,
"No wireframe for the loaded entry."
"No wireframe for the loaded entry after initial tab load."
);
let loaded = BrowserTestUtils.browserLoaded(browser, false, PAGE_2);
BrowserTestUtils.loadURI(browser, PAGE_2);
await loaded;
Assert.equal(sh.count, 2, "Got the right SessionHistory entry count.");
Assert.equal(
sh.count,
2,
"Got the right SessionHistory entry count after loading page 2."
);
Assert.ok(
sh.getEntryAtIndex(0).wireframe,
"A wireframe was captured for the first entry."
"A wireframe was captured for the first entry after loading page 2."
);
Assert.ok(
!sh.getEntryAtIndex(1).wireframe,
"No wireframe for the loaded entry."
"No wireframe for the loaded entry after loading page 2."
);
// Now go back
loaded = BrowserTestUtils.waitForContentEvent(browser, "pageshow");
browser.goBack();
await loaded;
// These are TODO due to bug 1759528.
todo(
Assert.ok(
sh.getEntryAtIndex(1).wireframe,
"A wireframe was captured for the second entry."
"A wireframe was captured for the second entry after going back."
);
todo(
Assert.ok(
!sh.getEntryAtIndex(0).wireframe,
"No wireframe for the loaded entry."
"No wireframe for the loaded entry after going back."
);
// Now forward again
@ -59,14 +66,18 @@ add_task(async function() {
browser.goForward();
await loaded;
Assert.equal(sh.count, 2, "Got the right SessionHistory entry count.");
Assert.equal(
sh.count,
2,
"Got the right SessionHistory entry count after going forward again."
);
Assert.ok(
sh.getEntryAtIndex(0).wireframe,
"A wireframe was captured for the first entry."
"A wireframe was captured for the first entry after going forward again."
);
Assert.ok(
!sh.getEntryAtIndex(1).wireframe,
"No wireframe for the loaded entry."
"No wireframe for the loaded entry after going forward again."
);
// And using pushState
@ -75,22 +86,26 @@ add_task(async function() {
content.history.pushState({}, "", "nothing-2.html");
});
Assert.equal(sh.count, 4, "Got the right SessionHistory entry count.");
Assert.equal(
sh.count,
4,
"Got the right SessionHistory entry count after using pushState."
);
Assert.ok(
sh.getEntryAtIndex(0).wireframe,
"A wireframe was captured for the first entry."
"A wireframe was captured for the first entry after using pushState."
);
Assert.ok(
sh.getEntryAtIndex(1).wireframe,
"A wireframe was captured for the second entry."
"A wireframe was captured for the second entry after using pushState."
);
Assert.ok(
sh.getEntryAtIndex(2).wireframe,
"A wireframe was captured for the third entry."
"A wireframe was captured for the third entry after using pushState."
);
Assert.ok(
!sh.getEntryAtIndex(3).wireframe,
"No wireframe for the loaded entry."
"No wireframe for the loaded entry after using pushState."
);
// Now check that wireframes can be written to in case we're restoring

View File

@ -226,21 +226,17 @@ void CacheOpParent::ProcessCrossOriginResourcePolicyHeader(
Maybe<mozilla::ipc::PrincipalInfo> principalInfo;
switch (mOpArgs.type()) {
case CacheOpArgs::TCacheMatchArgs: {
loadingCOEP =
mOpArgs.get_CacheMatchArgs().request().loadingEmbedderPolicy();
principalInfo = mOpArgs.get_CacheMatchArgs().request().principalInfo();
const auto& request = mOpArgs.get_CacheMatchArgs().request();
loadingCOEP = request.loadingEmbedderPolicy();
principalInfo = request.principalInfo();
break;
}
case CacheOpArgs::TCacheMatchAllArgs: {
if (mOpArgs.get_CacheMatchAllArgs().maybeRequest().isSome()) {
loadingCOEP = mOpArgs.get_CacheMatchAllArgs()
.maybeRequest()
.ref()
.loadingEmbedderPolicy();
principalInfo = mOpArgs.get_CacheMatchAllArgs()
.maybeRequest()
.ref()
.principalInfo();
const auto& request =
mOpArgs.get_CacheMatchAllArgs().maybeRequest().ref();
loadingCOEP = request.loadingEmbedderPolicy();
principalInfo = request.principalInfo();
}
break;
}
@ -266,6 +262,7 @@ void CacheOpParent::ProcessCrossOriginResourcePolicyHeader(
}
const auto& headers = it->mValue.headers();
const RequestCredentials credentials = it->mValue.credentials();
const auto corpHeaderIt =
std::find_if(headers.cbegin(), headers.cend(), [](const auto& header) {
return header.name().EqualsLiteral("Cross-Origin-Resource-Policy");
@ -294,11 +291,22 @@ void CacheOpParent::ProcessCrossOriginResourcePolicyHeader(
const mozilla::ipc::ContentPrincipalInfo& responseContentPrincipalInfo =
it->mValue.principalInfo().ref().get_ContentPrincipalInfo();
const auto& corp =
nsCString corp =
corpHeaderIt == headers.cend() ? EmptyCString() : corpHeaderIt->value();
if (corp.IsEmpty()) {
if (loadingCOEP == nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) {
// This means the request of this request doesn't have
// credentials, so it's safe for us to return.
if (credentials == RequestCredentials::Omit) {
return;
}
corp = "same-origin";
}
}
if (corp.EqualsLiteral("same-origin")) {
if (responseContentPrincipalInfo == contentPrincipalInfo) {
if (responseContentPrincipalInfo != contentPrincipalInfo) {
aRv.ThrowTypeError("Response is expected from same origin.");
return;
}

View File

@ -84,6 +84,7 @@ struct CacheResponse
PrincipalInfo? principalInfo;
uint32_t paddingInfo;
int64_t paddingSize;
RequestCredentials credentials;
};
struct CacheRequestResponse

View File

@ -1794,7 +1794,8 @@ Result<SavedResponse, nsresult> ReadResponse(mozIStorageConnection& aConn,
"entries.response_body_id, "
"entries.response_principal_info, "
"entries.response_padding_size, "
"security_info.data "
"security_info.data, "
"entries.request_credentials "
"FROM entries "
"LEFT OUTER JOIN security_info "
"ON entries.response_security_info_id=security_info.id "
@ -1896,6 +1897,11 @@ Result<SavedResponse, nsresult> ReadResponse(mozIStorageConnection& aConn,
QM_TRY(MOZ_TO_RESULT(state->GetBlobAsUTF8String(
7, savedResponse.mValue.channelInfo().securityInfo())));
QM_TRY_INSPECT(const int32_t& credentials,
MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 8));
savedResponse.mValue.credentials() =
static_cast<RequestCredentials>(credentials);
{
QM_TRY_INSPECT(const auto& state,
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(

View File

@ -196,6 +196,7 @@ SafeRefPtr<InternalResponse> InternalResponse::Clone(CloneType aCloneType) {
clone->mPaddingSize = mPaddingSize;
clone->mCacheInfoChannel = mCacheInfoChannel;
clone->mCredentialsMode = mCredentialsMode;
if (mWrappedResponse) {
clone->mWrappedResponse = mWrappedResponse->Clone(aCloneType);

View File

@ -4366,12 +4366,16 @@ mozilla::ipc::IPCResult ContentChild::RecvGetLayoutHistoryState(
GetLayoutHistoryStateResolver&& aResolver) {
nsCOMPtr<nsILayoutHistoryState> state;
nsIDocShell* docShell;
mozilla::Maybe<mozilla::dom::Wireframe> wireframe;
if (!aContext.IsNullOrDiscarded() &&
(docShell = aContext.get()->GetDocShell())) {
docShell->PersistLayoutHistoryState();
docShell->GetLayoutHistoryState(getter_AddRefs(state));
wireframe = static_cast<nsDocShell*>(docShell)->GetWireframe();
}
aResolver(state);
aResolver(Tuple<nsILayoutHistoryState*, const mozilla::Maybe<Wireframe>&>(
state, wireframe));
return IPC_OK();
}

View File

@ -1012,7 +1012,7 @@ child:
nsID aChangeID);
async GetLayoutHistoryState(MaybeDiscardedBrowsingContext aContext)
returns (nsILayoutHistoryState aState);
returns (nsILayoutHistoryState aState, Wireframe? aWireframe);
async DispatchLocationChangeEvent(MaybeDiscardedBrowsingContext aContext);

View File

@ -1513,6 +1513,10 @@ nsresult nsContentSecurityManager::CheckChannel(nsIChannel* aChannel) {
AddLoadFlags(aChannel, nsIRequest::LOAD_ANONYMOUS);
}
if (!CrossOriginEmbedderPolicyAllowsCredentials(aChannel)) {
AddLoadFlags(aChannel, nsIRequest::LOAD_ANONYMOUS);
}
nsSecurityFlags securityMode = loadInfo->GetSecurityMode();
// CORS mode is handled by nsCORSListenerProxy
@ -1562,6 +1566,54 @@ nsresult nsContentSecurityManager::CheckChannel(nsIChannel* aChannel) {
return NS_OK;
}
// https://fetch.spec.whatwg.org/#ref-for-cross-origin-embedder-policy-allows-credentials
bool nsContentSecurityManager::CrossOriginEmbedderPolicyAllowsCredentials(
nsIChannel* aChannel) {
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
// 1. If requests mode is not "no-cors", then return true.
//
// `no-cors` check applies to document navigation such that if it is
// an document navigation, this check should return true to allow
// credentials.
if (loadInfo->GetExternalContentPolicyType() ==
ExtContentPolicy::TYPE_DOCUMENT ||
loadInfo->GetExternalContentPolicyType() ==
ExtContentPolicy::TYPE_SUBDOCUMENT) {
return true;
}
if (loadInfo->GetSecurityMode() !=
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL &&
loadInfo->GetSecurityMode() !=
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT) {
return true;
}
// If requests clients policy containers embedder policys value is not
// "credentialless", then return true.
if (loadInfo->GetLoadingEmbedderPolicy() !=
nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) {
return true;
}
// If requests origin is same origin with requests current URLs origin and
// request does not have a redirect-tainted origin, then return true.
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
nsCOMPtr<nsIPrincipal> resourcePrincipal;
ssm->GetChannelURIPrincipal(aChannel, getter_AddRefs(resourcePrincipal));
bool sameOrigin = resourcePrincipal->Equals(loadInfo->TriggeringPrincipal());
nsAutoCString serializedOrigin;
GetSerializedOrigin(loadInfo->TriggeringPrincipal(), resourcePrincipal,
serializedOrigin, loadInfo);
if (sameOrigin && !serializedOrigin.IsEmpty()) {
return true;
}
return false;
}
// https://fetch.spec.whatwg.org/#serializing-a-request-origin
void nsContentSecurityManager::GetSerializedOrigin(
nsIPrincipal* aOrigin, nsIPrincipal* aResourceOrigin,

View File

@ -81,6 +81,7 @@ class nsContentSecurityManager : public nsIContentSecurityManager,
static nsresult CheckAllowLoadInSystemPrivilegedContext(nsIChannel* aChannel);
static nsresult CheckAllowLoadInPrivilegedAboutContext(nsIChannel* aChannel);
static nsresult CheckChannelHasProtocolSecurityFlag(nsIChannel* aChannel);
static bool CrossOriginEmbedderPolicyAllowsCredentials(nsIChannel* aChannel);
virtual ~nsContentSecurityManager() = default;
};

View File

@ -1751,6 +1751,13 @@ already_AddRefed<DrawTarget> DrawTargetCairo::CreateSimilarDrawTarget(
similar = cairo_win32_surface_create_with_dib(
GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height);
break;
#endif
#ifdef CAIRO_HAS_QUARTZ_SURFACE
case CAIRO_SURFACE_TYPE_QUARTZ:
similar = cairo_quartz_surface_create_cg_layer(
mSurface, GfxFormatToCairoContent(aFormat), aSize.width,
aSize.height);
break;
#endif
default:
similar = cairo_surface_create_similar(mSurface,

View File

@ -7560,7 +7560,7 @@ _cairo_pdf_surface_analyze_operation (cairo_pdf_surface_t *surface,
if (_cairo_surface_get_extents (surface_pattern->surface, &rec_extents)) {
if (_cairo_fixed_integer_ceil(box.p1.x) < rec_extents.x ||
_cairo_fixed_integer_ceil(box.p1.y) < rec_extents.y ||
_cairo_fixed_integer_floor(box.p2.y) > rec_extents.x + rec_extents.width ||
_cairo_fixed_integer_floor(box.p2.x) > rec_extents.x + rec_extents.width ||
_cairo_fixed_integer_floor(box.p2.y) > rec_extents.y + rec_extents.height)
{
return CAIRO_INT_STATUS_UNSUPPORTED;

View File

@ -55,7 +55,8 @@ typedef enum {
DO_DIRECT,
DO_SHADING,
DO_IMAGE,
DO_TILED_IMAGE
DO_TILED_IMAGE,
DO_LAYER
} cairo_quartz_action_t;
/* define CTFontRef for pre-10.5 SDKs */
@ -72,6 +73,11 @@ typedef struct cairo_quartz_surface {
cairo_surface_clipper_t clipper;
/**
* If non-null, this is the CGLayer for the surface.
*/
CGLayerRef cgLayer;
cairo_rectangle_int_t extents;
cairo_rectangle_int_t virtual_extents;

View File

@ -503,6 +503,7 @@ _cairo_quartz_cairo_operator_to_quartz_blend (cairo_operator_t op)
default:
ASSERT_NOT_REACHED;
}
return kCGBlendModeNormal; /* just to silence clang warning [-Wreturn-type] */
}
static cairo_int_status_t
@ -1065,7 +1066,7 @@ typedef struct {
/* Destination rect */
CGRect rect;
/* Used with DO_SHADING, DO_IMAGE and DO_TILED_IMAGE */
/* Used with DO_SHADING, DO_IMAGE, DO_TILED_IMAGE, DO_LAYER */
CGAffineTransform transform;
/* Used with DO_IMAGE and DO_TILED_IMAGE */
@ -1077,6 +1078,11 @@ typedef struct {
/* Temporary destination for unbounded operations */
CGLayerRef layer;
CGRect clipRect;
/* Source layer to be rendered when using DO_LAYER.
Unlike 'layer' above, this is not owned by the drawing state
but by the source surface. */
CGLayerRef sourceLayer;
} cairo_quartz_drawing_state_t;
/*
@ -1253,7 +1259,9 @@ _cairo_quartz_setup_state (cairo_quartz_drawing_state_t *state,
}
if (source->type == CAIRO_PATTERN_TYPE_SURFACE &&
(source->extend == CAIRO_EXTEND_NONE || (CGContextDrawTiledImagePtr && source->extend == CAIRO_EXTEND_REPEAT)))
(source->extend == CAIRO_EXTEND_NONE ||
source->extend == CAIRO_EXTEND_PAD ||
(CGContextDrawTiledImagePtr && source->extend == CAIRO_EXTEND_REPEAT)))
{
const cairo_surface_pattern_t *spat = (const cairo_surface_pattern_t *) source;
cairo_surface_t *pat_surf = spat->surface;
@ -1265,6 +1273,20 @@ _cairo_quartz_setup_state (cairo_quartz_drawing_state_t *state,
cairo_fixed_t fw, fh;
cairo_bool_t is_bounded;
/* Draw nonrepeating CGLayer surface using DO_LAYER */
if (source->extend != CAIRO_EXTEND_REPEAT &&
cairo_surface_get_type (pat_surf) == CAIRO_SURFACE_TYPE_QUARTZ) {
cairo_quartz_surface_t *quartz_surf = (cairo_quartz_surface_t *) pat_surf;
if (quartz_surf->cgLayer) {
cairo_matrix_invert(&m);
_cairo_quartz_cairo_matrix_to_quartz (&m, &state->transform);
state->rect = CGRectMake (0, 0, quartz_surf->extents.width, quartz_surf->extents.height);
state->sourceLayer = quartz_surf->cgLayer;
state->action = DO_LAYER;
return CAIRO_STATUS_SUCCESS;
}
}
_cairo_surface_get_extents (composite->surface, &extents);
status = _cairo_surface_to_cgimage (pat_surf, &extents, format,
&m, clip, &img);
@ -1426,7 +1448,14 @@ _cairo_quartz_draw_source (cairo_quartz_drawing_state_t *state,
CGContextTranslateCTM (state->cgDrawContext, 0, state->rect.size.height);
CGContextScaleCTM (state->cgDrawContext, 1, -1);
if (state->action == DO_IMAGE) {
if (state->action == DO_LAYER) {
/* Note that according to Apple docs it's completely legal to draw a CGLayer
* to any CGContext, even one it wasn't created for.
*/
assert (state->sourceLayer);
CGContextDrawLayerAtPoint (state->cgDrawContext, state->rect.origin,
state->sourceLayer);
} else if (state->action == DO_IMAGE) {
CGContextDrawImage (state->cgDrawContext, state->rect, state->image);
if (op == CAIRO_OPERATOR_SOURCE &&
state->cgDrawContext == state->cgMaskContext)
@ -1655,6 +1684,10 @@ _cairo_quartz_surface_finish (void *abstract_surface)
surface->imageData = NULL;
if (surface->cgLayer) {
CGLayerRelease (surface->cgLayer);
}
return CAIRO_STATUS_SUCCESS;
}
@ -1693,9 +1726,14 @@ _cairo_quartz_surface_create_similar (void *abstract_surface,
int width,
int height)
{
cairo_quartz_surface_t *surface, *similar_quartz;
cairo_quartz_surface_t *similar_quartz;
cairo_surface_t *similar;
cairo_format_t format;
cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;
if (surface->cgLayer)
return cairo_quartz_surface_create_cg_layer (abstract_surface, content,
width, height);
if (content == CAIRO_CONTENT_COLOR_ALPHA)
format = CAIRO_FORMAT_ARGB32;
@ -2068,7 +2106,6 @@ _cairo_quartz_cg_glyphs (const cairo_compositor_t *compositor,
cairo_quartz_drawing_state_t state;
cairo_int_status_t rv = CAIRO_INT_STATUS_UNSUPPORTED;
int i;
CGFontRef cgfref = NULL;
cairo_bool_t didForceFontSmoothing = FALSE;
cairo_antialias_t effective_antialiasing;
@ -2087,10 +2124,12 @@ _cairo_quartz_cg_glyphs (const cairo_compositor_t *compositor,
CGContextSetTextDrawingMode (state.cgMaskContext, kCGTextClip);
}
/* this doesn't addref */
cgfref = _cairo_quartz_scaled_font_get_cg_font_ref (scaled_font);
CGContextSetFont (state.cgMaskContext, cgfref);
CGContextSetFontSize (state.cgMaskContext, 1.0);
if (!CTFontDrawGlyphsPtr) {
/* this doesn't addref */
CGFontRef cgfref = _cairo_quartz_scaled_font_get_cg_font_ref (scaled_font);
CGContextSetFont (state.cgMaskContext, cgfref);
CGContextSetFontSize (state.cgMaskContext, 1.0);
}
effective_antialiasing = scaled_font->options.antialias;
if (effective_antialiasing == CAIRO_ANTIALIAS_SUBPIXEL &&
@ -2624,6 +2663,79 @@ cairo_quartz_surface_create_for_cg_context (CGContextRef cgContext,
return &surf->base;
}
/**
* cairo_quartz_surface_create_cg_layer
* @surface: The returned surface can be efficiently drawn into this
* destination surface (if tiling is not used)."
* @content: the content type of the surface
* @width: width of the surface, in pixels
* @height: height of the surface, in pixels
*
* Creates a Quartz surface backed by a CGLayer, if the given surface
* is a Quartz surface; the CGLayer is created to match the surface's
* Quartz context. Otherwise just calls cairo_surface_create_similar.
* The returned surface can be efficiently blitted to the given surface,
* but tiling and 'extend' modes other than NONE are not so efficient.
*
* Return value: the newly created surface.
*
* Since: 1.10
**/
cairo_surface_t *
cairo_quartz_surface_create_cg_layer (cairo_surface_t *surface,
cairo_content_t content,
unsigned int width,
unsigned int height)
{
cairo_quartz_surface_t *surf;
CGLayerRef layer;
CGContextRef ctx;
CGContextRef cgContext;
cgContext = cairo_quartz_surface_get_cg_context (surface);
if (!cgContext)
return cairo_surface_create_similar (surface, content,
width, height);
if (!_cairo_quartz_verify_surface_size(width, height))
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_SIZE));
/* If we pass zero width or height into CGLayerCreateWithContext below,
* it will fail.
*/
if (width == 0 || height == 0) {
return (cairo_surface_t*)
_cairo_quartz_surface_create_internal (NULL, content,
width, height);
}
layer = CGLayerCreateWithContext (cgContext,
CGSizeMake (width, height),
NULL);
if (!layer)
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
ctx = CGLayerGetContext (layer);
/* Flip it when we draw into it, so that when we finally composite it
* to a flipped target, the directions match and Quartz will optimize
* the composition properly
*/
CGContextTranslateCTM (ctx, 0, height);
CGContextScaleCTM (ctx, 1, -1);
CGContextRetain (ctx);
surf = _cairo_quartz_surface_create_internal (ctx, content,
width, height);
if (surf->base.status) {
CGLayerRelease (layer);
// create_internal will have set an error
return (cairo_surface_t*) surf;
}
surf->cgLayer = layer;
return (cairo_surface_t *) surf;
}
/**
* cairo_quartz_surface_create:
* @format: format of pixels in the surface to create

View File

@ -61,6 +61,12 @@ cairo_quartz_surface_create_for_data (unsigned char *data,
unsigned int height,
unsigned int stride);
cairo_public cairo_surface_t *
cairo_quartz_surface_create_cg_layer (cairo_surface_t *surface,
cairo_content_t content,
unsigned int width,
unsigned int height);
cairo_public CGContextRef
cairo_quartz_surface_get_cg_context (cairo_surface_t *surface);

Some files were not shown because too many files have changed in this diff Show More