mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-04 13:07:52 +00:00
Merge autoland to mozilla-central. a=merge
This commit is contained in:
commit
b7642d0089
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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]
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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>
|
||||
)}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"];
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -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" },
|
||||
]);
|
||||
});
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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'))
|
||||
|
@ -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();
|
||||
|
@ -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 =
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
@ -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(
|
||||
|
@ -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", {
|
||||
|
@ -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));
|
||||
}
|
@ -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]
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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(
|
||||
|
@ -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):
|
||||
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -2445,7 +2445,7 @@
|
||||
"byName": {},
|
||||
"byBlocks": {},
|
||||
"usedIds": {
|
||||
"0": 0
|
||||
"1": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2466,7 +2466,7 @@
|
||||
"byName": {},
|
||||
"byBlocks": {},
|
||||
"usedIds": {
|
||||
"0": 0
|
||||
"1": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14695,7 +14695,6 @@ function extractSymbols(sourceId) {
|
||||
literals: [],
|
||||
hasJsx: false,
|
||||
hasTypes: false,
|
||||
loading: false,
|
||||
framework: undefined
|
||||
};
|
||||
const state = {
|
||||
|
@ -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 };
|
||||
|
@ -53,7 +53,7 @@ export function highlightCalls(cx) {
|
||||
|
||||
const symbols = getSymbols(getState(), source);
|
||||
|
||||
if (!symbols || symbols.loading) {
|
||||
if (!symbols) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -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";
|
||||
|
@ -19,7 +19,6 @@ CompiledModules(
|
||||
"mapScopes.js",
|
||||
"paused.js",
|
||||
"pauseOnExceptions.js",
|
||||
"previewPausedLocation.js",
|
||||
"resumed.js",
|
||||
"selectFrame.js",
|
||||
"highlightCalls.js",
|
||||
|
@ -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",
|
||||
};
|
||||
}
|
@ -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);
|
||||
|
@ -36,7 +36,7 @@ export const setSymbols = memoizeableAction("setSymbols", {
|
||||
}
|
||||
|
||||
const symbols = getSymbols(getState(), source);
|
||||
if (!symbols || symbols.loading) {
|
||||
if (!symbols) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
]
|
||||
`;
|
@ -76,7 +76,6 @@ Object {
|
||||
],
|
||||
"imports": Array [],
|
||||
"literals": Array [],
|
||||
"loading": false,
|
||||
"memberExpressions": Array [],
|
||||
"objectProperties": Array [],
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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(
|
||||
|
@ -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)),
|
||||
};
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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 () => {
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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 });
|
||||
|
@ -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)];
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ export function formatSymbol(symbol) {
|
||||
}
|
||||
|
||||
export function formatSymbols(symbols) {
|
||||
if (!symbols || symbols.loading) {
|
||||
if (!symbols) {
|
||||
return { functions: [] };
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,6 @@ const defaultSymbolDeclarations = {
|
||||
literals: [],
|
||||
hasJsx: false,
|
||||
hasTypes: false,
|
||||
loading: false,
|
||||
framework: undefined,
|
||||
};
|
||||
|
||||
|
@ -107,7 +107,6 @@ function extractSymbols(sourceId) {
|
||||
literals: [],
|
||||
hasJsx: false,
|
||||
hasTypes: false,
|
||||
loading: false,
|
||||
framework: undefined,
|
||||
};
|
||||
|
||||
|
@ -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"
|
||||
`;
|
||||
|
@ -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"}`;
|
||||
|
@ -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 },
|
||||
|
@ -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"
|
||||
);
|
||||
|
@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -67,6 +67,7 @@ module.exports = async function({
|
||||
actor.cause.type == "websocket"
|
||||
? actor.url.replace(/^http/, "ws")
|
||||
: actor.channelId,
|
||||
chromeContext: actor.chromeContext,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
},
|
||||
[]() {});
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
34
dom/cache/CacheOpParent.cpp
vendored
34
dom/cache/CacheOpParent.cpp
vendored
@ -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;
|
||||
}
|
||||
|
1
dom/cache/CacheTypes.ipdlh
vendored
1
dom/cache/CacheTypes.ipdlh
vendored
@ -84,6 +84,7 @@ struct CacheResponse
|
||||
PrincipalInfo? principalInfo;
|
||||
uint32_t paddingInfo;
|
||||
int64_t paddingSize;
|
||||
RequestCredentials credentials;
|
||||
};
|
||||
|
||||
struct CacheRequestResponse
|
||||
|
8
dom/cache/DBSchema.cpp
vendored
8
dom/cache/DBSchema.cpp
vendored
@ -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(
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -1012,7 +1012,7 @@ child:
|
||||
nsID aChangeID);
|
||||
|
||||
async GetLayoutHistoryState(MaybeDiscardedBrowsingContext aContext)
|
||||
returns (nsILayoutHistoryState aState);
|
||||
returns (nsILayoutHistoryState aState, Wireframe? aWireframe);
|
||||
|
||||
async DispatchLocationChangeEvent(MaybeDiscardedBrowsingContext aContext);
|
||||
|
||||
|
@ -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 request’s 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 request’s client’s policy container’s embedder policy’s value is not
|
||||
// "credentialless", then return true.
|
||||
if (loadInfo->GetLoadingEmbedderPolicy() !=
|
||||
nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If request’s origin is same origin with request’s current URL’s 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,
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user