v0.33.1 9577fee

favicon_discovery.go RawHistoryFolder

1package cef
2
3import (
4	"net/url"
5	"strings"
6
7	"golang.org/x/net/html"
8)
9
10// DiscoverFaviconCandidates extracts ordered favicon URL candidates from page
11// metadata and appends the origin /favicon.ico fallback. Only http(s) page and
12// icon URLs are returned; duplicates are removed while preserving order.
13func DiscoverFaviconCandidates(pageURL, htmlSource string) []string {
14	candidates := make([]string, 0, 4)
15	for _, href := range parseFaviconHrefs(htmlSource) {
16		if resolved, ok := resolveFaviconURL(pageURL, href); ok {
17			candidates = append(candidates, resolved)
18		}
19	}
20	if fallback, ok := originFaviconURL(pageURL); ok {
21		candidates = append(candidates, fallback)
22	}
23	if len(candidates) == 0 {
24		return nil
25	}
26	return dedupeStrings(candidates)
27}
28
29func resolveFaviconCandidates(pageURL string, rawIconURLs []string) []string {
30	candidates := make([]string, 0, len(rawIconURLs))
31	for _, raw := range rawIconURLs {
32		if resolved, ok := resolveFaviconURL(pageURL, raw); ok {
33			candidates = append(candidates, resolved)
34		}
35	}
36	return dedupeStrings(candidates)
37}
38
39func fallbackFaviconCandidates(pageURL string) []string {
40	fallback, ok := originFaviconURL(pageURL)
41	if !ok {
42		return nil
43	}
44	return []string{fallback}
45}
46
47func parseFaviconHrefs(htmlSource string) []string {
48	z := html.NewTokenizer(strings.NewReader(htmlSource))
49	var hrefs []string
50	for {
51		switch z.Next() {
52		case html.ErrorToken:
53			return hrefs
54		case html.StartTagToken, html.SelfClosingTagToken:
55			tn, hasAttr := z.TagName()
56			if !hasAttr || !strings.EqualFold(string(tn), "link") {
57				continue
58			}
59			var rel, href string
60			for {
61				key, val, more := z.TagAttr()
62				switch strings.ToLower(string(key)) {
63				case "rel":
64					rel = string(val)
65				case "href":
66					href = string(val)
67				}
68				if !more {
69					break
70				}
71			}
72			if href != "" && faviconRel(rel) {
73				hrefs = append(hrefs, href)
74			}
75		}
76	}
77}
78
79func faviconRel(rel string) bool {
80	fields := strings.FieldsSeq(strings.ToLower(rel))
81	for field := range fields {
82		if field == "icon" || field == "apple-touch-icon" || field == "apple-touch-icon-precomposed" {
83			return true
84		}
85	}
86	return false
87}
88
89func resolveFaviconURL(pageURL, rawIconURL string) (string, bool) {
90	trimmed := strings.TrimSpace(rawIconURL)
91	if trimmed == "" {
92		return "", false
93	}
94	icon, err := url.Parse(trimmed)
95	if err != nil {
96		return "", false
97	}
98	if icon.IsAbs() {
99		if !isHTTPScheme(icon.Scheme) || icon.Host == "" {
100			return "", false
101		}
102		return icon.String(), true
103	}
104	base, err := url.Parse(pageURL)
105	if err != nil || !isHTTPScheme(base.Scheme) || base.Host == "" {
106		return "", false
107	}
108	resolved := base.ResolveReference(icon)
109	if !isHTTPScheme(resolved.Scheme) || resolved.Host == "" {
110		return "", false
111	}
112	return resolved.String(), true
113}
114
115func originFaviconURL(pageURL string) (string, bool) {
116	u, err := url.Parse(pageURL)
117	if err != nil || !isHTTPScheme(u.Scheme) || u.Host == "" {
118		return "", false
119	}
120	u.Path = "/favicon.ico"
121	u.RawPath = ""
122	u.RawQuery = ""
123	u.Fragment = ""
124	return u.String(), true
125}
126
127func isHTTPScheme(scheme string) bool {
128	return scheme == "http" || scheme == "https"
129}
130
131func dedupeStrings(values []string) []string {
132	seen := make(map[string]struct{}, len(values))
133	out := make([]string, 0, len(values))
134	for _, value := range values {
135		if value == "" {
136			continue
137		}
138		if _, ok := seen[value]; ok {
139			continue
140		}
141		seen[value] = struct{}{}
142		out = append(out, value)
143	}
144	return out
145}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close