Update On Wed Feb 5 19:53:07 CET 2025

This commit is contained in:
github-action[bot] 2025-02-05 19:53:08 +01:00
parent d57eb0346a
commit 139731c359
743 changed files with 14489 additions and 5946 deletions

8
Cargo.lock generated
View file

@ -2544,9 +2544,9 @@ dependencies = [
[[package]]
name = "glean"
version = "63.0.0"
version = "63.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6edc2134eac59587dc100b301eeba2dd473d74d6ecd51d635419a1865cc92496"
checksum = "e2afa6754943cac5243099efd0d26e89cc8e06f1585776ba14ab0c6ee99e1f71"
dependencies = [
"crossbeam-channel",
"glean-core",
@ -2558,9 +2558,9 @@ dependencies = [
[[package]]
name = "glean-core"
version = "63.0.0"
version = "63.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b840b5177e3e102332cfa09bbbe57ae69f939a180d73cb02351b2ddf4cb1fbee"
checksum = "53cd53bb7a3b89b17d3989e95dd808b137ff47c504d1d19f14cb0d820cc2f42e"
dependencies = [
"android_logger",
"bincode",

View file

@ -67,7 +67,7 @@ uniffi_bindgen = "0.28.2"
# Shared across multiple application-services consumers.
rusqlite = "0.31.0"
# Shared across multiple glean consumers.
glean = "=63.0.0"
glean = "=63.1.0"
# Explicitly specify what our profiles use. The opt-level setting here is
# a total fiction; see the setup of MOZ_RUST_DEFAULT_FLAGS for what the

View file

@ -1848,13 +1848,18 @@ TextLeafPoint TextLeafPoint::FindTextAttrsStart(nsDirection aDirection,
return AdjustEndOfLine().FindTextAttrsStart(aDirection, aIncludeOrigin);
}
const bool isRemote = mAcc->IsRemote();
RefPtr<const AccAttributes> lastAttrs =
isRemote ? mAcc->AsRemote()->GetCachedTextAttributes()
: GetTextAttributesLocalAcc();
RefPtr<const AccAttributes> lastAttrs;
if (mAcc->IsText()) {
lastAttrs = isRemote ? mAcc->AsRemote()->GetCachedTextAttributes()
: GetTextAttributesLocalAcc();
}
if (aIncludeOrigin && aDirection == eDirNext && mOffset == 0) {
if (!mAcc->IsText()) {
// Anything other than text breaks an attrs run.
return *this;
}
// Even when searching forward, the only way to know whether the origin is
// the start of a text attrs run is to compare with the previous sibling.
// Anything other than text breaks an attrs run.
TextLeafPoint point;
point.mAcc = mAcc->PrevSibling();
if (!point.mAcc || !point.mAcc->IsText()) {
@ -1909,7 +1914,7 @@ TextLeafPoint TextLeafPoint::FindTextAttrsStart(nsDirection aDirection,
RefPtr<const AccAttributes> attrs =
isRemote ? point.mAcc->AsRemote()->GetCachedTextAttributes()
: point.GetTextAttributesLocalAcc();
if (attrs && lastAttrs && !attrs->Equal(lastAttrs)) {
if (!lastAttrs || (attrs && !attrs->Equal(lastAttrs))) {
// The attributes change here. If we're moving forward, we want to return
// this point.
if (aDirection == eDirNext) {

View file

@ -3514,6 +3514,18 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
// have on-screen cells.
LocalAccessible* prevParentRow = nullptr;
for (nsIFrame* frame : frames) {
if (frame->IsInlineFrame() && !frame->IsPrimaryFrame()) {
// This is a line other than the first line in an inline element. Even
// though there are multiple frames for this element (one per line),
// there is only a single Accessible with bounds encompassing all the
// frames. We don't have any additional information about the
// individual continuation frames in our cache. Thus, we don't want
// this Accessible to appear before leaves on other lines which are
// later in the `frames` array. Otherwise, when hit testing, this
// Accessible will match instead of those leaves. We will add this
// Accessible when we get to its primary frame later.
continue;
}
nsIContent* content = frame->GetContent();
if (!content) {
continue;

View file

@ -487,7 +487,14 @@ bool RemoteAccessible::ContainsPoint(int32_t aX, int32_t aY) {
MOZ_ASSERT(lineEnd >= lineStart);
nsRect lineRect = GetCachedCharRect(lineStart);
if (lineEnd > lineStart) {
lineRect.UnionRect(lineRect, GetCachedCharRect(lineEnd));
nsRect lineEndRect = GetCachedCharRect(lineEnd);
if (lineEndRect.IsEmpty() && lineEnd - 1 > lineStart) {
// The line feed character at the end of a line in pre-formatted text
// doesn't have a useful rect. Use the previous character. Otherwise,
// lineRect won't span the line of text and we'll miss characters.
lineEndRect = GetCachedCharRect(lineEnd - 1);
}
lineRect.UnionRect(lineRect, lineEndRect);
}
if (BoundsWithOffset(Some(lineRect), true).Contains(aX, aY)) {
return true;

View file

@ -464,47 +464,39 @@ NSAttributedString* GeckoTextMarkerRange::AttributedText() const {
: mRange;
nsAutoString text;
RefPtr<AccAttributes> currentRun = GetTextAttributes(range.Start());
Accessible* runAcc = range.Start().mAcc;
for (TextLeafRange segment : range) {
TextLeafPoint start = segment.Start();
TextLeafPoint attributesNext;
TextLeafPoint start = range.Start();
const TextLeafPoint stop = range.End();
RefPtr<AccAttributes> currentRun = GetTextAttributes(start);
Accessible* runAcc = start.mAcc;
do {
TextLeafPoint attributesNext = start.FindTextAttrsStart(eDirNext, false);
if (stop < attributesNext) {
attributesNext = stop;
}
if (start.mAcc->IsMenuPopup() &&
(start.mAcc->State() & states::COLLAPSED)) {
// XXX: Menu collapsed XUL menu popups are in our tree and we need to skip
// them.
start = attributesNext;
continue;
}
do {
if (start.mAcc->IsText()) {
attributesNext = start.FindTextAttrsStart(eDirNext, false);
} else {
// If this segment isn't a text leaf, but another kind of inline element
// like a control, just consider this full segment one "attributes run".
attributesNext = segment.End();
}
if (attributesNext == start) {
// XXX: FindTextAttrsStart should not return the same point.
break;
}
RefPtr<AccAttributes> attributes = GetTextAttributes(start);
if (!currentRun || !attributes || !attributes->Equal(currentRun)) {
// If currentRun is null this is a non-text control and we will
// append a run with no text or attributes, just an AXAttachment
// referencing this accessible.
AppendTextToAttributedString(str, runAcc, text, currentRun);
text.Truncate();
currentRun = attributes;
runAcc = start.mAcc;
}
TextLeafPoint end =
attributesNext < segment.End() ? attributesNext : segment.End();
start.mAcc->AppendTextTo(text, start.mOffset,
end.mOffset - start.mOffset);
start = attributesNext;
} while (attributesNext < segment.End());
}
RefPtr<AccAttributes> attributes = GetTextAttributes(start);
if (!currentRun || !attributes || !attributes->Equal(currentRun)) {
// If currentRun is null this is a non-text control and we will
// append a run with no text or attributes, just an AXAttachment
// referencing this accessible.
AppendTextToAttributedString(str, runAcc, text, currentRun);
text.Truncate();
currentRun = attributes;
runAcc = start.mAcc;
}
for (TextLeafRange segment : TextLeafRange(start, attributesNext)) {
TextLeafPoint segStart = segment.Start();
segStart.mAcc->AppendTextTo(text, segStart.mOffset,
segment.End().mOffset - segStart.mOffset);
}
start = attributesNext;
} while (start != stop);
if (!text.IsEmpty()) {
AppendTextToAttributedString(str, runAcc, text, currentRun);

View file

@ -132,6 +132,48 @@ async function runTests(browser, accDoc) {
wrappedTextLeafFirstMark,
wrappedTextLeafFirstMark.firstChild
);
const wrappedTextNestedInlineP = findAccessibleChildByID(
accDoc,
"wrappedTextNestedInlineP"
);
const wrappedTextNestedInlineEm = findAccessibleChildByID(
accDoc,
"wrappedTextNestedInlineEm"
);
const wrappedTextNestedInlineStrong = findAccessibleChildByID(
accDoc,
"wrappedTextNestedInlineStrong"
);
await hitTest(
browser,
wrappedTextNestedInlineP,
wrappedTextNestedInlineEm,
wrappedTextNestedInlineStrong.firstChild
);
const wrappedTextPre = findAccessibleChildByID(accDoc, "wrappedTextPre");
const wrappedTextPreCode = findAccessibleChildByID(
accDoc,
"wrappedTextPreCode"
);
await hitTest(
browser,
wrappedTextPre,
wrappedTextPreCode,
wrappedTextPreCode.firstChild
);
// hitTest() can only test the first character. We need to test a subsequent
// character for this case.
let [x, y, w] = await getContentBoundsForDOMElm(
browser,
"wrappedTextPreCode"
);
// Use the top center of the element.
x = x + w / 2;
await untilCacheIs(
() => getChildAtPoint(wrappedTextPre, x, y, true),
wrappedTextPreCode.firstChild,
`Wrong deepest child accessible at the point (${x}, ${y}) of wrappedTextPre, sought wrappedTextPreCode leaf`
);
info("Testing image");
const imageP = findAccessibleChildByID(accDoc, "imageP");
@ -192,6 +234,13 @@ addAccessibleTask(
<mark id="wrappedTextLeafFirstMark">a</mark><a href="https://example.com/">b cd</a>
</p>
<p id="wrappedTextNestedInlineP" style="width: 1ch; font-family: monospace;">
<em id="wrappedTextNestedInlineEm"><strong id="wrappedTextNestedInlineStrong">y </strong>z</em>
</p>
<pre id="wrappedTextPre"><code id="wrappedTextPreCode">ab cd
e</pre>
<p id="imageP">
<img id="image" src="http://example.com/a11y/accessible/tests/mochitest/letters.gif">
</p>

View file

@ -533,6 +533,7 @@ function accessibleTask(doc, task, options = {}) {
gBrowser,
"about:blank",
{
allowInheritPrincipal: true,
forceNotRemote: true,
}
);

View file

@ -3,6 +3,7 @@ subsuite = "a11y"
skip-if = [
"os != 'win'",
"headless",
"artifact",
]
support-files = ["head.js"]

View file

@ -3,6 +3,7 @@ subsuite = "a11y"
skip-if = [
"os != 'win'",
"headless",
"artifact",
]
support-files = [
"head.js",

View file

@ -2288,3 +2288,85 @@ line7</textarea>
// The IA2 -> UIA proxy doesn't support GetVisibleRanges.
{ uiaEnabled: true, uiaDisabled: false }
);
/**
* Test the TextRange pattern's FindText method.
*/
addUiaTask(
`<div id="container"><b>abc</b>TEST<div id="inner">def</div>TEST<p>ghi</p></div>`,
async function testTextRangeFromChild() {
await runPython(`
global doc, docText, container, range
doc = getDocUia()
docText = getUiaPattern(doc, "Text")
container = findUiaByDomId(doc, "container")
range = docText.RangeFromChild(container)
`);
// The IA2 -> UIA bridge inserts a space at the end of the text.
if (gIsUiaEnabled) {
is(
await runPython(`range.GetText(-1)`),
`abcTESTdefTESTghi`,
"doc returned correct range for container"
);
}
info("Finding 'abc', searching from the start");
await runPython(`
global subrange
subrange = range.FindText("abc", False, False)
`);
is(await runPython(`subrange.GetText(-1)`), "abc", "range text correct");
info("Finding 'abc', searching from the end");
await runPython(`
global subrange
subrange = range.FindText("abc", True, False)
`);
is(await runPython(`subrange.GetText(-1)`), "abc", "range text correct");
info("Finding 'ghi', searching from the start");
await runPython(`
global subrange
subrange = range.FindText("ghi", False, False)
`);
is(await runPython(`subrange.GetText(-1)`), "ghi", "range text correct");
info("Finding 'ghi', searching from the end");
await runPython(`
global subrange
subrange = range.FindText("ghi", True, False)
`);
is(await runPython(`subrange.GetText(-1)`), "ghi", "range text correct");
info("Finding 'TEST', searching from the start");
await runPython(`
global subrange
subrange = range.FindText("TEST", False, False)
`);
is(await runPython(`subrange.GetText(-1)`), "TEST", "range text correct");
info("Finding 'TEST', searching from the end");
await runPython(`
global subrange2
subrange2 = range.FindText("TEST", True, False)
`);
is(await runPython(`subrange2.GetText(-1)`), "TEST", "range text correct");
ok(
!(await runPython(`subrange.compare(subrange2)`)),
"ranges are not equal"
);
info("Finding 'test', searching from the start, case-sensitive");
await runPython(`
global subrange
subrange = range.FindText("test", False, False)
`);
ok(await runPython(`not subrange`), "range not found");
info("Finding 'test', searching from the start, case-insensitive");
await runPython(`
global subrange
subrange = range.FindText("test", False, True)
`);
is(await runPython(`subrange.GetText(-1)`), "TEST", "range text correct");
},
{ uiaEnabled: true, uiaDisabled: true }
);

View file

@ -28,14 +28,6 @@
<script type="application/javascript">
<![CDATA[
////////////////////////////////////////////////////////////////////////////
// Hacky stuffs
// This is the hacks needed to use a searchbar without browser.js.
var BrowserSearch = {
updateOpenSearchBadge() {}
};
////////////////////////////////////////////////////////////////////////////
// Invokers

View file

@ -58,11 +58,6 @@
gQueue.invoke(); // Will call SimpleTest.finish();
}
// This is the hacks needed to use a searchbar without browser.js.
var BrowserSearch = {
updateOpenSearchBadge() {}
};
SimpleTest.waitForExplicitFinish();
// Register 'test-a11y-search' autocomplete search.

View file

@ -463,7 +463,82 @@ UiaTextRange::FindAttribute(TEXTATTRIBUTEID aAttributeId, VARIANT aVal,
STDMETHODIMP
UiaTextRange::FindText(__RPC__in BSTR aText, BOOL aBackward, BOOL aIgnoreCase,
__RPC__deref_out_opt ITextRangeProvider** aRetVal) {
return E_NOTIMPL;
if (!aRetVal) {
return E_INVALIDARG;
}
*aRetVal = nullptr;
TextLeafRange range = GetRange();
if (!range) {
return CO_E_OBJNOTCONNECTED;
}
MOZ_ASSERT(range.Start() <= range.End(), "Range must be valid to proceed.");
// We can't find anything in an empty range.
if (range.Start() == range.End()) {
return S_OK;
}
// Iterate over the range's leaf segments and append each leaf's text. Keep
// track of the indices in the built string, associating them with the
// Accessible pointer whose text begins at that index.
nsTArray<std::pair<int32_t, Accessible*>> indexToAcc;
nsAutoString rangeText;
for (const TextLeafRange leafSegment : range) {
Accessible* startAcc = leafSegment.Start().mAcc;
MOZ_ASSERT(startAcc, "Start acc of leaf segment was unexpectedly null.");
indexToAcc.EmplaceBack(rangeText.Length(), startAcc);
startAcc->AppendTextTo(rangeText);
}
// Find the search string's start position in the text of the range, ignoring
// case if requested.
const nsDependentString searchStr{aText};
const int32_t startIndex = [&]() {
if (aIgnoreCase) {
ToLowerCase(rangeText);
nsAutoString searchStrLower;
ToLowerCase(searchStr, searchStrLower);
return aBackward ? rangeText.RFind(searchStrLower)
: rangeText.Find(searchStrLower);
} else {
return aBackward ? rangeText.RFind(searchStr) : rangeText.Find(searchStr);
}
}();
if (startIndex == kNotFound) {
return S_OK;
}
const int32_t endIndex = startIndex + searchStr.Length();
// Binary search for the (index, Accessible*) pair where the index is as large
// as possible without exceeding the size of the search index. The associated
// Accessible* is the Accessible for the resulting TextLeafPoint.
auto GetNearestAccLessThanIndex = [&indexToAcc](int32_t aIndex) {
MOZ_ASSERT(aIndex >= 0, "Search index is less than 0.");
auto itr =
std::lower_bound(indexToAcc.begin(), indexToAcc.end(), aIndex,
[](const std::pair<int32_t, Accessible*>& aPair,
int32_t aIndex) { return aPair.first <= aIndex; });
MOZ_ASSERT(itr != indexToAcc.begin(),
"Iterator is unexpectedly at the beginning.");
--itr;
return itr;
};
// Calculate the TextLeafPoint for the start and end of the found text.
auto itr = GetNearestAccLessThanIndex(startIndex);
Accessible* foundTextStart = itr->second;
const int32_t offsetFromStart = startIndex - itr->first;
const TextLeafPoint rangeStart{foundTextStart, offsetFromStart};
itr = GetNearestAccLessThanIndex(endIndex);
Accessible* foundTextEnd = itr->second;
const int32_t offsetFromEndAccStart = endIndex - itr->first;
const TextLeafPoint rangeEnd{foundTextEnd, offsetFromEndAccStart};
TextLeafRange resultRange{rangeStart, rangeEnd};
RefPtr uiaRange = new UiaTextRange(resultRange);
uiaRange.forget(aRetVal);
return S_OK;
}
template <TEXTATTRIBUTEID Attr>

View file

@ -6,6 +6,7 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
OpenSearchManager: "resource:///modules/OpenSearchManager.sys.mjs",
});
let gTestListeners = new Set();
@ -67,7 +68,7 @@ export class LinkHandlerParent extends JSWindowActorParent {
this.notifyTestListeners("SetFailedIcon", aMsg.data);
break;
case "Link:AddSearch":
case "Link:AddSearch": {
if (!gBrowser) {
return;
}
@ -77,10 +78,9 @@ export class LinkHandlerParent extends JSWindowActorParent {
break;
}
if (win.BrowserSearch) {
win.BrowserSearch.addEngine(browser, aMsg.data.engine);
}
lazy.OpenSearchManager.addEngine(browser, aMsg.data.engine);
break;
}
}
}

View file

@ -1171,7 +1171,7 @@ pref("privacy.clearOnShutdown_v2.browsingHistoryAndDownloads", true);
pref("privacy.clearOnShutdown_v2.cookiesAndStorage", true);
pref("privacy.clearOnShutdown_v2.cache", true);
pref("privacy.clearOnShutdown_v2.siteSettings", false);
pref("privacy.clearOnShutdown_v2.formData", false);
pref("privacy.clearOnShutdown_v2.formdata", false);
pref("privacy.cpd.history", true);
pref("privacy.cpd.formdata", true);
@ -1191,13 +1191,13 @@ pref("privacy.clearHistory.browsingHistoryAndDownloads", true);
pref("privacy.clearHistory.cookiesAndStorage", true);
pref("privacy.clearHistory.cache", true);
pref("privacy.clearHistory.siteSettings", false);
pref("privacy.clearHistory.formData", false);
pref("privacy.clearHistory.formdata", false);
pref("privacy.clearSiteData.historyFormDataAndDownloads", false);
pref("privacy.clearSiteData.browsingHistoryAndDownloads", false);
pref("privacy.clearSiteData.cookiesAndStorage", true);
pref("privacy.clearSiteData.cache", true);
pref("privacy.clearSiteData.siteSettings", false);
pref("privacy.clearSiteData.formData", false);
pref("privacy.clearSiteData.formdata", false);
pref("privacy.history.custom", false);

View file

@ -217,7 +217,8 @@ document.addEventListener(
break;
case "context-searchselect": {
let { searchTerms, usePrivate, principal, csp } = event.target;
BrowserSearch.loadSearchFromContext(
SearchUIUtils.loadSearchFromContext(
window,
searchTerms,
usePrivate,
principal,
@ -228,7 +229,8 @@ document.addEventListener(
}
case "context-searchselect-private": {
let { searchTerms, principal, csp } = event.target;
BrowserSearch.loadSearchFromContext(
SearchUIUtils.loadSearchFromContext(
window,
searchTerms,
true,
principal,

View file

@ -198,7 +198,7 @@ var gBrowserInit = {
elem.removeAttribute("skipintoolbarset");
}
}
BrowserSearch.initPlaceHolder();
gURLBar.initPlaceHolder();
// Hack to ensure that the various initial pages favicon is loaded
// instantaneously, to avoid flickering and improve perceived performance.
@ -285,7 +285,6 @@ var gBrowserInit = {
Win10TabletModeUpdater.init();
CombinedStopReload.ensureInitialized();
gPrivateBrowsingUI.init();
BrowserSearch.init();
BrowserPageActions.init();
if (gToolbarKeyNavEnabled) {
ToolbarKeyboardNavigator.init();
@ -430,7 +429,7 @@ var gBrowserInit = {
UpdateUrlbarSearchSplitterState();
BookmarkingUI.init();
BrowserSearch.delayedStartupInit();
gURLBar.delayedStartupInit();
gProtectionsHandler.init();
let safeMode = document.getElementById("helpSafeMode");
@ -1077,8 +1076,6 @@ var gBrowserInit = {
ToolbarKeyboardNavigator.uninit();
}
BrowserSearch.uninit();
NewTabPagePreloading.removePreloadedBrowser(window);
FirefoxViewHandler.uninit();

View file

@ -206,7 +206,7 @@ document.addEventListener(
gProfiles.handleCommand(event);
break;
case "Tools:Search":
BrowserSearch.webSearch();
SearchUIUtils.webSearch(window);
break;
case "Tools:Downloads":
BrowserCommands.downloadsUI();

View file

@ -21,7 +21,6 @@ ChromeUtils.defineESModuleGetters(this, {
AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
AboutReaderParent: "resource:///actors/AboutReaderParent.sys.mjs",
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
BrowserSearchTelemetry: "resource:///modules/BrowserSearchTelemetry.sys.mjs",
BrowserTelemetryUtils: "resource://gre/modules/BrowserTelemetryUtils.sys.mjs",
BrowserUIUtils: "resource:///modules/BrowserUIUtils.sys.mjs",
BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.sys.mjs",
@ -50,6 +49,7 @@ ChromeUtils.defineESModuleGetters(this, {
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
nsContextMenu: "chrome://browser/content/nsContextMenu.sys.mjs",
OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.sys.mjs",
OpenSearchManager: "resource:///modules/OpenSearchManager.sys.mjs",
PageActions: "resource:///modules/PageActions.sys.mjs",
PageThumbs: "resource://gre/modules/PageThumbs.sys.mjs",
PanelMultiView: "resource:///modules/PanelMultiView.sys.mjs",
@ -410,13 +410,17 @@ ChromeUtils.defineLazyGetter(this, "ReferrerInfo", () =>
// High priority notification bars shown at the top of the window.
ChromeUtils.defineLazyGetter(this, "gNotificationBox", () => {
let securityDelayMS = Services.prefs.getIntPref(
"security.notification_enable_delay"
);
return new MozElements.NotificationBox(element => {
element.classList.add("global-notificationbox");
element.setAttribute("notificationside", "top");
element.setAttribute("prepend-notifications", true);
// We want this before the tab notifications.
document.getElementById("notifications-toolbar").prepend(element);
});
}, securityDelayMS);
});
ChromeUtils.defineLazyGetter(this, "InlineSpellCheckerUI", () => {
@ -1291,7 +1295,7 @@ function HandleAppCommandEvent(evt) {
}
break;
case "Search":
BrowserSearch.webSearch();
SearchUIUtils.webSearch(window);
break;
case "Bookmarks":
SidebarController.toggle("viewBookmarksSidebar");
@ -1835,608 +1839,6 @@ var newWindowButtonObserver = {
},
};
const BrowserSearch = {
_searchInitComplete: false,
init() {
Services.obs.addObserver(this, "browser-search-engine-modified");
},
delayedStartupInit() {
// Asynchronously initialize the search service if necessary, to get the
// current engine for working out the placeholder.
this._updateURLBarPlaceholderFromDefaultEngine(
PrivateBrowsingUtils.isWindowPrivate(window),
// Delay the update for this until so that we don't change it while
// the user is looking at it / isn't expecting it.
true
).then(() => {
this._searchInitComplete = true;
});
},
uninit() {
Services.obs.removeObserver(this, "browser-search-engine-modified");
},
observe(engine, topic, data) {
// There are two kinds of search engine objects, nsISearchEngine objects and
// plain { uri, title, icon } objects. `engine` in this method is the
// former. The browser.engines and browser.hiddenEngines arrays are the
// latter, and they're the engines offered by the the page in the browser.
//
// The two types of engines are currently related by their titles/names,
// although that may change; see bug 335102.
let engineName = engine.wrappedJSObject.name;
switch (data) {
case "engine-removed":
// An engine was removed from the search service. If a page is offering
// the engine, then the engine needs to be added back to the corresponding
// browser's offered engines.
this._addMaybeOfferedEngine(engineName);
break;
case "engine-added":
// An engine was added to the search service. If a page is offering the
// engine, then the engine needs to be removed from the corresponding
// browser's offered engines.
this._removeMaybeOfferedEngine(engineName);
break;
case "engine-default":
if (
this._searchInitComplete &&
!PrivateBrowsingUtils.isWindowPrivate(window)
) {
this._updateURLBarPlaceholder(engineName, false);
}
break;
case "engine-default-private":
if (
this._searchInitComplete &&
PrivateBrowsingUtils.isWindowPrivate(window)
) {
this._updateURLBarPlaceholder(engineName, true);
}
break;
}
},
_addMaybeOfferedEngine(engineName) {
let selectedBrowserOffersEngine = false;
for (let browser of gBrowser.browsers) {
for (let i = 0; i < (browser.hiddenEngines || []).length; i++) {
if (browser.hiddenEngines[i].title == engineName) {
if (!browser.engines) {
browser.engines = [];
}
browser.engines.push(browser.hiddenEngines[i]);
browser.hiddenEngines.splice(i, 1);
if (browser == gBrowser.selectedBrowser) {
selectedBrowserOffersEngine = true;
}
break;
}
}
}
if (selectedBrowserOffersEngine) {
this.updateOpenSearchBadge();
}
},
_removeMaybeOfferedEngine(engineName) {
let selectedBrowserOffersEngine = false;
for (let browser of gBrowser.browsers) {
for (let i = 0; i < (browser.engines || []).length; i++) {
if (browser.engines[i].title == engineName) {
if (!browser.hiddenEngines) {
browser.hiddenEngines = [];
}
browser.hiddenEngines.push(browser.engines[i]);
browser.engines.splice(i, 1);
if (browser == gBrowser.selectedBrowser) {
selectedBrowserOffersEngine = true;
}
break;
}
}
}
if (selectedBrowserOffersEngine) {
this.updateOpenSearchBadge();
}
},
/**
* Initializes the urlbar placeholder to the pre-saved engine name. We do this
* via a preference, to avoid needing to synchronously init the search service.
*
* This should be called around the time of DOMContentLoaded, so that it is
* initialized quickly before the user sees anything.
*
* Note: If the preference doesn't exist, we don't do anything as the default
* placeholder is a string which doesn't have the engine name; however, this
* can be overridden using the `force` parameter.
*
* @param {Boolean} force If true and the preference doesn't exist, the
* placeholder will be set to the default version
* without an engine name ("Search or enter address").
*/
initPlaceHolder(force = false) {
const prefName =
"browser.urlbar.placeholderName" +
(PrivateBrowsingUtils.isWindowPrivate(window) ? ".private" : "");
let engineName = Services.prefs.getStringPref(prefName, "");
if (engineName || force) {
// We can do this directly, since we know we're at DOMContentLoaded.
this._setURLBarPlaceholder(engineName);
}
},
/**
* This is a wrapper around '_updateURLBarPlaceholder' that uses the
* appropriate default engine to get the engine name.
*
* @param {Boolean} isPrivate Set to true if this is a private window.
* @param {Boolean} [delayUpdate] Set to true, to delay update until the
* placeholder is not displayed.
*/
async _updateURLBarPlaceholderFromDefaultEngine(
isPrivate,
delayUpdate = false
) {
const getDefault = isPrivate
? Services.search.getDefaultPrivate
: Services.search.getDefault;
let defaultEngine = await getDefault();
if (!this._searchInitComplete) {
// If we haven't finished initialising, ensure the placeholder
// preference is set for the next startup.
SearchUIUtils.updatePlaceholderNamePreference(defaultEngine, isPrivate);
}
this._updateURLBarPlaceholder(defaultEngine.name, isPrivate, delayUpdate);
},
/**
* Updates the URLBar placeholder for the specified engine, delaying the
* update if required. This also saves the current engine name in preferences
* for the next restart.
*
* Note: The engine name will only be displayed for built-in engines, as we
* know they should have short names.
*
* @param {String} engineName The search engine name to use for the update.
* @param {Boolean} isPrivate Set to true if this is a private window.
* @param {Boolean} [delayUpdate] Set to true, to delay update until the
* placeholder is not displayed.
*/
_updateURLBarPlaceholder(engineName, isPrivate, delayUpdate = false) {
if (!engineName) {
throw new Error("Expected an engineName to be specified");
}
const engine = Services.search.getEngineByName(engineName);
if (!engine.isAppProvided) {
// Set the engine name to an empty string for non-default engines, which'll
// make sure we display the default placeholder string.
engineName = "";
}
// Only delay if requested, and we're not displaying text in the URL bar
// currently.
if (delayUpdate && !gURLBar.value) {
// Delays changing the URL Bar placeholder until the user is not going to be
// seeing it, e.g. when there is a value entered in the bar, or if there is
// a tab switch to a tab which has a url loaded. We delay the update until
// the user is out of search mode since an alternative placeholder is used
// in search mode.
let placeholderUpdateListener = () => {
if (gURLBar.value && !gURLBar.searchMode) {
// By the time the user has switched, they may have changed the engine
// again, so we need to call this function again but with the
// new engine name.
// No need to await for this to finish, we're in a listener here anyway.
this._updateURLBarPlaceholderFromDefaultEngine(isPrivate, false);
gURLBar.removeEventListener("input", placeholderUpdateListener);
gBrowser.tabContainer.removeEventListener(
"TabSelect",
placeholderUpdateListener
);
}
};
gURLBar.addEventListener("input", placeholderUpdateListener);
gBrowser.tabContainer.addEventListener(
"TabSelect",
placeholderUpdateListener
);
} else if (!gURLBar.searchMode) {
this._setURLBarPlaceholder(engineName);
}
},
/**
* Sets the URLBar placeholder to either something based on the engine name,
* or the default placeholder.
*
* @param {String} name The name of the engine to use, an empty string if to
* use the default placeholder.
*/
_setURLBarPlaceholder(name) {
document.l10n.setAttributes(
gURLBar.inputField,
name ? "urlbar-placeholder-with-name" : "urlbar-placeholder",
name ? { name } : undefined
);
},
addEngine(browser, engine) {
if (!this._searchInitComplete) {
// We haven't finished initialising search yet. This means we can't
// call getEngineByName here. Since this is only on start-up and unlikely
// to happen in the normal case, we'll just return early rather than
// trying to handle it asynchronously.
return;
}
// Check to see whether we've already added an engine with this title
if (browser.engines) {
if (browser.engines.some(e => e.title == engine.title)) {
return;
}
}
var hidden = false;
// If this engine (identified by title) is already in the list, add it
// to the list of hidden engines rather than to the main list.
if (Services.search.getEngineByName(engine.title)) {
hidden = true;
}
var engines = (hidden ? browser.hiddenEngines : browser.engines) || [];
engines.push({
uri: engine.href,
title: engine.title,
get icon() {
return browser.mIconURL;
},
});
if (hidden) {
browser.hiddenEngines = engines;
} else {
browser.engines = engines;
if (browser == gBrowser.selectedBrowser) {
this.updateOpenSearchBadge();
}
}
},
/**
* Update the browser UI to show whether or not additional engines are
* available when a page is loaded or the user switches tabs to a page that
* has search engines.
*/
updateOpenSearchBadge() {
gURLBar.addSearchEngineHelper.setEnginesFromBrowser(
gBrowser.selectedBrowser
);
var searchBar = this.searchBar;
if (!searchBar) {
return;
}
var engines = gBrowser.selectedBrowser.engines;
if (engines && engines.length) {
searchBar.setAttribute("addengines", "true");
} else {
searchBar.removeAttribute("addengines");
}
},
/**
* Focuses the search bar if present on the toolbar, or the address bar,
* putting it in search mode. Will do so in an existing non-popup browser
* window or open a new one if necessary.
*/
webSearch: function BrowserSearch_webSearch() {
if (
window.location.href != AppConstants.BROWSER_CHROME_URL ||
gURLBar.readOnly
) {
let win = URILoadingHelper.getTopWin(window, { skipPopups: true });
if (win) {
// If there's an open browser window, it should handle this command
win.focus();
win.BrowserSearch.webSearch();
} else {
// If there are no open browser windows, open a new one
var observer = function (subject) {
if (subject == win) {
BrowserSearch.webSearch();
Services.obs.removeObserver(
observer,
"browser-delayed-startup-finished"
);
}
};
win = window.openDialog(
AppConstants.BROWSER_CHROME_URL,
"_blank",
"chrome,all,dialog=no",
"about:blank"
);
Services.obs.addObserver(observer, "browser-delayed-startup-finished");
}
return;
}
let focusUrlBarIfSearchFieldIsNotActive = function (aSearchBar) {
if (!aSearchBar || document.activeElement != aSearchBar.textbox) {
// Limit the results to search suggestions, like the search bar.
gURLBar.searchModeShortcut();
}
};
let searchBar = this.searchBar;
let placement = CustomizableUI.getPlacementOfWidget("search-container");
let focusSearchBar = () => {
searchBar = this.searchBar;
searchBar.select();
focusUrlBarIfSearchFieldIsNotActive(searchBar);
};
if (
placement &&
searchBar &&
((searchBar.parentNode.getAttribute("overflowedItem") == "true" &&
placement.area == CustomizableUI.AREA_NAVBAR) ||
placement.area == CustomizableUI.AREA_FIXED_OVERFLOW_PANEL)
) {
let navBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
navBar.overflowable.show().then(focusSearchBar);
return;
}
if (searchBar) {
if (window.fullScreen) {
FullScreen.showNavToolbox();
}
searchBar.select();
}
focusUrlBarIfSearchFieldIsNotActive(searchBar);
},
/**
* Loads a search results page, given a set of search terms. Uses the current
* engine if the search bar is visible, or the default engine otherwise.
*
* @param searchText
* The search terms to use for the search.
* @param where
* String indicating where the search should load. Most commonly used
* are 'tab' or 'window', defaults to 'current'.
* @param usePrivate
* Whether to use the Private Browsing mode default search engine.
* Defaults to `false`.
* @param purpose [optional]
* A string meant to indicate the context of the search request. This
* allows the search service to provide a different nsISearchSubmission
* depending on e.g. where the search is triggered in the UI.
* @param triggeringPrincipal
* The principal to use for a new window or tab.
* @param csp
* The content security policy to use for a new window or tab.
* @param inBackground [optional]
* Set to true for the tab to be loaded in the background, default false.
* @param engine [optional]
* The search engine to use for the search.
* @param tab [optional]
* The tab to show the search result.
*
* @return engine The search engine used to perform a search, or null if no
* search was performed.
*/
async _loadSearch(
searchText,
where,
usePrivate,
purpose,
triggeringPrincipal,
csp,
inBackground = false,
engine = null,
tab = null
) {
if (!triggeringPrincipal) {
throw new Error(
"Required argument triggeringPrincipal missing within _loadSearch"
);
}
if (!engine) {
engine = usePrivate
? await Services.search.getDefaultPrivate()
: await Services.search.getDefault();
}
let submission = engine.getSubmission(searchText, null, purpose); // HTML response
// getSubmission can return null if the engine doesn't have a URL
// with a text/html response type. This is unlikely (since
// SearchService._addEngineToStore() should fail for such an engine),
// but let's be on the safe side.
if (!submission) {
return null;
}
openLinkIn(submission.uri.spec, where || "current", {
private: usePrivate && !PrivateBrowsingUtils.isWindowPrivate(window),
postData: submission.postData,
inBackground,
relatedToCurrent: true,
triggeringPrincipal,
csp,
targetBrowser: tab?.linkedBrowser,
globalHistoryOptions: {
triggeringSearchEngine: engine.name,
},
});
return { engine, url: submission.uri };
},
/**
* Perform a search initiated from the context menu.
*
* This should only be called from the context menu. See
* BrowserSearch.loadSearch for the preferred API.
*/
async loadSearchFromContext(
terms,
usePrivate,
triggeringPrincipal,
csp,
event
) {
event = BrowserUtils.getRootEvent(event);
let where = BrowserUtils.whereToOpenLink(event);
if (where == "current") {
// override: historically search opens in new tab
where = "tab";
}
if (usePrivate && !PrivateBrowsingUtils.isWindowPrivate(window)) {
where = "window";
}
let inBackground = Services.prefs.getBoolPref(
"browser.search.context.loadInBackground"
);
if (event.button == 1 || event.ctrlKey) {
inBackground = !inBackground;
}
let { engine } = await BrowserSearch._loadSearch(
terms,
where,
usePrivate,
"contextmenu",
Services.scriptSecurityManager.createNullPrincipal(
triggeringPrincipal.originAttributes
),
csp,
inBackground
);
if (engine) {
BrowserSearchTelemetry.recordSearch(
gBrowser.selectedBrowser,
engine,
"contextmenu"
);
}
},
/**
* Perform a search initiated from the command line.
*/
async loadSearchFromCommandLine(terms, usePrivate, triggeringPrincipal, csp) {
let { engine } = await BrowserSearch._loadSearch(
terms,
"current",
usePrivate,
"system",
triggeringPrincipal,
csp
);
if (engine) {
BrowserSearchTelemetry.recordSearch(
gBrowser.selectedBrowser,
engine,
"system"
);
}
},
/**
* Perform a search initiated from an extension.
*/
async loadSearchFromExtension({
query,
engine,
where,
tab,
triggeringPrincipal,
}) {
const result = await BrowserSearch._loadSearch(
query,
where,
PrivateBrowsingUtils.isWindowPrivate(window),
"webextension",
triggeringPrincipal,
null,
false,
engine,
tab
);
BrowserSearchTelemetry.recordSearch(
gBrowser.selectedBrowser,
result.engine,
"webextension"
);
},
/**
* Returns the search bar element if it is present in the toolbar, null otherwise.
*/
get searchBar() {
return document.getElementById("searchbar");
},
/**
* Infobar to notify the user's search engine has been removed
* and replaced with an application default search engine.
*
* @param {string} oldEngine
* name of the engine to be moved and replaced.
* @param {string} newEngine
* name of the application default engine to replaced the removed engine.
*/
async removalOfSearchEngineNotificationBox(oldEngine, newEngine) {
let buttons = [
{
"l10n-id": "remove-search-engine-button",
primary: true,
callback() {
const notificationBox = gNotificationBox.getNotificationWithValue(
"search-engine-removal"
);
gNotificationBox.removeNotification(notificationBox);
},
},
{
supportPage: "search-engine-removal",
},
];
await gNotificationBox.appendNotification(
"search-engine-removal",
{
label: {
"l10n-id": "removed-search-engine-message2",
"l10n-args": { oldEngine, newEngine },
},
priority: gNotificationBox.PRIORITY_SYSTEM,
},
buttons
);
// Update engine name in the placeholder to the new default engine name.
this._updateURLBarPlaceholderFromDefaultEngine(
PrivateBrowsingUtils.isWindowPrivate(window),
false
).catch(console.error);
},
};
XPCOMUtils.defineConstant(this, "BrowserSearch", BrowserSearch);
function CreateContainerTabMenu(event) {
// Do not open context menus within menus.
// Note that triggerNode is null if we're opened by long press.
@ -3195,8 +2597,7 @@ var XULBrowserWindow = {
aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK
) {
if (aRequest && aWebProgress.isTopLevel) {
// clear out search-engine data
browser.engines = null;
OpenSearchManager.clearEngines(browser);
}
this.isBusy = true;
@ -3598,7 +2999,7 @@ var XULBrowserWindow = {
},
asyncUpdateUI() {
BrowserSearch.updateOpenSearchBadge();
OpenSearchManager.updateOpenSearchBadge(window);
},
onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
@ -4116,7 +3517,7 @@ var TabsProgressListener = {
if (browser == gBrowser.selectedBrowser) {
// If the "Add Search Engine" page action is in the urlbar, its image
// needs to be set to the new icon, so call updateOpenSearchBadge.
BrowserSearch.updateOpenSearchBadge();
OpenSearchManager.updateOpenSearchBadge(window);
}
},
};
@ -4778,7 +4179,7 @@ var gUIDensity = {
}
gBrowser.tabContainer.uiDensityChanged();
gURLBar.updateLayoutBreakout();
gURLBar.uiDensityChanged();
},
};

View file

@ -48,7 +48,7 @@
"openHomeDialog",
"newTabButtonObserver",
"newWindowButtonObserver",
"BrowserSearch",
"OpenSearchManager",
"CreateContainerTabMenu",
"FillHistoryMenu",
"toOpenWindowByType",
@ -112,7 +112,6 @@
"AboutNewTab",
"AboutReaderParent",
"AddonManager",
"BrowserSearchTelemetry",
"BrowserTelemetryUtils",
"BrowserUIUtils",
"BrowserUsageTelemetry",
@ -268,6 +267,5 @@
"gFindBar",
"gFindBarInitialized",
"gFindBarPromise",
"BrowserSearch",
"SelectableProfileService"
]

View file

@ -143,31 +143,6 @@
// may be leaking things because they will never be destroyed after.
window.addEventListener("DOMContentLoaded",
gBrowserInit.onDOMContentLoaded.bind(gBrowserInit), { once: true });
document.addEventListener("securitypolicyviolation", event => {
// Sanitize the URLs and the sample.
let source = ChromeUtils.sanitizeTelemetryFileURL(event.sourceFile);
let blocked = ChromeUtils.sanitizeTelemetryFileURL(event.blockedURI);
let sourceType = source.fileNameType;
let sample =
sourceType == "chromeuri" ||
sourceType == "resourceuri" ||
sourceType == "abouturi"
? event.sample
: "";
let isInline = event.blockedURI == "inline";
Glean.security.cspViolationBrowser.record({
directive: event.effectiveDirective,
sourcetype: sourceType,
sourcedetails: source.fileNameDetails || "",
blockeduritype: isInline ? "inline" : blocked.fileNameType,
blockeduridetails: isInline ? "" : blocked.fileNameDetails || "",
linenumber: event.lineNumber,
columnnumber: event.columnNumber,
sample,
});
});
</script>
</head>
<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

View file

@ -491,6 +491,15 @@ document.addEventListener(
event.target.querySelector(
"#open-tab-group-context-menu_moveToThisWindow"
).disabled = tabGroupIsInThisWindow;
// Disable "Move Group to New Window" menu option for tab groups
// that are the only things in their respective window.
let groupAloneInWindow =
tabGroup.tabs.length ==
tabGroup.ownerGlobal.gBrowser.openTabs.length;
event.target.querySelector(
"#open-tab-group-context-menu_moveToNewWindow"
).disabled = groupAloneInWindow;
}
});

View file

@ -42,18 +42,18 @@ Preferences.addAll([
{ id: "privacy.clearHistory.cookiesAndStorage", type: "bool" },
{ id: "privacy.clearHistory.cache", type: "bool" },
{ id: "privacy.clearHistory.siteSettings", type: "bool" },
{ id: "privacy.clearHistory.formData", type: "bool" },
{ id: "privacy.clearHistory.formdata", type: "bool" },
{ id: "privacy.clearSiteData.browsingHistoryAndDownloads", type: "bool" },
{ id: "privacy.clearSiteData.cookiesAndStorage", type: "bool" },
{ id: "privacy.clearSiteData.cache", type: "bool" },
{ id: "privacy.clearSiteData.siteSettings", type: "bool" },
{ id: "privacy.clearSiteData.formData", type: "bool" },
{ id: "privacy.clearSiteData.formdata", type: "bool" },
{
id: "privacy.clearOnShutdown_v2.browsingHistoryAndDownloads",
type: "bool",
},
{ id: "privacy.clearOnShutdown.formdata", type: "bool" },
{ id: "privacy.clearOnShutdown_v2.formData", type: "bool" },
{ id: "privacy.clearOnShutdown_v2.formdata", type: "bool" },
{ id: "privacy.clearOnShutdown.downloads", type: "bool" },
{ id: "privacy.clearOnShutdown_v2.downloads", type: "bool" },
{ id: "privacy.clearOnShutdown.cookies", type: "bool" },
@ -584,7 +584,7 @@ var gSanitizePromptDialog = {
cookies_and_storage: selectedOptions.includes("cookiesAndStorage"),
cache: selectedOptions.includes("cache"),
site_settings: selectedOptions.includes("siteSettings"),
form_data: selectedOptions.includes("formData"),
form_data: selectedOptions.includes("formdata"),
});
}
// if the dialog was just opened, just report which context it was opened in

View file

@ -160,7 +160,7 @@
class="clearingItemCheckbox"
data-l10n-id="item-formdata-prefs"
aria-describedby="formdata-description"
preference="privacy.clearHistory.formData"
preference="privacy.clearHistory.formdata"
id="formdata"
/>
<description
@ -241,7 +241,7 @@
class="clearingItemCheckbox"
data-l10n-id="item-formdata-prefs"
aria-describedby="formdata-description"
preference="privacy.clearSiteData.formData"
preference="privacy.clearSiteData.formdata"
id="formdata"
/>
<description
@ -322,7 +322,7 @@
class="clearingItemCheckbox"
data-l10n-id="item-formdata-prefs"
aria-describedby="formdata-description"
preference="privacy.clearOnShutdown_v2.formData"
preference="privacy.clearOnShutdown_v2.formdata"
id="formdata"
/>
<description

View file

@ -25,7 +25,7 @@ add_task(async function () {
await BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser);
let doc = window.document;
let searchInput = BrowserSearch.searchBar.textbox;
let searchInput = doc.getElementById("searchbar").textbox;
isnot(
searchInput,
doc.activeElement,

View file

@ -1,3 +1,10 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
ChromeUtils.defineESModuleGetters(this, {
OpenSearchManager: "resource:///modules/OpenSearchManager.sys.mjs",
});
function test() {
waitForExplicitFinish();
let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(
@ -11,8 +18,9 @@ function test() {
true
).then(() => {
executeSoon(function () {
ok(
!tab.linkedBrowser.engines,
Assert.equal(
OpenSearchManager.getEngines(tab.linkedBrowser).length,
0,
"the subframe's search engine wasn't detected"
);

View file

@ -301,7 +301,7 @@ add_task(async function testFormData_historyFalse() {
await SpecialPowers.pushPrefEnv({
set: [
["privacy.clearOnShutdown_v2.historyFormDataAndDownloads", false],
["privacy.clearOnShutdown_v2.formData", true],
["privacy.clearOnShutdown_v2.formdata", true],
["privacy.sanitize.clearOnShutdown.hasMigratedToNewPrefs3", false],
],
});
@ -315,7 +315,7 @@ add_task(async function testFormData_historyFalse() {
"historyFormDataAndDownloads should still be false"
);
ok(
Services.prefs.getBoolPref("privacy.clearOnShutdown_v2.formData"),
Services.prefs.getBoolPref("privacy.clearOnShutdown_v2.formdata"),
"old history pref should be set to true"
);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Before After
Before After

View file

@ -14,3 +14,5 @@ category browser-window-delayed-startup resource:///modules/ContentAnalysis.sys.
category browser-window-delayed-startup resource:///modules/HomePage.sys.mjs HomePage.delayedStartup
category browser-window-delayed-startup resource:///modules/ReportBrokenSite.sys.mjs ReportBrokenSite.init
category browser-window-delayed-startup resource:///modules/SearchUIUtils.sys.mjs SearchUIUtils.init
category search-service-notification resource:///modules/SearchUIUtils.sys.mjs SearchUIUtils.showSearchServiceNotification

View file

@ -16,6 +16,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
LaterRun: "resource:///modules/LaterRun.sys.mjs",
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
SearchUIUtils: "resource:///modules/SearchUIUtils.sys.mjs",
SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
ShellService: "resource:///modules/ShellService.sys.mjs",
SpecialMessageActions:
@ -419,7 +420,8 @@ async function doSearch(searchTerm, cmdLine) {
}, "browser-delayed-startup-finished");
});
win.BrowserSearch.loadSearchFromCommandLine(
lazy.SearchUIUtils.loadSearchFromCommandLine(
win,
searchTerm,
lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode ||
lazy.PrivateBrowsingUtils.isWindowPrivate(win),
@ -1317,6 +1319,7 @@ nsDefaultCommandLineHandler.prototype = {
if (AppConstants.platform == "win") {
// Windows itself does disk I/O when the notification service is
// initialized, so make sure that is lazy.
// eslint-disable-next-line no-constant-condition
while (true) {
let tag = cmdLine.handleFlagWithParam("notification-windowsTag", false);
if (!tag) {

View file

@ -17,12 +17,12 @@ add_task(async function checkSearchBarPresent() {
);
Assert.ok(
BrowserSearch.searchBar,
document.getElementById("searchbar"),
"Search bar should be present in the Nav bar"
);
SearchWidgetTracker._updateSearchBarVisibilityBasedOnUsage();
Assert.ok(
!BrowserSearch.searchBar,
!document.getElementById("searchbar"),
"Search bar should not be present in the Nav bar"
);
Assert.ok(

View file

@ -509,12 +509,13 @@ export var Policies = {
ContentAnalysis: {
onBeforeAddons(manager, param) {
if ("PipePathName" in param) {
setAndLockPref(
"browser.contentanalysis.pipe_path_name",
param.PipePathName
);
}
// For security reasons, all of the Content Analysis related prefs should be locked in
// this method, even if the values aren't specified in Enterprise Policies.
setPrefIfPresentAndLock(
param,
"PipePathName",
"browser.contentanalysis.pipe_path_name"
);
if ("AgentTimeout" in param) {
if (!Number.isInteger(param.AgentTimeout)) {
lazy.log.error(
@ -526,28 +527,29 @@ export var Policies = {
param.AgentTimeout
);
}
} else {
Services.prefs.lockPref("browser.contentanalysis.agent_timeout");
}
if ("AllowUrlRegexList" in param) {
setAndLockPref(
"browser.contentanalysis.allow_url_regex_list",
param.AllowUrlRegexList
);
}
if ("DenyUrlRegexList" in param) {
setAndLockPref(
"browser.contentanalysis.deny_url_regex_list",
param.DenyUrlRegexList
);
}
if ("AgentName" in param) {
setAndLockPref("browser.contentanalysis.agent_name", param.AgentName);
}
if ("ClientSignature" in param) {
setAndLockPref(
"browser.contentanalysis.client_signature",
param.ClientSignature
);
}
setPrefIfPresentAndLock(
param,
"AllowUrlRegexList",
"browser.contentanalysis.allow_url_regex_list"
);
setPrefIfPresentAndLock(
param,
"DenyUrlRegexList",
"browser.contentanalysis.deny_url_regex_list"
);
setPrefIfPresentAndLock(
param,
"AgentName",
"browser.contentanalysis.agent_name"
);
setPrefIfPresentAndLock(
param,
"ClientSignature",
"browser.contentanalysis.client_signature"
);
if ("DefaultResult" in param) {
if (
!Number.isInteger(param.DefaultResult) ||
@ -563,6 +565,8 @@ export var Policies = {
param.DefaultResult
);
}
} else {
Services.prefs.lockPref("browser.contentanalysis.default_result");
}
let boolPrefs = [
["IsPerUser", "is_per_user"],
@ -575,6 +579,8 @@ export var Policies = {
`browser.contentanalysis.${pref[1]}`,
!!param[pref[0]]
);
} else {
Services.prefs.lockPref(`browser.contentanalysis.${pref[1]}`);
}
}
let interceptionPointPrefs = [
@ -585,8 +591,6 @@ export var Policies = {
];
if ("InterceptionPoints" in param) {
for (let pref of interceptionPointPrefs) {
// Need to set and lock this value even if the enterprise
// policy isn't set so users can't change it
let value = true;
if (pref[0] in param.InterceptionPoints) {
if ("Enabled" in param.InterceptionPoints[pref[0]]) {
@ -598,6 +602,12 @@ export var Policies = {
value
);
}
} else {
for (let pref of interceptionPointPrefs) {
Services.prefs.lockPref(
`browser.contentanalysis.interception_point.${pref[1]}.enabled`
);
}
}
if ("Enabled" in param) {
let enabled = !!param.Enabled;
@ -606,6 +616,10 @@ export var Policies = {
Ci.nsIContentAnalysis
);
ca.isSetByEnterprisePolicy = true;
} else {
// Probably not strictly necessary, but let's lock everything
// to be consistent.
Services.prefs.lockPref("browser.contentanalysis.enabled");
}
},
},
@ -2652,6 +2666,28 @@ export function setAndLockPref(prefName, prefValue) {
PoliciesUtils.setDefaultPref(prefName, prefValue, true);
}
/**
*
* setPrefIfPresentAndLock
*
* Sets the pref to the value param[paramKey] if that exists. Either
* way, the pref is locked.
*
* @param {object} param
* Object with pref values
* @param {string} paramKey
* The key to look up the value in param
* @param {string} prefName
* The pref to be changed
*/
function setPrefIfPresentAndLock(param, paramKey, prefName) {
if (paramKey in param) {
setAndLockPref(prefName, param[paramKey]);
} else {
Services.prefs.lockPref(prefName);
}
}
/**
* setDefaultPref
*

View file

@ -19,7 +19,7 @@ add_task(async function test_setup() {
// |shouldWork| should be true if opensearch is expected to work and false if
// it is not.
async function test_opensearch(shouldWork) {
let searchBar = BrowserSearch.searchBar;
let searchBar = document.getElementById("searchbar");
let rootDir = getRootDirectory(gTestPath);
let tab = await BrowserTestUtils.openNewForegroundTab(

View file

@ -7,6 +7,10 @@
"use strict";
ChromeUtils.defineESModuleGetters(this, {
SearchUIUtils: "resource:///modules/SearchUIUtils.sys.mjs",
});
var { ExtensionError } = ExtensionUtils;
const dispositionMap = {
@ -86,7 +90,8 @@ this.search = class extends ExtensionAPI {
defaultDisposition: "NEW_TAB",
});
await windowTracker.topWindow.BrowserSearch.loadSearchFromExtension({
await SearchUIUtils.loadSearchFromExtension({
window: windowTracker.topWindow,
query: searchProperties.query,
where,
engine,
@ -104,7 +109,8 @@ this.search = class extends ExtensionAPI {
defaultDisposition: "CURRENT_TAB",
});
await windowTracker.topWindow.BrowserSearch.loadSearchFromExtension({
await SearchUIUtils.loadSearchFromExtension({
window: windowTracker.topWindow,
query: queryProperties.text,
where,
tab,

View file

@ -10,7 +10,7 @@
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
data-l10n-id="bookmarks-sidebar-content"
csp="default-src chrome:; style-src-elem chrome: 'unsafe-inline';">
csp="default-src chrome:; img-src chrome: data:; style-src-elem chrome: 'unsafe-inline';">
<script src="chrome://browser/content/places/bookmarksSidebar.js"/>
<script src="chrome://global/content/globalOverlay.js"/>

View file

@ -19,7 +19,7 @@
#endif
toggletoolbar="true"
persist="width height screenX screenY sizemode"
csp="default-src chrome:; img-src moz-icon:; style-src 'unsafe-inline';">
csp="default-src chrome:; img-src chrome: moz-icon: data:; style-src 'unsafe-inline';">
<linkset>
<html:link

View file

@ -55,7 +55,7 @@ add_task(async function test_execute() {
true
);
Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "cookies", true);
Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "formData", true);
Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "formdata", true);
Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "sessions", true);
Services.prefs.setBoolPref(
Sanitizer.PREF_SHUTDOWN_BRANCH + "siteSettings",

View file

@ -9,6 +9,7 @@
data-l10n-id="add-engine-window2"
data-l10n-attrs="title, style"
persist="width height"
csp="default-src chrome:; style-src chrome: 'unsafe-inline';"
>
<dialog
buttons="accept,cancel"

View file

@ -17,11 +17,28 @@ var gDoHExceptionsManager = {
this._btnAddException = document.getElementById("btnAddException");
this._removeButton = document.getElementById("removeException");
this._removeAllButton = document.getElementById("removeAllExceptions");
this._list = document.getElementById("permissionsBox");
this._list.addEventListener("keypress", event =>
this.onListBoxKeyPress(event)
);
this._list.addEventListener("select", () => this.onListBoxSelect());
this._urlField = document.getElementById("url");
this.onExceptionInput();
this._urlField.addEventListener("input", () => this.onExceptionInput());
this._urlField.addEventListener("keypress", event =>
this.onExceptionKeyPress(event)
);
document
.getElementById("siteCol")
.addEventListener("click", event =>
this.buildExceptionList(event.target)
);
document.addEventListener("command", this);
this.onExceptionInput();
this._loadExceptions();
this.buildExceptionList();
@ -36,6 +53,24 @@ var gDoHExceptionsManager = {
this._urlField.disabled = this._prefLocked;
},
handleEvent(event) {
switch (event.target.id) {
case "key_close":
window.close();
break;
case "btnAddException":
this.addException();
break;
case "removeException":
this.onExceptionDelete();
break;
case "removeAllExceptions":
this.onAllExceptionsDelete();
break;
}
},
_loadExceptions() {
let exceptionsFromPref = Services.prefs.getStringPref(
"network.trr.excluded-domains"

View file

@ -11,6 +11,7 @@
data-l10n-id="permissions-exceptions-doh-window"
data-l10n-attrs="title, style"
persist="width height"
csp="default-src chrome:; style-src chrome: 'unsafe-inline';"
>
<dialog
id="exceptionDialog"
@ -40,9 +41,9 @@
<keyset>
<key
id="key_close"
data-l10n-id="permissions-close-key"
modifiers="accel"
oncommand="window.close();"
/>
</keyset>
@ -59,20 +60,13 @@
data-l10n-id="permissions-doh-entry-field"
/>
<hbox align="start">
<html:input
id="url"
type="text"
style="flex: 1"
oninput="gDoHExceptionsManager.onExceptionInput();"
onkeypress="gDoHExceptionsManager.onExceptionKeyPress(event);"
/>
<html:input id="url" type="text" style="flex: 1" />
</hbox>
<hbox pack="end">
<button
id="btnAddException"
disabled="true"
data-l10n-id="permissions-doh-add-exception"
oncommand="gDoHExceptionsManager.addException();"
/>
</hbox>
<separator class="thin" />
@ -82,15 +76,9 @@
data-l10n-id="permissions-doh-col"
style="flex: 3 3 auto; width: 0"
data-isCurrentSortCol="true"
onclick="gDoHExceptionsManager.buildExceptionList(event.target)"
/>
</listheader>
<richlistbox
id="permissionsBox"
selected="false"
onkeypress="gDoHExceptionsManager.onListBoxKeyPress(event);"
onselect="gDoHExceptionsManager.onListBoxSelect();"
/>
<richlistbox id="permissionsBox" selected="false" />
</vbox>
<hbox class="actionButtons">
@ -98,12 +86,10 @@
id="removeException"
disabled="true"
data-l10n-id="permissions-doh-remove"
oncommand="gDoHExceptionsManager.onExceptionDelete();"
/>
<button
id="removeAllExceptions"
data-l10n-id="permissions-doh-remove-all"
oncommand="gDoHExceptionsManager.onAllExceptionsDelete();"
/>
</hbox>
</dialog>

View file

@ -9,48 +9,46 @@
* This dialog will ask the user to confirm that they really want to delete all
* site data for a number of hosts.
**/
let gSiteDataRemoveSelected = {
init() {
document.addEventListener("dialogaccept", function () {
window.arguments[0].allowed = true;
});
document.addEventListener("dialogcancel", function () {
window.arguments[0].allowed = false;
});
window.addEventListener("load", () => {
document.addEventListener("dialogaccept", function () {
window.arguments[0].allowed = true;
});
document.addEventListener("dialogcancel", function () {
window.arguments[0].allowed = false;
});
let list = document.getElementById("removalList");
let list = document.getElementById("removalList");
let hosts = window.arguments[0].hosts;
let hosts = window.arguments[0].hosts;
if (!hosts) {
throw new Error("Must specify hosts option in arguments.");
}
let dialog = document.getElementById("SiteDataRemoveSelectedDialog");
if (hosts.length == 1) {
dialog.classList.add("single-entry");
document.l10n.setAttributes(
document.getElementById("removing-description"),
"site-data-removing-single-desc",
{
baseDomain: hosts[0],
}
);
return;
}
dialog.classList.add("multi-entry");
hosts.sort();
let fragment = document.createDocumentFragment();
for (let host of hosts) {
let listItem = document.createXULElement("richlistitem");
let label = document.createXULElement("label");
if (host) {
label.setAttribute("value", host);
} else {
document.l10n.setAttributes(label, "site-data-local-file-host");
if (!hosts) {
throw new Error("Must specify hosts option in arguments.");
}
let dialog = document.getElementById("SiteDataRemoveSelectedDialog");
if (hosts.length == 1) {
dialog.classList.add("single-entry");
document.l10n.setAttributes(
document.getElementById("removing-description"),
"site-data-removing-single-desc",
{
baseDomain: hosts[0],
}
listItem.appendChild(label);
fragment.appendChild(listItem);
);
return;
}
dialog.classList.add("multi-entry");
hosts.sort();
let fragment = document.createDocumentFragment();
for (let host of hosts) {
let listItem = document.createXULElement("richlistitem");
let label = document.createXULElement("label");
if (host) {
label.setAttribute("value", host);
} else {
document.l10n.setAttributes(label, "site-data-local-file-host");
}
list.appendChild(fragment);
},
};
listItem.appendChild(label);
fragment.appendChild(listItem);
}
list.appendChild(fragment);
});

View file

@ -8,9 +8,9 @@
width="500"
data-l10n-id="site-data-removing-dialog"
data-l10n-attrs="title"
onload="gSiteDataRemoveSelected.init();"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
xmlns:html="http://www.w3.org/1999/xhtml"
csp="default-src chrome:; img-src moz-icon: chrome:;">
<dialog data-l10n-id="site-data-removing-dialog"
data-l10n-attrs="buttonlabelaccept">

View file

@ -57,3 +57,5 @@ let gSyncChooseWhatToSync = {
}
},
};
window.addEventListener("load", () => gSyncChooseWhatToSync.init());

View file

@ -9,9 +9,9 @@
type="child"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
onload="gSyncChooseWhatToSync.init();"
data-l10n-id="sync-choose-what-to-sync-dialog4"
data-l10n-attrs="title, style"
csp="default-src chrome:; style-src chrome: 'unsafe-inline';"
>
<dialog
id="syncChooseOptions"

View file

@ -118,6 +118,9 @@ var gTranslationsSettings = {
Services.prefs.addObserver(ALWAYS_TRANSLATE_LANGS_PREF, this);
Services.prefs.addObserver(NEVER_TRANSLATE_LANGS_PREF, this);
window.addEventListener("unload", this);
document.addEventListener("command", this);
// Build trees from the arrays.
this._alwaysTranslateLangsTree = new Tree(
"alwaysTranslateLanguagesTree",
@ -132,6 +135,15 @@ var gTranslationsSettings = {
this._neverTranslateSites
);
for (let { tree } of [
this._alwaysTranslateLangsTree,
this._neverTranslateLangsTree,
this._neverTranslateSiteTree,
]) {
tree.addEventListener("keypress", this);
tree.addEventListener("select", this);
}
// Ensure the UI for each group is in the correct state.
this.onSelectAlwaysTranslateLanguage();
this.onSelectNeverTranslateLanguage();
@ -279,6 +291,66 @@ var gTranslationsSettings = {
}
},
handleEvent(event) {
switch (event.type) {
case "unload":
this.removeObservers();
break;
case "command":
switch (event.target.id) {
case "key_close":
window.close();
break;
case "removeAlwaysTranslateLanguage":
this.onRemoveAlwaysTranslateLanguage();
break;
case "removeAllAlwaysTranslateLanguages":
this.onRemoveAllAlwaysTranslateLanguages();
break;
case "removeNeverTranslateLanguage":
this.onRemoveNeverTranslateLanguage();
break;
case "removeAllNeverTranslateLanguages":
this.onRemoveAllNeverTranslateLanguages();
break;
case "removeNeverTranslateSite":
this.onRemoveNeverTranslateSite();
break;
case "removeAllNeverTranslateSites":
this.onRemoveAllNeverTranslateSites();
break;
}
break;
case "keypress":
switch (event.currentTarget.id) {
case "alwaysTranslateLanguagesTree":
this.onAlwaysTranslateLanguageKeyPress(event);
break;
case "neverTranslateLanguagesTree":
this.onNeverTranslateLanguageKeyPress(event);
break;
case "neverTranslateSitesTree":
this.onNeverTranslateSiteKeyPress(event);
break;
}
break;
case "select":
switch (event.currentTarget.id) {
case "alwaysTranslateLanguagesTree":
this.onSelectAlwaysTranslateLanguage();
break;
case "neverTranslateLanguagesTree":
this.onSelectNeverTranslateLanguage();
break;
case "neverTranslateSitesTree":
this.onSelectNeverTranslateSite();
break;
}
break;
}
},
/**
* Ensures that buttons states are enabled/disabled accordingly based on the
* content of the trees.
@ -452,3 +524,5 @@ var gTranslationsSettings = {
Services.prefs.removeObserver(NEVER_TRANSLATE_LANGS_PREF, this);
},
};
window.addEventListener("load", () => gTranslationsSettings.onLoad());

View file

@ -10,9 +10,8 @@
data-l10n-attrs="title, style"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
onload="gTranslationsSettings.onLoad();"
onunload="gTranslationsSettings.removeObservers();"
persist="width height"
csp="default-src chrome:; style-src chrome: 'unsafe-inline';"
>
<dialog
buttons="accept"
@ -33,9 +32,9 @@
<keyset>
<key
id="key_close"
data-l10n-id="translations-settings-close-key"
modifiers="accel"
oncommand="window.close();"
/>
</keyset>
@ -52,8 +51,6 @@
flex="1"
style="height: 12em"
hidecolumnpicker="true"
onkeypress="gTranslationsSettings.onAlwaysTranslateLanguageKeyPress(event)"
onselect="gTranslationsSettings.onSelectAlwaysTranslateLanguage();"
>
<treecols>
<treecol
@ -70,12 +67,10 @@
id="removeAlwaysTranslateLanguage"
disabled="true"
data-l10n-id="translations-settings-remove-language-button"
oncommand="gTranslationsSettings.onRemoveAlwaysTranslateLanguage();"
/>
<button
id="removeAllAlwaysTranslateLanguages"
data-l10n-id="translations-settings-remove-all-languages-button"
oncommand="gTranslationsSettings.onRemoveAllAlwaysTranslateLanguages();"
/>
</hbox>
<separator />
@ -91,8 +86,6 @@
flex="1"
style="height: 12em"
hidecolumnpicker="true"
onkeypress="gTranslationsSettings.onNeverTranslateLanguageKeyPress(event)"
onselect="gTranslationsSettings.onSelectNeverTranslateLanguage();"
>
<treecols>
<treecol
@ -109,12 +102,10 @@
id="removeNeverTranslateLanguage"
disabled="true"
data-l10n-id="translations-settings-remove-language-button"
oncommand="gTranslationsSettings.onRemoveNeverTranslateLanguage();"
/>
<button
id="removeAllNeverTranslateLanguages"
data-l10n-id="translations-settings-remove-all-languages-button"
oncommand="gTranslationsSettings.onRemoveAllNeverTranslateLanguages();"
/>
</hbox>
<separator />
@ -130,8 +121,6 @@
flex="1"
style="height: 12em"
hidecolumnpicker="true"
onkeypress="gTranslationsSettings.onNeverTranslateSiteKeyPress(event)"
onselect="gTranslationsSettings.onSelectNeverTranslateSite();"
>
<treecols>
<treecol
@ -148,12 +137,10 @@
id="removeNeverTranslateSite"
disabled="true"
data-l10n-id="translations-settings-remove-site-button"
oncommand="gTranslationsSettings.onRemoveNeverTranslateSite();"
/>
<button
id="removeAllNeverTranslateSites"
data-l10n-id="translations-settings-remove-all-sites-button"
oncommand="gTranslationsSettings.onRemoveAllNeverTranslateSites();"
/>
</hbox>
</vbox>

View file

@ -35,6 +35,11 @@ const MIN_PAIRING_LOADING_TIME_MS = 1000;
*/
var gFxaPairDeviceDialog = {
init() {
window.addEventListener("unload", () => this.uninit());
document
.getElementById("qrError")
.addEventListener("click", () => this.startPairingFlow());
this._resetBackgroundQR();
// We let the modal show itself before eventually showing a primary-password dialog later.
Services.tm.dispatchToMainThread(() => this.startPairingFlow());
@ -140,3 +145,5 @@ var gFxaPairDeviceDialog = {
}
},
};
window.addEventListener("load", () => gFxaPairDeviceDialog.init());

View file

@ -10,10 +10,9 @@
type="child"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
onload="gFxaPairDeviceDialog.init();"
onunload="gFxaPairDeviceDialog.uninit()"
data-l10n-id="fxa-pair-device-dialog-sync2"
data-l10n-attrs="style"
csp="default-src chrome:; img-src chrome: data:; style-src chrome: 'unsafe-inline';"
>
<dialog id="fxaPairDeviceDialog1" buttons="accept">
<linkset>
@ -59,7 +58,7 @@
<vbox align="center" id="qrWrapper" pairing-status="loading">
<box id="qrContainer"></box>
<box id="qrSpinner"></box>
<vbox id="qrError" onclick="gFxaPairDeviceDialog.startPairingFlow();">
<vbox id="qrError">
<image id="refresh-qr" />
<label
class="qr-error-text"

View file

@ -106,7 +106,9 @@ async function openPreferencesViaOpenPreferencesAPI(aPane, aOptions) {
? "sync-pane-loaded"
: "privacy-pane-loaded";
let finalPrefPaneLoaded = TestUtils.topicObserved(finalPaneEvent, () => true);
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank");
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
allowInheritPrincipal: true,
});
openPreferences(aPane, aOptions);
let newTabBrowser = gBrowser.selectedBrowser;

View file

@ -6,7 +6,16 @@ const TEST_PATH = getRootDirectory(gTestPath).replace(
"chrome://mochitests/content",
"https://example.com"
);
const SECURITY_DELAY = 3000;
add_task(async function () {
// Set a custom, higher security delay for the test to avoid races on slow
// builds.
await SpecialPowers.pushPrefEnv({
set: [["security.notification_enable_delay", SECURITY_DELAY]],
});
let notificationValue = "Protocol Registration: web+testprotocol";
let testURI = TEST_PATH + "browser_registerProtocolHandler_notification.html";
@ -54,4 +63,16 @@ add_task(async function () {
let button = buttons[0];
isnot(button.label, null, "We expect the add button to have a label.");
todo(button.accesskey, "We expect the add button to have a accesskey.");
ok(button.disabled, "We expect the button to be disabled initially.");
let timeoutMS = SECURITY_DELAY + 100;
info(`Wait ${timeoutMS}ms for the button to enable.`);
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(resolve => setTimeout(resolve, SECURITY_DELAY + 100));
ok(
!button.disabled,
"We expect the button to be enabled after the security delay."
);
});

View file

@ -0,0 +1,214 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
});
/**
* Manages the set of available opensearch engines per browser.
*/
class _OpenSearchManager {
/**
* @typedef {object} OpenSearchData
* @property {string} uri
* The uri of the opensearch XML.
* @property {string} title
* The name of the engine.
* @property {string} icon
* Data URI containing the engine's icon.
*/
/**
* @type {WeakMap<XULBrowserElement, OpenSearchData[]>}
*/
#offeredEngines = new WeakMap();
/**
* @type {WeakMap<XULBrowserElement, OpenSearchData[]>}
*/
#hiddenEngines = new WeakMap();
constructor() {
Services.obs.addObserver(this, "browser-search-engine-modified");
}
/**
* Observer for browser-search-engine-modified.
*
* @param {nsISearchEngine} engine
* The modified engine.
* @param {string} _topic
* Always browser-search-engine-modified.
* @param {string} data
* The type of modification.
*/
observe(engine, _topic, data) {
// There are two kinds of search engine objects: nsISearchEngine objects
// and plain OpenSearchData objects. `engine` in this observer is the
// former and the arrays in #offeredEngines and #hiddenEngines contain the
// latter. They are related by their names.
switch (data) {
case "engine-added":
// An engine was added to the search service. If a page is offering the
// engine, then the engine needs to be removed from the corresponding
// browser's offered engines.
this.#removeMaybeOfferedEngine(engine.name);
break;
case "engine-removed":
// An engine was removed from the search service. If a page is offering
// the engine, then the engine needs to be added back to the corresponding
// browser's offered engines.
this.#addMaybeOfferedEngine(engine.name);
break;
}
}
/**
* Adds an open search engine to the list of available engines for a browser.
* If an engine with that name is already installed, adds it to the list
* of hidden engines instead.
*
* @param {XULBrowserElement} browser
* The browser offering the engine.
* @param {{title: string, href: string}} engine
* The title of the engine and the url to the opensearch XML.
*/
addEngine(browser, engine) {
if (!Services.search.hasSuccessfullyInitialized) {
// We haven't finished initializing search yet. This means we can't
// call getEngineByName here. Since this is only on start-up and unlikely
// to happen in the normal case, we'll just return early rather than
// trying to handle it asynchronously.
return;
}
// Check to see whether we've already added an engine with this title
if (this.#offeredEngines.get(browser)?.some(e => e.title == engine.title)) {
return;
}
// If this engine (identified by title) is already in the list, add it
// to the list of hidden engines rather than to the main list.
let shouldBeHidden = !!Services.search.getEngineByName(engine.title);
let engines =
(shouldBeHidden
? this.#hiddenEngines.get(browser)
: this.#offeredEngines.get(browser)) || [];
engines.push({
uri: engine.href,
title: engine.title,
get icon() {
return browser.mIconURL;
},
});
if (shouldBeHidden) {
this.#hiddenEngines.set(browser, engines);
} else {
let win = browser.ownerGlobal;
this.#offeredEngines.set(browser, engines);
if (browser == win.gBrowser.selectedBrowser) {
this.updateOpenSearchBadge(win);
}
}
}
/**
* Updates the browser UI to show whether or not additional engines are
* available when a page is loaded or the user switches tabs to a page that
* has open search engines.
*
* @param {WindowProxy} win
* The window whose UI should be updated.
*/
updateOpenSearchBadge(win) {
let engines = this.#offeredEngines.get(win.gBrowser.selectedBrowser);
win.gURLBar.addSearchEngineHelper.setEnginesFromBrowser(
win.gBrowser.selectedBrowser,
engines || []
);
let searchBar = win.document.getElementById("searchbar");
if (!searchBar) {
return;
}
if (engines && engines.length) {
searchBar.setAttribute("addengines", "true");
} else {
searchBar.removeAttribute("addengines");
}
}
#addMaybeOfferedEngine(engineName) {
for (let win of lazy.BrowserWindowTracker.orderedWindows) {
for (let browser of win.gBrowser.browsers) {
let hiddenEngines = this.#hiddenEngines.get(browser) || [];
let offeredEngines = this.#offeredEngines.get(browser) || [];
for (let i = 0; i < hiddenEngines.length; i++) {
if (hiddenEngines[i].title == engineName) {
offeredEngines.push(hiddenEngines[i]);
if (offeredEngines.length == 1) {
this.#offeredEngines.set(browser, offeredEngines);
}
hiddenEngines.splice(i, 1);
if (browser == win.gBrowser.selectedBrowser) {
this.updateOpenSearchBadge(win);
}
break;
}
}
}
}
}
#removeMaybeOfferedEngine(engineName) {
for (let win of lazy.BrowserWindowTracker.orderedWindows) {
for (let browser of win.gBrowser.browsers) {
let hiddenEngines = this.#hiddenEngines.get(browser) || [];
let offeredEngines = this.#offeredEngines.get(browser) || [];
for (let i = 0; i < offeredEngines.length; i++) {
if (offeredEngines[i].title == engineName) {
hiddenEngines.push(offeredEngines[i]);
if (hiddenEngines.length == 1) {
this.#hiddenEngines.set(browser, hiddenEngines);
}
offeredEngines.splice(i, 1);
if (browser == win.gBrowser.selectedBrowser) {
this.updateOpenSearchBadge(win);
}
break;
}
}
}
}
}
/**
* Get the open search engines offered by a certain browser.
*
* @param {XULBrowserElement} browser
* The browser for which to get the engines.
* @returns {OpenSearchData[]}
* The open search engines.
*/
getEngines(browser) {
return this.#offeredEngines.get(browser) || [];
}
clearEngines(browser) {
this.#offeredEngines.delete(browser);
this.#hiddenEngines.delete(browser);
}
}
export const OpenSearchManager = new _OpenSearchManager();

View file

@ -5,12 +5,11 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
OpenSearchManager: "resource:///modules/OpenSearchManager.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
SearchUIUtils: "resource:///modules/SearchUIUtils.sys.mjs",
});
const EMPTY_ADD_ENGINES = [];
/**
* Defines the search one-off button elements. These are displayed at the bottom
* of the address bar and search bar. The address bar buttons are a subclass in
@ -371,10 +370,6 @@ export class SearchOneOffs {
}
}
_getAddEngines() {
return this.window.gBrowser.selectedBrowser.engines || EMPTY_ADD_ENGINES;
}
get _maxInlineAddEngines() {
return 3;
}
@ -407,7 +402,9 @@ export class SearchOneOffs {
return;
}
const addEngines = this._getAddEngines();
const addEngines = lazy.OpenSearchManager.getEngines(
this.window.gBrowser.selectedBrowser
);
// Return early if the engines and panel width have not changed.
if (this.popup && this._textbox) {

View file

@ -6,12 +6,23 @@
* Various utilities for search related UI.
*/
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
const lazy = {};
ChromeUtils.defineLazyGetter(lazy, "SearchUIUtilsL10n", () => {
return new Localization(["browser/search.ftl", "branding/brand.ftl"]);
});
ChromeUtils.defineESModuleGetters(lazy, {
BrowserSearchTelemetry: "resource:///modules/BrowserSearchTelemetry.sys.mjs",
BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs",
});
export var SearchUIUtils = {
initialized: false,
@ -34,6 +45,84 @@ export var SearchUIUtils = {
}
},
/**
* This function is called by the category manager for the
* `search-service-notification` category.
*
* It allows the SearchService (in toolkit) to display
* notifications in the browser for certain events.
*
* @param {string} notificationType
* Determines the function displaying the notification.
* @param {...any} args
* The arguments for that function.
*/
showSearchServiceNotification(notificationType, ...args) {
switch (notificationType) {
case "search-engine-removal":
this.removalOfSearchEngineNotificationBox(...args);
break;
}
},
/**
* Infobar to notify the user's search engine has been removed
* and replaced with an application default search engine.
*
* @param {string} oldEngine
* name of the engine to be moved and replaced.
* @param {string} newEngine
* name of the application default engine to replaced the removed engine.
*/
async removalOfSearchEngineNotificationBox(oldEngine, newEngine) {
let win = lazy.BrowserWindowTracker.getTopWindow();
let buttons = [
{
"l10n-id": "remove-search-engine-button",
primary: true,
callback() {
const notificationBox = win.gNotificationBox.getNotificationWithValue(
"search-engine-removal"
);
win.gNotificationBox.removeNotification(notificationBox);
},
},
{
supportPage: "search-engine-removal",
},
];
await win.gNotificationBox.appendNotification(
"search-engine-removal",
{
label: {
"l10n-id": "removed-search-engine-message2",
"l10n-args": { oldEngine, newEngine },
},
priority: win.gNotificationBox.PRIORITY_SYSTEM,
},
buttons
);
// _updatePlaceholderFromDefaultEngine only updates the pref if the search service
// hasn't finished initializing, so we explicitly update it here to be sure.
SearchUIUtils.updatePlaceholderNamePreference(
await Services.search.getDefault(),
false
);
SearchUIUtils.updatePlaceholderNamePreference(
await Services.search.getDefaultPrivate(),
true
);
for (let openWin of lazy.BrowserWindowTracker.orderedWindows) {
openWin.gURLBar
?._updatePlaceholderFromDefaultEngine(false)
.catch(console.error);
}
},
/**
* Adds an open search engine and handles error UI.
*
@ -107,7 +196,7 @@ export var SearchUIUtils = {
/**
* Update the placeholderName preference for the default search engine.
*
* @param {SearchEngine} engine The new default search engine.
* @param {nsISearchEngine} engine The new default search engine.
* @param {boolean} isPrivate Whether this change applies to private windows.
*/
updatePlaceholderNamePreference(engine, isPrivate) {
@ -119,4 +208,311 @@ export var SearchUIUtils = {
Services.prefs.clearUserPref(prefName);
}
},
/**
* Focuses the search bar if present on the toolbar, or the address bar,
* putting it in search mode. Will do so in an existing non-popup browser
* window or open a new one if necessary.
*
* @param {WindowProxy} window
* The window where the seach was triggered.
*/
webSearch(window) {
if (
window.location.href != AppConstants.BROWSER_CHROME_URL ||
window.gURLBar.readOnly
) {
let topWindow = lazy.URILoadingHelper.getTopWin(window, {
skipPopups: true,
});
if (topWindow) {
// If there's an open browser window, it should handle this command
topWindow.focus();
SearchUIUtils.webSearch(topWindow);
} else {
// If there are no open browser windows, open a new one
let newWindow = window.openDialog(
AppConstants.BROWSER_CHROME_URL,
"_blank",
"chrome,all,dialog=no",
"about:blank"
);
let observer = subject => {
if (subject == newWindow) {
SearchUIUtils.webSearch(newWindow);
Services.obs.removeObserver(
observer,
"browser-delayed-startup-finished"
);
}
};
Services.obs.addObserver(observer, "browser-delayed-startup-finished");
}
return;
}
let focusUrlBarIfSearchFieldIsNotActive = function (aSearchBar) {
if (!aSearchBar || window.document.activeElement != aSearchBar.textbox) {
// Limit the results to search suggestions, like the search bar.
window.gURLBar.searchModeShortcut();
}
};
let searchBar = window.document.getElementById("searchbar");
let placement =
lazy.CustomizableUI.getPlacementOfWidget("search-container");
let focusSearchBar = () => {
searchBar = window.document.getElementById("searchbar");
searchBar.select();
focusUrlBarIfSearchFieldIsNotActive(searchBar);
};
if (
placement &&
searchBar &&
((searchBar.parentNode.getAttribute("overflowedItem") == "true" &&
placement.area == lazy.CustomizableUI.AREA_NAVBAR) ||
placement.area == lazy.CustomizableUI.AREA_FIXED_OVERFLOW_PANEL)
) {
let navBar = window.document.getElementById(
lazy.CustomizableUI.AREA_NAVBAR
);
navBar.overflowable.show().then(focusSearchBar);
return;
}
if (searchBar) {
if (window.fullScreen) {
window.FullScreen.showNavToolbox();
}
searchBar.select();
}
focusUrlBarIfSearchFieldIsNotActive(searchBar);
},
/**
* Loads a search results page, given a set of search terms. Uses the current
* engine if the search bar is visible, or the default engine otherwise.
*
* @param {WindowProxy} window
* The window where the search was triggered.
* @param {string} searchText
* The search terms to use for the search.
* @param {?string} where
* String indicating where the search should load. Most commonly used
* are 'tab' or 'window', defaults to 'current'.
* @param {boolean} usePrivate
* Whether to use the Private Browsing mode default search engine.
* Defaults to `false`.
* @param {nsIPrincipal} triggeringPrincipal
* The principal to use for a new window or tab.
* @param {nsIContentSecurityPolicy} csp
* The content security policy to use for a new window or tab.
* @param {boolean} [inBackground=false]
* Set to true for the tab to be loaded in the background.
* @param {?nsISearchEngine} [engine=null]
* The search engine to use for the search.
* @param {?NativeTab} [tab=null]
* The tab to show the search result.
*
* @returns {Promise<?{engine: nsISearchEngine, url: nsIURI}>}
* Object containing the search engine used to perform the
* search and the url, or null if no search was performed.
*/
async _loadSearch(
window,
searchText,
where,
usePrivate,
triggeringPrincipal,
csp,
inBackground = false,
engine = null,
tab = null
) {
if (!triggeringPrincipal) {
throw new Error(
"Required argument triggeringPrincipal missing within _loadSearch"
);
}
if (!engine) {
engine = usePrivate
? await Services.search.getDefaultPrivate()
: await Services.search.getDefault();
}
let submission = engine.getSubmission(searchText);
// getSubmission can return null if the engine doesn't have a URL
// with a text/html response type. This is unlikely (since
// SearchService._addEngineToStore() should fail for such an engine),
// but let's be on the safe side.
if (!submission) {
return null;
}
window.openLinkIn(submission.uri.spec, where || "current", {
private: usePrivate && !lazy.PrivateBrowsingUtils.isWindowPrivate(window),
postData: submission.postData,
inBackground,
relatedToCurrent: true,
triggeringPrincipal,
csp,
targetBrowser: tab?.linkedBrowser,
globalHistoryOptions: {
triggeringSearchEngine: engine.name,
},
});
return { engine, url: submission.uri };
},
/**
* Perform a search initiated from the context menu.
* This should only be called from the context menu.
*
* @param {WindowProxy} window
* The window where the search was triggered.
* @param {string} searchText
* The search terms to use for the search.
* @param {boolean} usePrivate
* Whether to use the Private Browsing mode default search engine.
* Defaults to `false`.
* @param {nsIPrincipal} triggeringPrincipal
* The principal of the document whose context menu was clicked.
* @param {nsIContentSecurityPolicy} csp
* The content security policy to use for a new window or tab.
* @param {Event} event
* The event triggering the search.
*/
async loadSearchFromContext(
window,
searchText,
usePrivate,
triggeringPrincipal,
csp,
event
) {
event = lazy.BrowserUtils.getRootEvent(event);
let where = lazy.BrowserUtils.whereToOpenLink(event);
if (where == "current") {
// override: historically search opens in new tab
where = "tab";
}
if (usePrivate && !lazy.PrivateBrowsingUtils.isWindowPrivate(window)) {
where = "window";
}
let inBackground = Services.prefs.getBoolPref(
"browser.search.context.loadInBackground"
);
if (event.button == 1 || event.ctrlKey) {
inBackground = !inBackground;
}
let searchInfo = await SearchUIUtils._loadSearch(
window,
searchText,
where,
usePrivate,
Services.scriptSecurityManager.createNullPrincipal(
triggeringPrincipal.originAttributes
),
csp,
inBackground
);
if (searchInfo) {
lazy.BrowserSearchTelemetry.recordSearch(
window.gBrowser.selectedBrowser,
searchInfo.engine,
"contextmenu"
);
}
},
/**
* Perform a search initiated from the command line.
*
* @param {WindowProxy} window
* The window where the search was triggered.
* @param {string} searchText
* The search terms to use for the search.
* @param {boolean} usePrivate
* Whether to use the Private Browsing mode default search engine.
* Defaults to `false`.
* @param {nsIPrincipal} triggeringPrincipal
* The principal to use for a new window or tab.
* @param {nsIContentSecurityPolicy} csp
* The content security policy to use for a new window or tab.
*/
async loadSearchFromCommandLine(
window,
searchText,
usePrivate,
triggeringPrincipal,
csp
) {
let searchInfo = await SearchUIUtils._loadSearch(
window,
searchText,
"current",
usePrivate,
triggeringPrincipal,
csp
);
if (searchInfo) {
lazy.BrowserSearchTelemetry.recordSearch(
window.gBrowser.selectedBrowser,
searchInfo.engine,
"system"
);
}
},
/**
* Perform a search initiated from an extension.
*
* @param {object} params
* The params.
* @param {WindowProxy} params.window
* The window where the search was triggered.
* @param {string} params.query
* The search terms to use for the search.
* @param {nsISearchEngine} params.engine
* The search engine to use for the search.
* @param {string} params.where
* String indicating where the search should load.
* @param {NativeTab} params.tab
* The tab to show the search result.
* @param {nsIPrincipal} params.triggeringPrincipal
* The principal to use for a new window or tab.
*/
async loadSearchFromExtension({
window,
query,
engine,
where,
tab,
triggeringPrincipal,
}) {
let searchInfo = await SearchUIUtils._loadSearch(
window,
query,
where,
lazy.PrivateBrowsingUtils.isWindowPrivate(window),
triggeringPrincipal,
null,
false,
engine,
tab
);
if (searchInfo) {
lazy.BrowserSearchTelemetry.recordSearch(
window.gBrowser.selectedBrowser,
searchInfo.engine,
"webextension"
);
}
},
};

View file

@ -7,6 +7,8 @@
// Wrap in a block to prevent leaking to window scope.
{
ChromeUtils.defineESModuleGetters(this, {
BrowserSearchTelemetry:
"resource:///modules/BrowserSearchTelemetry.sys.mjs",
BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
SearchOneOffs: "resource:///modules/SearchOneOffs.sys.mjs",
});

View file

@ -12,6 +12,8 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
BrowserSearchTelemetry:
"resource:///modules/BrowserSearchTelemetry.sys.mjs",
BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
SearchSuggestionController:
@ -128,7 +130,7 @@
this._textbox.popup.updateHeader();
// Refresh the display (updating icon, etc)
this.updateDisplay();
BrowserSearch.updateOpenSearchBadge();
OpenSearchManager.updateOpenSearchBadge(window);
})
.catch(status =>
console.error(
@ -328,7 +330,7 @@
let selectedIndex = this.telemetrySelectedIndex;
let isOneOff = false;
BrowserSearchTelemetry.recordSearchSuggestionSelectionMethod(
lazy.BrowserSearchTelemetry.recordSearchSuggestionSelectionMethod(
aEvent,
"searchbar",
selectedIndex
@ -387,7 +389,7 @@
this.telemetrySelectedIndex = -1;
BrowserSearchTelemetry.recordSearch(
lazy.BrowserSearchTelemetry.recordSearch(
gBrowser.selectedBrowser,
engine,
"searchbar",

View file

@ -6,6 +6,7 @@
EXTRA_JS_MODULES += [
"BrowserSearchTelemetry.sys.mjs",
"OpenSearchManager.sys.mjs",
"SearchOneOffs.sys.mjs",
"SearchSERPTelemetry.sys.mjs",
"SearchUIUtils.sys.mjs",

View file

@ -48,6 +48,8 @@ support-files = [
["browser_defaultPrivate_nimbus.js"]
["browser_displayNotification.js"]
["browser_google_behavior.js"]
["browser_hiddenOneOffs_diacritics.js"]

View file

@ -98,16 +98,14 @@ async function prepareTest(searchBarValue = "test") {
add_setup(async function () {
await Services.search.init();
await gCUITestUtils.addSearchBar();
searchBar = await gCUITestUtils.addSearchBar();
searchButton = searchBar.querySelector(".search-go-button");
await SearchTestUtils.installOpenSearchEngine({
url: "http://mochi.test:8888/browser/browser/components/search/test/browser/426329.xml",
setAsDefault: true,
});
searchBar = BrowserSearch.searchBar;
searchButton = searchBar.querySelector(".search-go-button");
registerCleanupFunction(() => {
searchBar.value = "";
while (gBrowser.tabs.length != 1) {

View file

@ -44,14 +44,16 @@ add_task(async function test() {
BrowserTestUtils.addTab(gBrowser, "about:blank");
BrowserSearch.loadSearchFromContext(
SearchUIUtils.loadSearchFromContext(
window,
"mozilla",
false,
Services.scriptSecurityManager.getSystemPrincipal(),
Services.scriptSecurityManager.getSystemPrincipal().csp,
new PointerEvent("click")
);
BrowserSearch.loadSearchFromContext(
SearchUIUtils.loadSearchFromContext(
window,
"firefox",
false,
Services.scriptSecurityManager.getSystemPrincipal(),

View file

@ -0,0 +1,29 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function test_removalMessage() {
Assert.ok(
!gNotificationBox.getNotificationWithValue("search-engine-removal"),
"Message is not displayed initially."
);
BrowserUtils.callModulesFromCategory(
"search-service-notification",
"search-engine-removal",
"Engine 1",
"Engine 2"
);
await TestUtils.waitForCondition(
() => gNotificationBox.getNotificationWithValue("search-engine-removal"),
"Waiting for message to be displayed"
);
let notificationBox = gNotificationBox.getNotificationWithValue(
"search-engine-removal"
);
Assert.ok(notificationBox, "Message is displayed.");
notificationBox.close();
});

View file

@ -120,11 +120,11 @@ async function testSearchEngine(engineDetails) {
run() {
// Simulate a contextmenu search
// FIXME: This is a bit "low-level"...
BrowserSearch._loadSearch(
SearchUIUtils._loadSearch(
window,
"foo",
false,
false,
"contextmenu",
Services.scriptSecurityManager.getSystemPrincipal()
);
},
@ -154,13 +154,13 @@ async function testSearchEngine(engineDetails) {
await gCUITestUtils.addSearchBar();
},
run() {
let sb = BrowserSearch.searchBar;
let sb = document.getElementById("searchbar");
sb.focus();
sb.value = "foo";
EventUtils.synthesizeKey("KEY_Enter");
},
postTest() {
BrowserSearch.searchBar.value = "";
document.getElementById("searchbar").value = "";
gCUITestUtils.removeSearchBar();
},
},

View file

@ -18,11 +18,11 @@ add_task(async function test_composition_with_focus() {
await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com");
info("Focus on the search bar");
const searchBarTextBox = BrowserSearch.searchBar.textbox;
const searchBarTextBox = document.getElementById("searchbar").textbox;
EventUtils.synthesizeMouseAtCenter(searchBarTextBox, {});
is(
document.activeElement,
BrowserSearch.searchBar.textbox,
document.getElementById("searchbar").textbox,
"The text box of search bar has focus"
);
@ -40,7 +40,7 @@ add_task(async function test_composition_with_focus() {
EventUtils.synthesizeMouseAtCenter(searchBarTextBox, {});
is(
document.activeElement,
BrowserSearch.searchBar.textbox,
document.getElementById("searchbar").textbox,
"The textbox of search bar has focus again"
);
@ -67,7 +67,7 @@ add_task(async function test_composition_with_focus() {
});
is(
document.activeElement,
BrowserSearch.searchBar.textbox,
document.getElementById("searchbar").textbox,
"The search bar still has focus"
);

View file

@ -18,7 +18,7 @@ add_setup(async function () {
await gCUITestUtils.addSearchBar();
win = await BrowserTestUtils.openNewBrowserWindow();
searchBar = win.BrowserSearch.searchBar;
searchBar = win.document.getElementById("searchbar");
searchIcon = searchBar.querySelector(".searchbar-search-button");
searchPopup = win.document.getElementById("PopupSearchAutoComplete");

View file

@ -18,7 +18,7 @@ add_task(async function () {
let windowsToClose = [];
function performSearch(aWin, aIsPrivate) {
let searchBar = aWin.BrowserSearch.searchBar;
let searchBar = aWin.document.getElementById("searchbar");
ok(searchBar, "got search bar");
let loadPromise = BrowserTestUtils.browserLoaded(
@ -49,7 +49,7 @@ add_task(async function () {
newWindow = await testOnWindow(false);
let searchBar = newWindow.BrowserSearch.searchBar;
let searchBar = newWindow.document.getElementById("searchbar");
searchBar.value = "p";
searchBar.focus();

View file

@ -132,7 +132,7 @@ async function testSearchEngine(engineDetails) {
base.replace("{code}", engineDetails.codes.submission),
"Check search URL for 'foo'"
);
let sb = BrowserSearch.searchBar;
let sb = document.getElementById("searchbar");
let engineTests = [
{
@ -141,11 +141,11 @@ async function testSearchEngine(engineDetails) {
run() {
// Simulate a contextmenu search
// FIXME: This is a bit "low-level"...
BrowserSearch._loadSearch(
SearchUIUtils._loadSearch(
window,
"foo",
false,
false,
"contextmenu",
Services.scriptSecurityManager.getSystemPrincipal()
);
},

View file

@ -16,7 +16,7 @@ add_setup(async function () {
await gCUITestUtils.addSearchBar();
win = await BrowserTestUtils.openNewBrowserWindow();
searchBar = win.BrowserSearch.searchBar;
searchBar = win.document.getElementById("searchbar");
searchPopup = win.document.getElementById("PopupSearchAutoComplete");
searchIcon = searchBar.querySelector(".searchbar-search-button");

View file

@ -3,10 +3,14 @@
/* eslint-disable mozilla/no-arbitrary-setTimeout */
ChromeUtils.defineESModuleGetters(this, {
OpenSearchManager: "resource:///modules/OpenSearchManager.sys.mjs",
});
// Bug 1588193 - BrowserTestUtils.waitForContentEvent now resolves slightly
// earlier than before, so it no longer suffices to only wait for a single event
// tick before checking if browser.engines has been updated. Instead we use a 1s
// timeout, which may cause the test to take more time.
// tick before checking if the available engines has been updated. Instead we use
// a 1s timeout, which may cause the test to take more time.
requestLongerTimeout(2);
add_task(async function () {
@ -82,18 +86,18 @@ async function searchDiscovery() {
await promiseLinkAdded;
await new Promise(resolve => setTimeout(resolve, 1000));
if (browser.engines) {
info(`Found ${browser.engines.length} engines`);
info(`First engine title: ${browser.engines[0].title}`);
let engines = OpenSearchManager.getEngines(browser);
if (engines.length) {
info(`Found ${engines.length} engines`);
info(`First engine title: ${engines[0].title}`);
let hasEngine = testCase.count
? browser.engines[0].title == testCase.title &&
browser.engines.length == testCase.count
: browser.engines[0].title == testCase.title;
ok(hasEngine, testCase.text);
browser.engines = null;
? engines[0].title == testCase.title && engines.length == testCase.count
: engines[0].title == testCase.title;
Assert.ok(hasEngine, testCase.text);
} else {
ok(!testCase.pass, testCase.text);
Assert.ok(!testCase.pass, testCase.text);
}
OpenSearchManager.clearEngines(browser);
}
info("Test multiple engines with the same title");
@ -121,12 +125,12 @@ async function searchDiscovery() {
await promiseLinkAdded;
await new Promise(resolve => setTimeout(resolve, 1000));
ok(browser.engines, "has engines");
is(browser.engines.length, 1, "only one engine");
is(
browser.engines[0].uri,
let engines = OpenSearchManager.getEngines(browser);
Assert.equal(engines.length, 1, "only one engine");
Assert.equal(
engines[0].uri,
"https://first.mozilla.com/search.xml",
"first engine wins"
);
browser.engines = null;
OpenSearchManager.clearEngines(browser);
}

View file

@ -36,7 +36,7 @@ add_setup(async function () {
});
add_task(async function test_emptybar() {
const searchbar = win.BrowserSearch.searchBar;
const searchbar = win.document.getElementById("searchbar");
searchbar.focus();
let contextMenu = searchbar.querySelector(".textbox-contextmenu");
@ -74,7 +74,7 @@ add_task(async function test_emptybar() {
});
add_task(async function test_text_in_bar() {
const searchbar = win.BrowserSearch.searchBar;
const searchbar = win.document.getElementById("searchbar");
searchbar.focus();
searchbar.value = "Test";
@ -115,7 +115,7 @@ add_task(async function test_text_in_bar() {
});
add_task(async function test_unfocused_emptybar() {
const searchbar = win.BrowserSearch.searchBar;
const searchbar = win.document.getElementById("searchbar");
// clear searchbar value from previous test
searchbar.value = "";
@ -158,8 +158,7 @@ add_task(async function test_unfocused_emptybar() {
});
add_task(async function test_text_in_unfocused_bar() {
const searchbar = win.BrowserSearch.searchBar;
const searchbar = win.document.getElementById("searchbar");
searchbar.value = "Test";
// force focus onto another component
@ -205,7 +204,7 @@ add_task(async function test_paste_and_go() {
gBrowser: win.gBrowser,
});
const searchbar = win.BrowserSearch.searchBar;
const searchbar = win.document.getElementById("searchbar");
searchbar.value = "";
searchbar.focus();

View file

@ -72,12 +72,10 @@ async function doSearch(
templateUrl,
inputText = "query"
) {
await searchInSearchbar(inputText, win);
let popup = await searchInSearchbar(inputText, win);
Assert.ok(
win.BrowserSearch.searchBar.textbox.popup.searchbarEngineName
.getAttribute("value")
.includes(engineName),
popup.searchbarEngineName.getAttribute("value").includes(engineName),
"Should have the correct engine name displayed in the bar"
);
@ -178,11 +176,11 @@ add_task(async function test_form_history_delete() {
await FormHistoryTestUtils.clear("searchbar-history");
await FormHistoryTestUtils.add("searchbar-history", ["first", "second"]);
let sb = BrowserSearch.searchBar;
sb.focus();
sb.value = "";
let searchBar = document.getElementById("searchbar");
searchBar.focus();
searchBar.value = "";
let popupshown = BrowserTestUtils.waitForEvent(
sb.textbox.popup,
searchBar.textbox.popup,
"popupshown"
);
EventUtils.synthesizeKey("KEY_ArrowDown");
@ -258,9 +256,9 @@ add_task(async function test_searchbar_revert() {
await doSearch(window, tab, "MozSearch1", templateNormal, "testQuery");
let searchbar = window.BrowserSearch.searchBar;
let searchBar = window.document.getElementById("searchbar");
is(
searchbar.value,
searchBar.value,
"testQuery",
"Search value should be the the last search"
);
@ -268,34 +266,34 @@ add_task(async function test_searchbar_revert() {
// focus search bar
let promise = promiseEvent(searchPopup, "popupshown");
info("Opening search panel");
searchbar.focus();
searchBar.focus();
await promise;
searchbar.value = "aQuery";
searchbar.value = "anotherQuery";
searchBar.value = "aQuery";
searchBar.value = "anotherQuery";
// close the panel using the escape key.
promise = promiseEvent(searchPopup, "popuphidden");
EventUtils.synthesizeKey("KEY_Escape");
await promise;
is(searchbar.value, "anotherQuery", "The search value should be the same");
is(searchBar.value, "anotherQuery", "The search value should be the same");
// revert the search bar value
EventUtils.synthesizeKey("KEY_Escape");
is(
searchbar.value,
searchBar.value,
"testQuery",
"The search value should have been reverted"
);
EventUtils.synthesizeKey("KEY_Escape");
is(searchbar.value, "testQuery", "The search value should be the same");
is(searchBar.value, "testQuery", "The search value should be the same");
await doSearch(window, tab, "MozSearch1", templateNormal, "query");
is(searchbar.value, "query", "The search value should be query");
is(searchBar.value, "query", "The search value should be query");
EventUtils.synthesizeKey("KEY_Escape");
is(searchbar.value, "query", "The search value should be the same");
is(searchBar.value, "query", "The search value should be the same");
BrowserTestUtils.removeTab(tab);
});

View file

@ -19,7 +19,6 @@ add_task(async function searchOnEnterSoon() {
info("Search on Enter as soon as typing a char");
const win = await BrowserTestUtils.openNewBrowserWindow();
const browser = win.gBrowser.selectedBrowser;
const browserSearch = win.BrowserSearch;
const onPageHide = SpecialPowers.spawn(browser, [], () => {
return new Promise(resolve => {
@ -40,7 +39,7 @@ add_task(async function searchOnEnterSoon() {
});
info("Focus on the search bar");
const searchBarTextBox = browserSearch.searchBar.textbox;
const searchBarTextBox = win.document.getElementById("searchbar").textbox;
EventUtils.synthesizeMouseAtCenter(searchBarTextBox, {}, win);
const ownerDocument = browser.ownerDocument;
is(ownerDocument.activeElement, searchBarTextBox, "The search bar has focus");
@ -78,7 +77,7 @@ add_task(async function typeCharWhileProcessingEnter() {
info("Typing a char while processing enter key");
const win = await BrowserTestUtils.openNewBrowserWindow();
const browser = win.gBrowser.selectedBrowser;
const searchBar = win.BrowserSearch.searchBar;
const searchBar = win.document.getElementById("searchbar");
const SEARCH_WORD = "test";
const onLoad = BrowserTestUtils.browserLoaded(
@ -119,7 +118,7 @@ add_task(async function typeCharWhileProcessingEnter() {
add_task(async function keyupEnterWhilePressingMeta() {
const win = await BrowserTestUtils.openNewBrowserWindow();
const browser = win.gBrowser.selectedBrowser;
const searchBar = win.BrowserSearch.searchBar;
const searchBar = win.document.getElementById("searchbar");
info("Keydown Meta+Enter");
searchBar.textbox.focus();
@ -154,7 +153,7 @@ add_task(async function keyupEnterWhilePressingMeta() {
add_task(async function enterOnEmptySearchBar() {
const win = await BrowserTestUtils.openNewBrowserWindow();
const browser = win.gBrowser.selectedBrowser;
const searchBar = win.BrowserSearch.searchBar;
const searchBar = win.document.getElementById("searchbar");
// Enter should be ignored if the searchbar is empty.
info("Pressing Enter");
@ -179,7 +178,7 @@ add_task(async function enterOnEmptySearchBar() {
add_task(async function openSettingsWithEnter() {
const win = await BrowserTestUtils.openNewBrowserWindow();
const searchBar = win.BrowserSearch.searchBar;
const searchBar = win.document.getElementById("searchbar");
const searchPopup = win.document.getElementById("PopupSearchAutoComplete");
const searchButton = searchBar.querySelector(".searchbar-search-button");

View file

@ -6,21 +6,21 @@
// Check that when the searchbar has a specific width, opening a new window
// honours that specific width.
add_task(async function test_searchbar_width_persistence() {
await gCUITestUtils.addSearchBar();
let searchBar = await gCUITestUtils.addSearchBar();
registerCleanupFunction(async function () {
gCUITestUtils.removeSearchBar();
});
// Really, we should use the splitter, but drag/drop is hard and fragile in
// tests, so let's just fake it real quick:
let container = BrowserSearch.searchBar.parentNode;
let container = searchBar.parentNode;
// There's no width attribute set initially, just grab the info from layout:
let oldWidth = container.getBoundingClientRect().width;
let newWidth = "" + Math.round(oldWidth * 2);
container.setAttribute("width", newWidth);
let win = await BrowserTestUtils.openNewBrowserWindow();
let otherBar = win.BrowserSearch.searchBar;
let otherBar = win.document.getElementById("searchbar");
ok(otherBar, "Should have a search bar in the other window");
if (otherBar) {
is(

View file

@ -3,6 +3,10 @@
// This test makes sure that when a page offers many search engines,
// a limited number of add-engine items will be shown in the searchbar.
ChromeUtils.defineESModuleGetters(this, {
OpenSearchManager: "resource:///modules/OpenSearchManager.sys.mjs",
});
const searchPopup = document.getElementById("PopupSearchAutoComplete");
add_setup(async function () {
@ -15,25 +19,26 @@ add_setup(async function () {
});
add_task(async function test() {
let searchbar = BrowserSearch.searchBar;
let searchbar = document.getElementById("searchbar");
let rootDir = getRootDirectory(gTestPath);
let url = rootDir + "tooManyEnginesOffered.html";
await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
// Open the search popup.
let promise = promiseEvent(searchPopup, "popupshown");
let promise2 = promiseEvent(searchPopup.oneOffButtons, "rebuild");
info("Opening search panel");
searchbar.focus();
// In TV we may try opening too early, when the searchbar is not ready yet.
await TestUtils.waitForCondition(
() => BrowserSearch.searchBar.textbox.controller.input,
() => document.getElementById("searchbar").textbox.controller.input,
"Wait for the searchbar controller to connect"
);
EventUtils.synthesizeKey("KEY_ArrowDown");
await promise;
await Promise.all([promise, promise2]);
const addEngineList = searchPopup.oneOffButtons._getAddEngines();
const addEngineList = OpenSearchManager.getEngines(tab.linkedBrowser);
Assert.equal(
addEngineList.length,
6,

View file

@ -106,7 +106,7 @@ async function typeInSearchField(browser, text, fieldName) {
async function searchInSearchbar(inputText, win = window) {
await new Promise(r => waitForFocus(r, win));
let sb = win.BrowserSearch.searchBar;
let sb = win.document.getElementById("searchbar");
// Write the search query in the searchbar.
sb.focus();
sb.value = inputText;

View file

@ -7,6 +7,7 @@ ChromeUtils.defineESModuleGetters(this, {
});
let suggestionEngine;
let searchBar;
function checkHistogramResults(resultIndexes, expected, histogram) {
for (let [i, val] of Object.entries(resultIndexes.values)) {
@ -35,7 +36,7 @@ function checkHistogramResults(resultIndexes, expected, histogram) {
* The options to use for the click.
*/
function clickSearchbarSuggestion(entryName, clickOptions = {}) {
let richlistbox = BrowserSearch.searchBar.textbox.popup.richlistbox;
let richlistbox = searchBar.textbox.popup.richlistbox;
let richlistitem = Array.prototype.find.call(
richlistbox.children,
item => item.getAttribute("ac-value") == entryName
@ -47,7 +48,7 @@ function clickSearchbarSuggestion(entryName, clickOptions = {}) {
}
add_setup(async function () {
await gCUITestUtils.addSearchBar();
searchBar = await gCUITestUtils.addSearchBar();
const url = getRootDirectory(gTestPath) + "telemetrySearchSuggestions.xml";
suggestionEngine = await SearchTestUtils.installOpenSearchEngine({ url });

View file

@ -11,6 +11,10 @@
"use strict";
ChromeUtils.defineESModuleGetters(this, {
SearchUIUtils: "resource:///modules/SearchUIUtils.sys.mjs",
});
const TEST_PROVIDER_INFO = [
{
telemetryId: "example",
@ -294,7 +298,7 @@ add_task(async function test_source_searchbar() {
async () => {
tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
let sb = BrowserSearch.searchBar;
let sb = document.getElementById("searchbar");
// Write the search query in the searchbar.
sb.focus();
sb.value = "searchSuggestion";
@ -332,7 +336,8 @@ add_task(async function test_source_system() {
// This is not quite the same as calling from the commandline, but close
// enough for this test.
BrowserSearch.loadSearchFromCommandLine(
SearchUIUtils.loadSearchFromCommandLine(
window,
"searchSuggestion",
false,
Services.scriptSecurityManager.getSystemPrincipal(),

View file

@ -4,6 +4,7 @@
ChromeUtils.defineESModuleGetters(this, {
ADLINK_CHECK_TIMEOUT_MS:
"resource:///actors/SearchSERPTelemetryChild.sys.mjs",
BrowserSearchTelemetry: "resource:///modules/BrowserSearchTelemetry.sys.mjs",
CATEGORIZATION_SETTINGS: "resource:///modules/SearchSERPTelemetry.sys.mjs",
CustomizableUITestUtils:
"resource://testing-common/CustomizableUITestUtils.sys.mjs",
@ -108,7 +109,7 @@ async function typeInSearchField(browser, text, fieldName) {
async function searchInSearchbar(inputText, win = window) {
await new Promise(r => waitForFocus(r, win));
let sb = win.BrowserSearch.searchBar;
let sb = win.document.getElementById("searchbar");
// Write the search query in the searchbar.
sb.focus();
sb.value = inputText;

View file

@ -5156,7 +5156,9 @@ var SessionStoreInternal = {
}
const sidebarUIState = aWindow.SidebarController.getUIState();
winData.sidebar = structuredClone(sidebarUIState);
if (sidebarUIState) {
winData.sidebar = structuredClone(sidebarUIState);
}
let workspaceID = aWindow.getWorkspaceID();
if (workspaceID) {

View file

@ -72,6 +72,7 @@ export class ReviewCheckerParent extends JSWindowActorParent {
ReviewCheckerParent.SHOPPING_OPTED_IN_PREF,
2
);
this.closeSidebarPanel();
break;
case "CloseShoppingSidebar":
this.closeSidebarPanel();

View file

@ -444,3 +444,44 @@ add_task(async function test_no_reliability_available() {
"surface_no_review_reliability_available"
);
});
add_task(async function test_close_sidebar_after_opt_out() {
await SpecialPowers.pushPrefEnv({
set: [["browser.shopping.experience2023.optedIn", 1]],
});
await BrowserTestUtils.withNewTab(PRODUCT_TEST_URL, async () => {
await SidebarController.show("viewReviewCheckerSidebar");
info("Waiting for sidebar to update.");
await reviewCheckerSidebarUpdated(PRODUCT_TEST_URL);
await withReviewCheckerSidebar(async () => {
let shoppingContainer = await ContentTaskUtils.waitForCondition(
() =>
content.document.querySelector("shopping-container")?.wrappedJSObject,
"Review Checker is loaded."
);
Assert.ok(shoppingContainer, "Review Checker is loaded");
await shoppingContainer.updateComplete;
let shoppingSettings = shoppingContainer.settingsEl;
Assert.ok(shoppingSettings, "Got the shopping-settings element");
let optOutButton = shoppingSettings.optOutButtonEl;
Assert.ok(optOutButton, "There should be an opt-out button");
optOutButton.click();
});
let rcSidebarPanelOpen =
SidebarController.isOpen &&
SidebarController.currentID == "viewReviewCheckerSidebar";
Assert.ok(
!rcSidebarPanelOpen,
"Review Checker panel is closed after pressing the opt-out button"
);
});
await SpecialPowers.popPrefEnv();
});

View file

@ -273,6 +273,10 @@ var SidebarController = {
return this._uninitializing;
},
get inPopup() {
return !window.toolbar.visible;
},
get sidebarContainer() {
if (!this._sidebarContainer) {
// This is the *parent* of the `sidebar-main` component.
@ -493,7 +497,7 @@ var SidebarController = {
},
getUIState() {
return this._state.getProperties();
return this.inPopup ? null : this._state.getProperties();
},
/**
@ -798,6 +802,11 @@ var SidebarController = {
* If loading a sidebar was delayed on startup, start the load now.
*/
async startDelayedLoad() {
if (this.inPopup) {
this._state.launcherVisible = false;
return;
}
let sourceWindow = window.opener;
// No source window means this is the initial window. If we're being
// opened from another window, check that it is one we might open a sidebar
@ -1078,6 +1087,9 @@ var SidebarController = {
},
async handleToolbarButtonClick() {
if (this.inPopup || this.uninitializing) {
return;
}
if (this._animationEnabled && !window.gReduceMotion) {
this._animateSidebarMain();
}
@ -1088,7 +1100,7 @@ var SidebarController = {
* Update `checked` state and tooltip text of the toolbar button.
*/
updateToolbarButton(toolbarButton = this.toolbarButton) {
if (!toolbarButton) {
if (!toolbarButton || this.inPopup) {
return;
}
if (!this.sidebarRevampEnabled) {
@ -1218,6 +1230,9 @@ var SidebarController = {
},
addOrUpdateExtension(commandID, extension) {
if (this.inPopup) {
return;
}
if (this.toolsAndExtensions.has(commandID)) {
// Update existing extension
let extensionToUpdate = this.toolsAndExtensions.get(commandID);
@ -1414,6 +1429,9 @@ var SidebarController = {
* @param {string} commandID
*/
removeExtension(commandID) {
if (this.inPopup) {
return;
}
const sidebar = this.sidebars.get(commandID);
if (!sidebar) {
return;
@ -1439,6 +1457,9 @@ var SidebarController = {
* @returns {Promise<boolean>}
*/
async show(commandID, triggerNode) {
if (this.inPopup) {
return false;
}
if (this.currentID) {
// If there is currently a panel open, we are about to hide it in order
// to show another one, so record a "hide" event on the current panel.
@ -1473,6 +1494,9 @@ var SidebarController = {
* @returns {Promise<boolean>}
*/
async showInitially(commandID) {
if (this.inPopup) {
return false;
}
this._recordPanelToggle(commandID, true);
// Extensions without private window access wont be in the
@ -1780,7 +1804,7 @@ XPCOMUtils.defineLazyPreferenceGetter(
SidebarController.POSITION_START_PREF,
true,
(_aPreference, _previousValue, newValue) => {
if (!SidebarController.uninitializing) {
if (!SidebarController.uninitializing && !SidebarController.inPopup) {
SidebarController.setPosition();
SidebarController.recordPositionSetting(newValue);
}
@ -1816,7 +1840,7 @@ XPCOMUtils.defineLazyPreferenceGetter(
"sidebar.main.tools",
"aichat,syncedtabs,history",
() => {
if (!SidebarController.uninitializing) {
if (!SidebarController.inPopup && !SidebarController.uninitializing) {
SidebarController.refreshTools();
}
}
@ -1827,7 +1851,7 @@ XPCOMUtils.defineLazyPreferenceGetter(
"sidebar.visibility",
"always-show",
(_aPreference, _previousValue, newValue) => {
if (!SidebarController.uninitializing) {
if (!SidebarController.inPopup && !SidebarController.uninitializing) {
SidebarController.recordVisibilitySetting(newValue);
SidebarController._state.revampVisibility = newValue;
SidebarController._state.updateVisibility(
@ -1845,7 +1869,7 @@ XPCOMUtils.defineLazyPreferenceGetter(
"sidebar.verticalTabs",
false,
(_aPreference, _previousValue, newValue) => {
if (!SidebarController.uninitializing) {
if (!SidebarController.uninitializing && !SidebarController.inPopup) {
SidebarController.recordTabsLayoutSetting(newValue);
Services.prefs.setStringPref(
SidebarController.VISIBILITY_PREF,

View file

@ -6,11 +6,9 @@ import { html, when } from "chrome://global/content/vendor/lit.all.mjs";
import { SidebarPage } from "./sidebar-page.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs",
});
const { XPCOMUtils } = ChromeUtils.importESModule(
"resource://gre/modules/XPCOMUtils.sys.mjs"
);
const l10nMap = new Map([
["viewGenaiChatSidebar", "sidebar-menu-genai-chat-label"],
@ -20,23 +18,52 @@ const l10nMap = new Map([
["viewBookmarksSidebar", "sidebar-menu-bookmarks-label"],
]);
const VISIBILITY_SETTING_PREF = "sidebar.visibility";
const POSITION_SETTING_PREF = "sidebar.position_start";
const TAB_DIRECTION_SETTING_PREF = "sidebar.verticalTabs";
export class SidebarCustomize extends SidebarPage {
constructor() {
super();
this.activeExtIndex = 0;
this.visibility = Services.prefs.getStringPref(
XPCOMUtils.defineLazyPreferenceGetter(
this.#prefValues,
"visibility",
VISIBILITY_SETTING_PREF,
"always-show"
"always-show",
(_aPreference, _previousValue, newValue) => {
this.visibility = newValue;
}
);
this.verticalTabsEnabled = lazy.CustomizableUI.verticalTabsEnabled;
XPCOMUtils.defineLazyPreferenceGetter(
this.#prefValues,
"isPositionStart",
POSITION_SETTING_PREF,
true,
(_aPreference, _previousValue, newValue) => {
this.isPositionStart = newValue;
}
);
XPCOMUtils.defineLazyPreferenceGetter(
this.#prefValues,
"verticalTabsEnabled",
TAB_DIRECTION_SETTING_PREF,
false,
(_aPreference, _previousValue, newValue) => {
this.verticalTabsEnabled = newValue;
}
);
this.visibility = this.#prefValues.visibility;
this.isPositionStart = this.#prefValues.isPositionStart;
this.verticalTabsEnabled = this.#prefValues.verticalTabsEnabled;
this.boundObserve = (...args) => this.observe(...args);
}
#prefValues = {};
static properties = {
activeExtIndex: { type: Number },
visibility: { type: String },
isPositionStart: { type: Boolean },
verticalTabsEnabled: { type: Boolean },
};
@ -53,8 +80,6 @@ export class SidebarCustomize extends SidebarPage {
this.getWindow().addEventListener("SidebarItemAdded", this);
this.getWindow().addEventListener("SidebarItemChanged", this);
this.getWindow().addEventListener("SidebarItemRemoved", this);
Services.prefs.addObserver(VISIBILITY_SETTING_PREF, this.boundObserve);
Services.obs.addObserver(this.boundObserve, "tabstrip-orientation-change");
}
disconnectedCallback() {
@ -62,33 +87,6 @@ export class SidebarCustomize extends SidebarPage {
this.getWindow().removeEventListener("SidebarItemAdded", this);
this.getWindow().removeEventListener("SidebarItemChanged", this);
this.getWindow().removeEventListener("SidebarItemRemoved", this);
Services.obs.removeObserver(
this.boundObserve,
"tabstrip-orientation-change"
);
Services.prefs.removeObserver(VISIBILITY_SETTING_PREF, this.boundObserve);
}
observe(subject, topic, prefName) {
switch (topic) {
case "nsPref:changed":
switch (prefName) {
case VISIBILITY_SETTING_PREF:
this.visibility = Services.prefs.getStringPref(
VISIBILITY_SETTING_PREF,
"always-show"
);
break;
}
break;
case "tabstrip-orientation-change":
this.verticalTabsEnabled = lazy.CustomizableUI.verticalTabsEnabled;
break;
}
}
get sidebarLauncher() {
return this.getWindow().document.querySelector("sidebar-launcher");
}
get fluentStrings() {
@ -212,9 +210,7 @@ export class SidebarCustomize extends SidebarPage {
SidebarController.reversePosition();
Glean.sidebarCustomize.sidebarPosition.record({
position:
SidebarController._positionStart !== this.getWindow().RTL_UI
? "left"
: "right",
this.isPositionStart !== this.getWindow().RTL_UI ? "left" : "right",
});
}
@ -280,7 +276,7 @@ export class SidebarCustomize extends SidebarPage {
name="position"
data-l10n-id=${document.dir == "rtl" ? "sidebar-show-on-the-left" : "sidebar-show-on-the-right"}
@change=${this.reversePosition}
?checked=${!this.getWindow().SidebarController._positionStart}
?checked=${!this.isPositionStart}
></moz-checkbox>
</moz-fieldset>
<moz-fieldset class="customize-group" data-l10n-id="sidebar-customize-firefox-tools-header">

View file

@ -6,8 +6,15 @@
requestLongerTimeout(2);
const SIDEBAR_VISIBILITY_PREF = "sidebar.visibility";
const POSITION_SETTING_PREF = "sidebar.position_start";
const TAB_DIRECTION_PREF = "sidebar.verticalTabs";
registerCleanupFunction(() => {
Services.prefs.clearUserPref(SIDEBAR_VISIBILITY_PREF);
Services.prefs.clearUserPref(POSITION_SETTING_PREF);
Services.prefs.clearUserPref(TAB_DIRECTION_PREF);
});
async function showCustomizePanel(win) {
await win.SidebarController.show("viewCustomizeSidebar");
const document = win.SidebarController.browser.contentDocument;
@ -302,3 +309,41 @@ add_task(async function test_keyboard_navigation_away_from_settings_link() {
await BrowserTestUtils.closeWindow(win);
});
add_task(async function test_settings_synchronized_across_windows() {
const panel = await showCustomizePanel(window);
const { contentWindow } = SidebarController.browser;
const newWindow = await BrowserTestUtils.openNewBrowserWindow();
const newPanel = await showCustomizePanel(newWindow);
info("Update vertical tabs settings.");
EventUtils.synthesizeMouseAtCenter(
panel.verticalTabsInput,
{},
contentWindow
);
await newPanel.updateComplete;
ok(
newPanel.verticalTabsInput.checked,
"New window shows the vertical tabs setting."
);
info("Update visibility settings.");
EventUtils.synthesizeMouseAtCenter(panel.visibilityInput, {}, contentWindow);
await newPanel.updateComplete;
ok(
newPanel.visibilityInput.checked,
"New window shows the updated visibility setting."
);
info("Update position settings.");
EventUtils.synthesizeMouseAtCenter(panel.positionInput, {}, contentWindow);
await newPanel.updateComplete;
ok(
newPanel.positionInput.checked,
"New window shows the updated position setting."
);
SidebarController.hide();
await BrowserTestUtils.closeWindow(newWindow);
});

View file

@ -16,7 +16,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
});
add_setup(async () => {
Services.fog.testResetFOG();
SidebarController.init();
await TestUtils.waitForTick();
});

View file

@ -47,6 +47,12 @@ async function test_sidebar_hidden_on_popup() {
"All View > Sidebar menu items are disabled on popup"
);
// Try to show the sidebar launcher using the keyboard shortcut
popup.document.getElementById("toggleSidebarKb").doCommand();
// Give popup window a chance to display the sidebar (which it shouldn't).
await new Promise(resolve => ChromeUtils.idleDispatch(resolve));
ok(popupSidebar.hidden, "Sidebar is still hidden on popup window");
// Bug 1925451 - Check that vertical tabs are visible in new window after opening popup
await BrowserTestUtils.closeWindow(popup);
const win2 = await BrowserTestUtils.openNewBrowserWindow();

View file

@ -415,6 +415,4 @@ add_task(async function test_keyboard_shortcut() {
"false",
"Glean event recorded that sidebar was collapsed/hidden with keyboard shortcut"
);
Services.fog.testResetFOG();
});

View file

@ -141,3 +141,8 @@ async function toggleSidebarPanel(win, commandID) {
win.SidebarController.toggle(commandID);
await promiseFocused;
}
// Reset the Glean events after each test.
registerCleanupFunction(() => {
Services.fog.testResetFOG();
});

View file

@ -11,17 +11,19 @@ const { SidebarState } = ChromeUtils.importESModule(
"resource:///modules/SidebarState.sys.mjs"
);
const mockElement = { toggleAttribute: sinon.stub() };
const mockElement = {
setAttribute(name, value) {
this[name] = value;
},
style: { width: "200px" },
toggleAttribute: sinon.stub(),
};
const mockGlobal = {
document: { getElementById: () => mockElement },
gBrowser: { tabContainer: mockElement },
};
const mockPanel = {
setAttribute: (name, value) => (mockPanel[name] = value),
style: { width: "200px" },
};
const mockController = {
_box: mockPanel,
_box: mockElement,
showInitially: sinon.stub(),
sidebarContainer: { ownerGlobal: mockGlobal },
sidebarMain: mockElement,

View file

@ -156,6 +156,12 @@
"browser.tabs.unloadTabInContextMenu",
false
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"_notificationEnableDelay",
"security.notification_enable_delay",
500
);
if (AppConstants.MOZ_CRASHREPORTER) {
ChromeUtils.defineESModuleGetters(this, {
@ -908,7 +914,7 @@
if (browser == this.selectedBrowser) {
this._updateVisibleNotificationBox(browser);
}
});
}, this._notificationEnableDelay);
}
return browser._notificationBox;
}
@ -3693,6 +3699,9 @@
let lastRelatedTab =
openerTab && this._lastRelatedTabMap.get(openerTab);
let previousTab = lastRelatedTab || openerTab || this.selectedTab;
if (!tabGroup) {
tabGroup = previousTab.group;
}
if (
Services.prefs.getBoolPref(
"browser.tabs.insertAfterCurrentExceptPinned"
@ -5781,6 +5790,10 @@
initialBrowsingContextGroupId: linkedBrowser.browsingContext?.group.id,
skipAnimation: true,
index: aIndex,
tabGroup:
typeof aIndex == "number" && aIndex > -1
? this.tabs.at(aIndex)?.group
: null,
createLazyBrowser,
};

View file

@ -8,9 +8,9 @@ add_setup(async function () {
});
});
function createManyTabs(number) {
function createManyTabs(number, win = window) {
return Array.from({ length: number }, () => {
return BrowserTestUtils.addTab(gBrowser, "about:blank", {
return BrowserTestUtils.addTab(win.gBrowser, "about:blank", {
skipAnimation: true,
});
});
@ -2007,10 +2007,54 @@ add_task(async function test_pinFirstGroupedTab() {
gBrowser.pinTab(tabs[0]);
Assert.ok(tabs[0].pinned, "pinned first tab of group");
Assert.ok(!tabs[0].group, "pinning first tab ungroups it");
gBrowser.unpinTab(tabs[0]);
await removeTabGroup(group);
});
add_task(async function test_adoptTab() {
let newWin = await BrowserTestUtils.openNewBrowserWindow();
let tabs = createManyTabs(3, newWin);
let group = newWin.gBrowser.addTabGroup(tabs);
let otherWinTab = BrowserTestUtils.addTab(gBrowser, "about:robots", {
skipAnimation: true,
});
let adoptedTab = newWin.gBrowser.adoptTab(otherWinTab, 1);
Assert.equal(adoptedTab._tPos, 1, "tab adopted into expected position");
Assert.equal(adoptedTab.group, group, "tab adopted into tab group");
await BrowserTestUtils.closeWindow(newWin, { animate: false });
});
add_task(async function test_insertAfterCurrent() {
await SpecialPowers.pushPrefEnv({
set: [["browser.tabs.insertAfterCurrent", true]],
});
let group = gBrowser.addTabGroup([gBrowser.selectedTab]);
let extraTab = BrowserTestUtils.addTab(gBrowser, "about:robots", {
skipAnimation: true,
});
let newTabPromise = BrowserTestUtils.waitForEvent(window, "TabOpen");
BrowserCommands.openTab();
let { target: newTab } = await newTabPromise;
Assert.equal(newTab.group, group, "new tab added to current group");
Assert.equal(
group.tabs.indexOf(newTab),
1,
"new tab added as second tab to group"
);
group.ungroupTabs();
BrowserTestUtils.removeTab(newTab);
BrowserTestUtils.removeTab(extraTab);
await SpecialPowers.popPrefEnv();
});
add_task(async function test_bug1936015() {
// Checks that groups are properly deleted if the group was created before
// the user switches between vertical and horizontal tabs.

View file

@ -399,11 +399,14 @@ add_task(async function test_tabGroupsViewContextMenu_openGroups() {
let groupId = "test-group";
let otherWindow = await BrowserTestUtils.openNewBrowserWindow();
let initialTab = otherWindow.gBrowser.tabs[0];
await createTestGroup({
id: groupId,
label: "Test Group",
targetWin: otherWindow,
});
// remove the initial tab to test context menu disabling
BrowserTestUtils.removeTab(initialTab, { animate: false });
otherWindow.gTabsPanel.init();
let allTabsMenu = await openTabsMenu();
@ -419,6 +422,13 @@ add_task(async function test_tabGroupsViewContextMenu_openGroups() {
gBrowser.tabContainer,
"TabGroupCreate"
);
Assert.ok(
menu.querySelector("#open-tab-group-context-menu_moveToNewWindow").disabled,
"'Move to New Window' is disabled"
);
menu.hidePopup();
await addTabTo(otherWindow.gBrowser);
menu = await getContextMenu(group1MenuItem, "open-tab-group-context-menu");
menu.querySelector("#open-tab-group-context-menu_moveToThisWindow").click();
await waitForGroup;
@ -451,6 +461,11 @@ add_task(async function test_tabGroupsViewContextMenu_openGroups() {
gBrowser.tabContainer,
"TabGroupRemoved"
);
Assert.ok(
!menu.querySelector("#open-tab-group-context-menu_moveToNewWindow")
.disabled,
"'Move to New Window' is enabled"
);
let waitForWindow = BrowserTestUtils.waitForNewWindow();
menu.querySelector("#open-tab-group-context-menu_moveToNewWindow").click();
let menuHidden = BrowserTestUtils.waitForPopupEvent(menu, "hidden");

View file

@ -14,9 +14,9 @@ add_task(async function test_blocks_event_handlers() {
});
is(
Glean.security.cspViolationBrowser.testGetValue(),
Glean.security.cspViolationInternalPage.testGetValue(),
null,
`No telemetry should have been recorded yet for cspViolationBrowser`
`No telemetry should have been recorded yet for cspViolationInternalPage`
);
window.dont_run_me = function () {
@ -48,17 +48,27 @@ add_task(async function test_blocks_event_handlers() {
"sourceFile matches"
);
let testValue = Glean.security.cspViolationBrowser.testGetValue();
let testValue = Glean.security.cspViolationInternalPage.testGetValue();
is(testValue.length, 1, "Should have telemetry for one violation");
let extra = testValue[0].extra;
is(extra.directive, "script-src-attr", "violation's `directive` is correct");
is(extra.selftype, "chromeuri", "violation's `selftype` is correct");
is(
extra.selfdetails,
"chrome://browser/content/browser.xhtml",
"violation's `selfdetails` is correct"
);
is(extra.sourcetype, "chromeuri", "violation's `sourcetype` is correct");
ok(
extra.sourcedetails.endsWith("browser_csp_blocks_event_handlers.js"),
"violation's `sourcedetails` is correct"
);
is(extra.blockeduritype, "inline", "violation's `blockeduritype` is correct");
is(extra.blockeduridetails, "", "violation's `blockeduridetails` is correct");
is(
extra.blockeduridetails,
undefined,
"violation's `blockeduridetails` is correct"
);
is(extra.linenumber, "31", "violation's `linenumber` is correct");
is(extra.columnnumber, "8", "violation's `columnnumber` is correct");
is(extra.sample, "dont_run_me()", "violation's sample is correct");

View file

@ -2,7 +2,10 @@
* 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 { TippyTopProvider } from "resource:///modules/topsites/TippyTopProvider.sys.mjs";
import {
getDomain,
TippyTopProvider,
} from "resource:///modules/topsites/TippyTopProvider.sys.mjs";
import { Dedupe } from "resource:///modules/Dedupe.sys.mjs";
import { TOP_SITES_MAX_SITES_PER_ROW } from "resource:///modules/topsites/constants.mjs";
import {
@ -14,9 +17,9 @@ import {
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
FaviconFeed: "resource://activity-stream/lib/FaviconFeed.sys.mjs",
FilterAdult: "resource:///modules/FilterAdult.sys.mjs",
LinksCache: "resource:///modules/LinksCache.sys.mjs",
NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
Region: "resource://gre/modules/Region.sys.mjs",
@ -106,7 +109,7 @@ class _TopSites {
"links",
[...PINNED_FAVICON_PROPS_TO_MIGRATE]
);
this.faviconFeed = new lazy.FaviconFeed();
this._faviconProvider = new FaviconProvider();
this.handlePlacesEvents = this.handlePlacesEvents.bind(this);
}
@ -868,7 +871,7 @@ class _TopSites {
}
_requestRichIcon(url) {
this.faviconFeed.fetchIcon(url);
this._faviconProvider.fetchIcon(url);
}
/**
@ -1124,4 +1127,256 @@ export function insertPinned(links, pinned) {
return newLinks;
}
/**
* FaviconProvider class handles the retrieval and management of favicons
* for TopSites.
*/
export class FaviconProvider {
constructor() {
this._queryForRedirects = new Set();
}
/**
* fetchIcon attempts to fetch a rich icon for the given url from two sources.
* First, it looks up the tippy top feed, if it's still missing, then it queries
* the places for rich icon with its most recent visit in order to deal with
* the redirected visit. See Bug 1421428 for more details.
*/
async fetchIcon(url) {
// Avoid initializing and fetching icons if prefs are turned off
if (!this.shouldFetchIcons) {
return;
}
const site = await this.getSite(getDomain(url));
if (!site) {
if (!this._queryForRedirects.has(url)) {
this._queryForRedirects.add(url);
Services.tm.idleDispatchToMainThread(() =>
this.fetchIconFromRedirects(url)
);
}
return;
}
let iconUri = Services.io.newURI(site.image_url);
// The #tippytop is to be able to identify them for telemetry.
iconUri = iconUri.mutate().setRef("tippytop").finalize();
await this.#setFaviconForPage(Services.io.newURI(url), iconUri);
}
/**
* Get the site tippy top data from Remote Settings.
*/
async getSite(domain) {
const sites = await this.tippyTop.get({
filters: { domain },
syncIfEmpty: false,
});
return sites.length ? sites[0] : null;
}
/**
* Get the tippy top collection from Remote Settings.
*/
get tippyTop() {
if (!this._tippyTop) {
this._tippyTop = lazy.RemoteSettings("tippytop");
}
return this._tippyTop;
}
/**
* Determine if we should be fetching and saving icons.
*/
get shouldFetchIcons() {
return Services.prefs.getBoolPref("browser.chrome.site_icons");
}
/**
* Get favicon info (uri and size) for a uri from Places.
*
* @param {nsIURI} uri
* Page to check for favicon data
* @returns {object}
* A promise of an object (possibly null) containing the data
*/
getFaviconInfo(uri) {
return new Promise(resolve =>
lazy.PlacesUtils.favicons.getFaviconDataForPage(
uri,
// Package up the icon data in an object if we have it; otherwise null
(iconUri, faviconLength, favicon, mimeType, faviconSize) =>
resolve(iconUri ? { iconUri, faviconSize } : null),
lazy.NewTabUtils.activityStreamProvider.THUMB_FAVICON_SIZE
)
);
}
/**
* Fetch favicon for a url by following its redirects in Places.
*
* This can improve the rich icon coverage for Top Sites since Places only
* associates the favicon to the final url if the original one gets redirected.
* Note this is not an urgent request, hence it is dispatched to the main
* thread idle handler to avoid any possible performance impact.
*/
async fetchIconFromRedirects(url) {
const visitPaths = await this.#fetchVisitPaths(url);
if (visitPaths.length > 1) {
const lastVisit = visitPaths.pop();
const redirectedUri = Services.io.newURI(lastVisit.url);
const iconInfo = await this.getFaviconInfo(redirectedUri);
if (iconInfo?.faviconSize >= MIN_FAVICON_SIZE) {
try {
lazy.PlacesUtils.favicons.copyFavicons(
redirectedUri,
Services.io.newURI(url),
lazy.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE
);
} catch (ex) {
console.error(`Failed to copy favicon [${ex}]`);
}
}
}
}
/**
* Get favicon data for given URL from network.
*
* @param {nsIURI} faviconURI
* nsIURI for the favicon.
* @returns {nsIURI} data URL
*/
async getFaviconDataURLFromNetwork(faviconURI) {
let channel = lazy.NetUtil.newChannel({
uri: faviconURI,
loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
securityFlags:
Ci.nsILoadInfo.SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT |
Ci.nsILoadInfo.SEC_ALLOW_CHROME |
Ci.nsILoadInfo.SEC_DISALLOW_SCRIPT,
contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE_FAVICON,
});
let resolver = Promise.withResolvers();
lazy.NetUtil.asyncFetch(channel, async (input, status, request) => {
if (!Components.isSuccessCode(status)) {
resolver.resolve();
return;
}
try {
let data = lazy.NetUtil.readInputStream(input, input.available());
let { contentType } = request.QueryInterface(Ci.nsIChannel);
input.close();
let buffer = new Uint8ClampedArray(data);
let blob = new Blob([buffer], { type: contentType });
let dataURL = await new Promise((resolve, reject) => {
let reader = new FileReader();
reader.addEventListener("load", () => resolve(reader.result));
reader.addEventListener("error", reject);
reader.readAsDataURL(blob);
});
resolver.resolve(Services.io.newURI(dataURL));
} catch (e) {
resolver.reject(e);
}
});
return resolver.promise;
}
/**
* Set favicon for page.
*
* @param {nsIURI} pageURI
* @param {nsIURI} faviconURI
*/
async #setFaviconForPage(pageURI, faviconURI) {
try {
// If the given faviconURI is data URL, set it as is.
if (faviconURI.schemeIs("data")) {
lazy.PlacesUtils.favicons
.setFaviconForPage(pageURI, faviconURI, faviconURI)
.catch(console.error);
return;
}
// Try to find the favicon data from DB.
const faviconInfo = await this.getFaviconInfo(pageURI);
if (faviconInfo?.faviconSize) {
// As valid favicon data is already stored for the page,
// we don't have to update.
return;
}
// Otherwise, fetch from network.
lazy.PlacesUtils.favicons
.setFaviconForPage(
pageURI,
faviconURI,
await this.getFaviconDataURLFromNetwork(faviconURI)
)
.catch(console.error);
} catch (ex) {
console.error(`Failed to set favicon for page:${ex}`);
}
}
/**
* Fetches visit paths for a given URL from its most recent visit in Places.
*
* Note that this includes the URL itself as well as all the following
* permenent&temporary redirected URLs if any.
*
* @param {string} url
* a URL string
*
* @returns {Array} Returns an array containing objects as
* {int} visit_id: ID of the visit in moz_historyvisits.
* {String} url: URL of the redirected URL.
*/
async #fetchVisitPaths(url) {
const query = `
WITH RECURSIVE path(visit_id)
AS (
SELECT v.id
FROM moz_places h
JOIN moz_historyvisits v
ON v.place_id = h.id
WHERE h.url_hash = hash(:url) AND h.url = :url
AND v.visit_date = h.last_visit_date
UNION
SELECT id
FROM moz_historyvisits
JOIN path
ON visit_id = from_visit
WHERE visit_type IN
(${lazy.PlacesUtils.history.TRANSITIONS.REDIRECT_PERMANENT},
${lazy.PlacesUtils.history.TRANSITIONS.REDIRECT_TEMPORARY})
)
SELECT visit_id, (
SELECT (
SELECT url
FROM moz_places
WHERE id = place_id)
FROM moz_historyvisits
WHERE id = visit_id) AS url
FROM path
`;
const visits =
await lazy.NewTabUtils.activityStreamProvider.executePlacesQuery(query, {
columns: ["visit_id", "url"],
params: { url },
});
return visits;
}
}
export const TopSites = new _TopSites();

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