Skip to content

Commit b7816e5

Browse files
authored
Detect redirecting page in the dartdoc-generated output + render in-place. (#8342)
1 parent 7e107a5 commit b7816e5

File tree

5 files changed

+78
-1
lines changed

5 files changed

+78
-1
lines changed

app/lib/fake/backend/fake_pub_worker.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ Map<String, String> _fakeDartdocFiles(
237237
usingBaseHref: null,
238238
aboveSidebarUrl: null,
239239
belowSidebarUrl: null,
240+
redirectPath: null,
240241
).toJson()),
241242
'index.json': '{}',
242243
'pub-data.json': json.encode(pubData),
@@ -251,6 +252,7 @@ Map<String, String> _fakeDartdocFiles(
251252
usingBaseHref: null,
252253
aboveSidebarUrl: null,
253254
belowSidebarUrl: null,
255+
redirectPath: null,
254256
).toJson()),
255257
};
256258
}

app/lib/task/handlers.dart

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import 'dart:convert';
22
import 'dart:io' show gzip;
33

4+
import 'package:path/path.dart' as p;
5+
46
import 'package:pub_dev/dartdoc/dartdoc_page.dart';
57
import 'package:pub_dev/dartdoc/models.dart';
68
import 'package:pub_dev/shared/exceptions.dart';
@@ -92,7 +94,29 @@ Future<shelf.Response> handleDartDoc(
9294
DartDocSidebar.fromJson(dataJson as Map<String, dynamic>);
9395
return utf8.encode(sidebar.content);
9496
}
95-
final page = DartDocPage.fromJson(dataJson as Map<String, dynamic>);
97+
var page = DartDocPage.fromJson(dataJson as Map<String, dynamic>);
98+
99+
// NOTE: If the loaded page is redirecting, we try to load it and render
100+
// the page it is pointing to. Instead, we should redirect the page
101+
// to the new URL.
102+
// TODO: restructure cache logic and implement proper redirect
103+
final redirectPath = page.redirectPath;
104+
if (page.isEmpty() &&
105+
redirectPath != null &&
106+
p.isRelative(redirectPath)) {
107+
final newPath = p.normalize(p.joinAll([
108+
p.dirname(path),
109+
redirectPath,
110+
if (!redirectPath.endsWith('.html')) 'index.html',
111+
]));
112+
final newDataGz =
113+
await taskBackend.dartdocFile(package, version, newPath);
114+
if (newDataGz != null) {
115+
final newDataJson = gzippedUtf8JsonCodec.decode(newDataGz);
116+
page = DartDocPage.fromJson(newDataJson as Map<String, dynamic>);
117+
}
118+
}
119+
96120
final html = page.render(DartDocPageOptions(
97121
package: package,
98122
version: version,

app/test/dartdoc/dartdoc_page_test.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,24 @@ import '../shared/utils.dart';
3434
// we may need to update either the test or the rendering templates until
3535
// there are only known and accepted differences between the two.
3636
void main() {
37+
group('DartDocPage parsing edge cases', () {
38+
test('parse redirect URL', () {
39+
final redirectContent = '''<!DOCTYPE html>
40+
<html lang="en">
41+
<head>
42+
<link rel="canonical" href="../retry" />
43+
<meta http-equiv="refresh" content="0; url=../retry" />
44+
</head>
45+
<body>
46+
<p><a href="../retry">New URL</a></p>
47+
</body>
48+
</html>''';
49+
final page = DartDocPage.parse(redirectContent);
50+
expect(page.isEmpty(), true);
51+
expect(page.redirectPath, '../retry');
52+
});
53+
});
54+
3755
group('DartDocPage rendering', () {
3856
final tempDir = Directory.systemTemp.createTempSync();
3957
final pkgDir = p.join(tempDir.path, 'pkg');

pkg/_pub_shared/lib/dartdoc/dartdoc_page.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ final class DartDocPage {
8989
/// The right/below sidebar URL that dartdoc will load dynamically.
9090
final String? belowSidebarUrl;
9191

92+
/// The path URL this page redirects to.
93+
final String? redirectPath;
94+
9295
DartDocPage({
9396
required this.title,
9497
required this.description,
@@ -100,6 +103,7 @@ final class DartDocPage {
100103
required this.usingBaseHref,
101104
required this.aboveSidebarUrl,
102105
required this.belowSidebarUrl,
106+
required this.redirectPath,
103107
});
104108

105109
factory DartDocPage.fromJson(Map<String, dynamic> json) =>
@@ -126,6 +130,21 @@ final class DartDocPage {
126130
},
127131
);
128132

133+
/// Indicates that the [DartDocPage] was could not parse any displayable
134+
/// content from the dartdoc output. Such page is a redirect page that was
135+
/// introduced in `dartdoc 8.3.0`.
136+
bool isEmpty() {
137+
return title.isEmpty &&
138+
description.isEmpty &&
139+
breadcrumbs.isEmpty &&
140+
left.isEmpty &&
141+
right.isEmpty &&
142+
(baseHref == null || baseHref!.isEmpty) &&
143+
(usingBaseHref == null || usingBaseHref!.isEmpty) &&
144+
(aboveSidebarUrl == null || aboveSidebarUrl!.isEmpty) &&
145+
(belowSidebarUrl == null || belowSidebarUrl!.isEmpty);
146+
}
147+
129148
static DartDocPage parse(String rawHtml) {
130149
final document = html_parser.parse(rawHtml);
131150

@@ -182,6 +201,17 @@ final class DartDocPage {
182201
}
183202
final content = rawContent?.innerHtml;
184203

204+
final httpEquivRefresh = document.head
205+
?.querySelectorAll('meta')
206+
.where((e) => e.attributes['http-equiv'] == 'refresh')
207+
.firstOrNull;
208+
final redirectPath = httpEquivRefresh?.attributes['content']
209+
?.split(';')
210+
.map((p) => p.trim())
211+
.where((p) => p.startsWith('url='))
212+
.firstOrNull
213+
?.substring(4);
214+
185215
return DartDocPage(
186216
title: document.querySelector('head > title')?.text ?? '',
187217
description: document
@@ -196,6 +226,7 @@ final class DartDocPage {
196226
usingBaseHref: body?.attributes['data-using-base-href'],
197227
aboveSidebarUrl: rawContent?.attributes['data-above-sidebar'],
198228
belowSidebarUrl: rawContent?.attributes['data-below-sidebar'],
229+
redirectPath: redirectPath,
199230
);
200231
}
201232
}

pkg/_pub_shared/lib/dartdoc/dartdoc_page.g.dart

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)