Update On Wed Feb 5 19:53:07 CET 2025
8
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -533,6 +533,7 @@ function accessibleTask(doc, task, options = {}) {
|
|||
gBrowser,
|
||||
"about:blank",
|
||||
{
|
||||
allowInheritPrincipal: true,
|
||||
forceNotRemote: true,
|
||||
}
|
||||
);
|
||||
|
|
|
@ -3,6 +3,7 @@ subsuite = "a11y"
|
|||
skip-if = [
|
||||
"os != 'win'",
|
||||
"headless",
|
||||
"artifact",
|
||||
]
|
||||
support-files = ["head.js"]
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ subsuite = "a11y"
|
|||
skip-if = [
|
||||
"os != 'win'",
|
||||
"headless",
|
||||
"artifact",
|
||||
]
|
||||
support-files = [
|
||||
"head.js",
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -206,7 +206,7 @@ document.addEventListener(
|
|||
gProfiles.handleCommand(event);
|
||||
break;
|
||||
case "Tools:Search":
|
||||
BrowserSearch.webSearch();
|
||||
SearchUIUtils.webSearch(window);
|
||||
break;
|
||||
case "Tools:Downloads":
|
||||
BrowserCommands.downloadsUI();
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
|
||||
|
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 8 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 48 KiB |
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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">
|
||||
|
||||
|
|
|
@ -57,3 +57,5 @@ let gSyncChooseWhatToSync = {
|
|||
}
|
||||
},
|
||||
};
|
||||
|
||||
window.addEventListener("load", () => gSyncChooseWhatToSync.init());
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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."
|
||||
);
|
||||
});
|
||||
|
|
214
browser/components/search/OpenSearchManager.sys.mjs
Normal 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();
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
});
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
EXTRA_JS_MODULES += [
|
||||
"BrowserSearchTelemetry.sys.mjs",
|
||||
"OpenSearchManager.sys.mjs",
|
||||
"SearchOneOffs.sys.mjs",
|
||||
"SearchSERPTelemetry.sys.mjs",
|
||||
"SearchUIUtils.sys.mjs",
|
||||
|
|
|
@ -48,6 +48,8 @@ support-files = [
|
|||
|
||||
["browser_defaultPrivate_nimbus.js"]
|
||||
|
||||
["browser_displayNotification.js"]
|
||||
|
||||
["browser_google_behavior.js"]
|
||||
|
||||
["browser_hiddenOneOffs_diacritics.js"]
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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();
|
||||
});
|
|
@ -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();
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 });
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -72,6 +72,7 @@ export class ReviewCheckerParent extends JSWindowActorParent {
|
|||
ReviewCheckerParent.SHOPPING_OPTED_IN_PREF,
|
||||
2
|
||||
);
|
||||
this.closeSidebarPanel();
|
||||
break;
|
||||
case "CloseShoppingSidebar":
|
||||
this.closeSidebarPanel();
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -16,7 +16,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
});
|
||||
|
||||
add_setup(async () => {
|
||||
Services.fog.testResetFOG();
|
||||
SidebarController.init();
|
||||
await TestUtils.waitForTick();
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
|
|