mirror of
https://github.com/zen-browser/desktop.git
synced 2026-05-20 03:51:20 +00:00
Merge commit from fork
This commit is contained in:
6
prefs/zen/live-folders.yaml
Normal file
6
prefs/zen/live-folders.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# 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/.
|
||||
|
||||
- name: zen.live-folders.github.skip-new-pr-ui-check
|
||||
value: false
|
||||
@@ -34,7 +34,11 @@ export class nsGithubLiveFolderProvider extends nsZenLiveFolderProvider {
|
||||
|
||||
if (
|
||||
this.state.type === "pull-requests" &&
|
||||
typeof this.state.isJsonApi !== "boolean"
|
||||
typeof this.state.isJsonApi !== "boolean" &&
|
||||
!Services.prefs.getBoolPref(
|
||||
"zen.live-folders.github.skip-new-pr-ui-check",
|
||||
false
|
||||
)
|
||||
) {
|
||||
const { text, status } = await this.fetch(this.state.url, {
|
||||
headers: {
|
||||
|
||||
@@ -62,6 +62,14 @@ export class nsRssLiveFolderProvider extends nsZenLiveFolderProvider {
|
||||
if (!item.url || !item.date) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const parsed = Services.io.newURI(item.url);
|
||||
if (parsed.scheme !== "http" && parsed.scheme !== "https") {
|
||||
return false;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
if (!this.state.timeRange) {
|
||||
return true;
|
||||
}
|
||||
@@ -73,10 +81,9 @@ export class nsRssLiveFolderProvider extends nsZenLiveFolderProvider {
|
||||
for (let item of items) {
|
||||
if (item.url) {
|
||||
try {
|
||||
const url = new URL(item.url);
|
||||
const favicon = await lazy.PlacesUtils.favicons.getFaviconForPage(
|
||||
Services.io.newURI(url.href)
|
||||
);
|
||||
const url = Services.io.newURI(item.url);
|
||||
const favicon =
|
||||
await lazy.PlacesUtils.favicons.getFaviconForPage(url);
|
||||
item.icon =
|
||||
favicon?.dataURI.spec ||
|
||||
this.manager.window.gZenEmojiPicker.getSVGURL("logo-rss.svg");
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
[DEFAULT]
|
||||
prefs = ["zen.live-folders.github.skip-new-pr-ui-check=true"]
|
||||
|
||||
["browser_github_live_folder.js"]
|
||||
|
||||
|
||||
@@ -65,10 +65,10 @@ add_task(async function test_fetch_items_url_construction() {
|
||||
const fetchedUrl = new URL(instance.fetch.firstCall.args[0]);
|
||||
const searchParams = fetchedUrl.searchParams;
|
||||
|
||||
Assert.ok(fetchedUrl.href.startsWith("https://github.com/issues/assigned"));
|
||||
Assert.ok(fetchedUrl.href.startsWith("https://github.com/pulls"));
|
||||
|
||||
const query = searchParams.get("q");
|
||||
Assert.ok(query.includes("state:open"), "Should include state:open");
|
||||
Assert.ok(query.includes("is:open"), "Should include state:open");
|
||||
Assert.ok(query.includes("is:pr"), "Should include is:PR");
|
||||
Assert.ok(query.includes("author:@me"), "Should include author:@me");
|
||||
Assert.ok(!query.includes("assignee:@me"), "Should NOT include assignee:@me");
|
||||
@@ -176,3 +176,169 @@ add_task(async function test_fetch_network_error() {
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
add_task(async function test_no_filter_enabled_returns_error() {
|
||||
info(
|
||||
"should short-circuit and return the no-filter error when every option is off"
|
||||
);
|
||||
|
||||
let sandbox = sinon.createSandbox();
|
||||
let instance = getGithubProviderForTest(sandbox, {
|
||||
type: "pull-requests",
|
||||
authorMe: false,
|
||||
assignedMe: false,
|
||||
reviewRequested: false,
|
||||
});
|
||||
|
||||
const result = await instance.fetchItems();
|
||||
|
||||
Assert.equal(
|
||||
result,
|
||||
"zen-live-folder-github-no-filter",
|
||||
"Should return the no-filter error id"
|
||||
);
|
||||
Assert.ok(
|
||||
instance.fetch.notCalled,
|
||||
"Should not issue a fetch when no filter is enabled"
|
||||
);
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
add_task(async function test_404_returns_no_auth() {
|
||||
info("should treat a 404 as a missing-auth signal");
|
||||
|
||||
let sandbox = sinon.createSandbox();
|
||||
let instance = getGithubProviderForTest(sandbox, {
|
||||
type: "pull-requests",
|
||||
authorMe: true,
|
||||
});
|
||||
|
||||
instance.fetch.resolves({ status: 404, text: "" });
|
||||
|
||||
const result = await instance.fetchItems();
|
||||
|
||||
Assert.equal(
|
||||
result,
|
||||
"zen-live-folder-github-no-auth",
|
||||
"Should return the no-auth error id"
|
||||
);
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
add_task(async function test_repo_excludes_emit_negative_repo_filters() {
|
||||
info("should add -repo:<repo> clauses for each excluded repository");
|
||||
|
||||
let sandbox = sinon.createSandbox();
|
||||
let instance = getGithubProviderForTest(sandbox, {
|
||||
type: "pull-requests",
|
||||
authorMe: true,
|
||||
assignedMe: false,
|
||||
reviewRequested: false,
|
||||
repoExcludes: ["zen-browser/desktop", "foo/bar"],
|
||||
});
|
||||
|
||||
instance.fetch.resolves({ status: 200, text: "<html></html>" });
|
||||
|
||||
await instance.fetchItems();
|
||||
|
||||
const fetchedUrl = new URL(instance.fetch.firstCall.args[0]);
|
||||
const query = fetchedUrl.searchParams.get("q");
|
||||
|
||||
Assert.ok(
|
||||
query.includes("-repo:zen-browser/desktop"),
|
||||
"Should exclude zen-browser/desktop from the query"
|
||||
);
|
||||
Assert.ok(
|
||||
query.includes("-repo:foo/bar"),
|
||||
"Should exclude foo/bar from the query"
|
||||
);
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
add_task(async function test_pull_requests_json_api_parsing() {
|
||||
info("should parse the new PR dashboard JSON payload");
|
||||
|
||||
let sandbox = sinon.createSandbox();
|
||||
let instance = getGithubProviderForTest(sandbox, {
|
||||
type: "pull-requests",
|
||||
authorMe: true,
|
||||
});
|
||||
|
||||
const payload = JSON.stringify({
|
||||
payload: {
|
||||
pullsDashboardSurfaceContentRoute: {
|
||||
results: [
|
||||
{
|
||||
repoNameWithOwner: "zen-browser/desktop",
|
||||
number: 42,
|
||||
title: "Add live folders",
|
||||
author: { displayLogin: "alice" },
|
||||
permalink: "https://github.com/zen-browser/desktop/pull/42",
|
||||
},
|
||||
{
|
||||
repoNameWithOwner: "zen-browser/desktop",
|
||||
number: 43,
|
||||
title: "Fix bug",
|
||||
author: { displayLogin: "bob" },
|
||||
permalink: "https://github.com/zen-browser/desktop/pull/43",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
instance.fetch.resolves({ status: 200, text: payload });
|
||||
|
||||
const items = await instance.fetchItems();
|
||||
|
||||
Assert.equal(items.length, 2, "Should parse two PRs from the JSON payload");
|
||||
Assert.equal(items[0].id, "zen-browser/desktop#42");
|
||||
Assert.equal(items[0].title, "Add live folders");
|
||||
Assert.equal(items[0].subtitle, "alice");
|
||||
Assert.equal(items[0].url, "https://github.com/zen-browser/desktop/pull/42");
|
||||
Assert.equal(items[1].id, "zen-browser/desktop#43");
|
||||
Assert.ok(
|
||||
instance.state.isJsonApi,
|
||||
"Should mark the provider as using the JSON API"
|
||||
);
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
add_task(async function test_pull_requests_json_api_falls_back_to_html() {
|
||||
info(
|
||||
"should fall back to HTML parsing when an HTML response arrives unexpectedly"
|
||||
);
|
||||
|
||||
let sandbox = sinon.createSandbox();
|
||||
let instance = getGithubProviderForTest(sandbox, {
|
||||
type: "pull-requests",
|
||||
authorMe: true,
|
||||
});
|
||||
|
||||
// Simulate a previous fetch having locked the provider into JSON-API mode.
|
||||
instance.state.isJsonApi = true;
|
||||
|
||||
instance.fetch.resolves({
|
||||
status: 200,
|
||||
text: "<html><body>not JSON</body></html>",
|
||||
});
|
||||
|
||||
const result = await instance.fetchItems();
|
||||
|
||||
Assert.equal(
|
||||
instance.state.isJsonApi,
|
||||
false,
|
||||
"Should clear isJsonApi after seeing a non-JSON response"
|
||||
);
|
||||
Assert.equal(
|
||||
result,
|
||||
"zen-live-folder-failed-fetch",
|
||||
"Should surface a fetch error so the user is prompted to retry"
|
||||
);
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
@@ -162,9 +162,9 @@ add_task(async function test_max_items_limit() {
|
||||
const rssXml = `
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<item><title>1</title><link>1</link><pubDate>${date}</pubDate></item>
|
||||
<item><title>2</title><link>2</link><pubDate>${date}</pubDate></item>
|
||||
<item><title>3</title><link>3</link><pubDate>${date}</pubDate></item>
|
||||
<item><title>1</title><link>https://example.com/1</link><pubDate>${date}</pubDate></item>
|
||||
<item><title>2</title><link>https://example.com/2</link><pubDate>${date}</pubDate></item>
|
||||
<item><title>3</title><link>https://example.com/3</link><pubDate>${date}</pubDate></item>
|
||||
</channel>
|
||||
</rss>
|
||||
`;
|
||||
@@ -218,6 +218,113 @@ add_task(async function test_invalid_dates() {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
add_task(async function test_item_url_scheme_filtering() {
|
||||
info("should drop items whose link uses a non-http(s) scheme");
|
||||
|
||||
let sandbox = sinon.createSandbox();
|
||||
let instance = getRssProviderForTest(sandbox, { timeRange: 0 });
|
||||
|
||||
const date = new Date().toUTCString();
|
||||
const rssXml = `
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<item>
|
||||
<title>JavaScript scheme</title>
|
||||
<link>javascript:alert(1)</link>
|
||||
<pubDate>${date}</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title>Data scheme</title>
|
||||
<link>data:text/html,<script>alert(1)</script></link>
|
||||
<pubDate>${date}</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title>File scheme</title>
|
||||
<link>file:///etc/passwd</link>
|
||||
<pubDate>${date}</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title>about: scheme</title>
|
||||
<link>about:config</link>
|
||||
<pubDate>${date}</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title>chrome: scheme</title>
|
||||
<link>chrome://browser/content/browser.xhtml</link>
|
||||
<pubDate>${date}</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title>Invalid URL</title>
|
||||
<link>not a url</link>
|
||||
<pubDate>${date}</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title>Good https</title>
|
||||
<link>https://example.com/good</link>
|
||||
<pubDate>${date}</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title>Good http</title>
|
||||
<link>http://example.com/good</link>
|
||||
<pubDate>${date}</pubDate>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
`;
|
||||
|
||||
instance.fetch.resolves({ text: rssXml });
|
||||
|
||||
const items = await instance.fetchItems();
|
||||
|
||||
Assert.equal(
|
||||
items.length,
|
||||
2,
|
||||
"Only http(s) items should survive scheme filtering"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
items.map(i => i.url).sort(),
|
||||
["http://example.com/good", "https://example.com/good"],
|
||||
"Surviving items should be the http and https links"
|
||||
);
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
add_task(async function test_atom_item_url_scheme_filtering() {
|
||||
info("should drop Atom entries whose link href uses a non-http(s) scheme");
|
||||
|
||||
let sandbox = sinon.createSandbox();
|
||||
let instance = getRssProviderForTest(sandbox, { timeRange: 0 });
|
||||
|
||||
const updated = new Date().toISOString();
|
||||
const atomXml = `
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Atom Feed</title>
|
||||
<entry>
|
||||
<title>Bad scheme</title>
|
||||
<link href="javascript:alert(1)" />
|
||||
<id>urn:uuid:bad</id>
|
||||
<updated>${updated}</updated>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Good scheme</title>
|
||||
<link href="https://example.com/atom-good" />
|
||||
<id>urn:uuid:good</id>
|
||||
<updated>${updated}</updated>
|
||||
</entry>
|
||||
</feed>
|
||||
`;
|
||||
|
||||
instance.fetch.resolves({ text: atomXml });
|
||||
|
||||
const items = await instance.fetchItems();
|
||||
|
||||
Assert.equal(items.length, 1, "Only the https Atom entry should remain");
|
||||
Assert.equal(items[0].url, "https://example.com/atom-good");
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
add_task(async function test_fetch_network_error() {
|
||||
info("should return empty array on network error");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user