Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/lib/fake/backend/fake_pub_worker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ Map<String, String> _fakeDartdocFiles(
usingBaseHref: null,
aboveSidebarUrl: null,
belowSidebarUrl: null,
redirectPath: null,
).toJson()),
'index.json': '{}',
'pub-data.json': json.encode(pubData),
Expand All @@ -251,6 +252,7 @@ Map<String, String> _fakeDartdocFiles(
usingBaseHref: null,
aboveSidebarUrl: null,
belowSidebarUrl: null,
redirectPath: null,
).toJson()),
};
}
Expand Down
26 changes: 25 additions & 1 deletion app/lib/task/handlers.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'dart:convert';
import 'dart:io' show gzip;

import 'package:path/path.dart' as p;

import 'package:pub_dev/dartdoc/dartdoc_page.dart';
import 'package:pub_dev/dartdoc/models.dart';
import 'package:pub_dev/shared/exceptions.dart';
Expand Down Expand Up @@ -92,7 +94,29 @@ Future<shelf.Response> handleDartDoc(
DartDocSidebar.fromJson(dataJson as Map<String, dynamic>);
return utf8.encode(sidebar.content);
}
final page = DartDocPage.fromJson(dataJson as Map<String, dynamic>);
var page = DartDocPage.fromJson(dataJson as Map<String, dynamic>);

// NOTE: If the loaded page is redirecting, we try to load it and render
// the page it is pointing to. Instead, we should redirect the page
// to the new URL.
// TODO: restructure cache logic and implement proper redirect
final redirectPath = page.redirectPath;
if (page.isEmpty() &&
redirectPath != null &&
p.isRelative(redirectPath)) {
final newPath = p.normalize(p.joinAll([
p.dirname(path),
redirectPath,
if (!redirectPath.endsWith('.html')) 'index.html',
]));
final newDataGz =
await taskBackend.dartdocFile(package, version, newPath);
if (newDataGz != null) {
final newDataJson = gzippedUtf8JsonCodec.decode(newDataGz);
page = DartDocPage.fromJson(newDataJson as Map<String, dynamic>);
}
}

final html = page.render(DartDocPageOptions(
package: package,
version: version,
Expand Down
18 changes: 18 additions & 0 deletions app/test/dartdoc/dartdoc_page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,24 @@ import '../shared/utils.dart';
// we may need to update either the test or the rendering templates until
// there are only known and accepted differences between the two.
void main() {
group('DartDocPage parsing edge cases', () {
test('parse redirect URL', () {
final redirectContent = '''<!DOCTYPE html>
<html lang="en">
<head>
<link rel="canonical" href="../retry" />
<meta http-equiv="refresh" content="0; url=../retry" />
</head>
<body>
<p><a href="../retry">New URL</a></p>
</body>
</html>''';
final page = DartDocPage.parse(redirectContent);
expect(page.isEmpty(), true);
expect(page.redirectPath, '../retry');
});
});

group('DartDocPage rendering', () {
final tempDir = Directory.systemTemp.createTempSync();
final pkgDir = p.join(tempDir.path, 'pkg');
Expand Down
31 changes: 31 additions & 0 deletions pkg/_pub_shared/lib/dartdoc/dartdoc_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ final class DartDocPage {
/// The right/below sidebar URL that dartdoc will load dynamically.
final String? belowSidebarUrl;

/// The path URL this page redirects to.
final String? redirectPath;

DartDocPage({
required this.title,
required this.description,
Expand All @@ -100,6 +103,7 @@ final class DartDocPage {
required this.usingBaseHref,
required this.aboveSidebarUrl,
required this.belowSidebarUrl,
required this.redirectPath,
});

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

/// Indicates that the [DartDocPage] was could not parse any displayable
/// content from the dartdoc output. Such page is a redirect page that was
/// introduced in `dartdoc 8.3.0`.
bool isEmpty() {
return title.isEmpty &&
description.isEmpty &&
breadcrumbs.isEmpty &&
left.isEmpty &&
right.isEmpty &&
(baseHref == null || baseHref!.isEmpty) &&
(usingBaseHref == null || usingBaseHref!.isEmpty) &&
(aboveSidebarUrl == null || aboveSidebarUrl!.isEmpty) &&
(belowSidebarUrl == null || belowSidebarUrl!.isEmpty);
}

static DartDocPage parse(String rawHtml) {
final document = html_parser.parse(rawHtml);

Expand Down Expand Up @@ -182,6 +201,17 @@ final class DartDocPage {
}
final content = rawContent?.innerHtml;

final httpEquivRefresh = document.head
?.querySelectorAll('meta')
.where((e) => e.attributes['http-equiv'] == 'refresh')
.firstOrNull;
final redirectPath = httpEquivRefresh?.attributes['content']
?.split(';')
.map((p) => p.trim())
.where((p) => p.startsWith('url='))
.firstOrNull
?.substring(4);

return DartDocPage(
title: document.querySelector('head > title')?.text ?? '',
description: document
Expand All @@ -196,6 +226,7 @@ final class DartDocPage {
usingBaseHref: body?.attributes['data-using-base-href'],
aboveSidebarUrl: rawContent?.attributes['data-above-sidebar'],
belowSidebarUrl: rawContent?.attributes['data-below-sidebar'],
redirectPath: redirectPath,
);
}
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/_pub_shared/lib/dartdoc/dartdoc_page.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.