Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for whitelisting tags #619

Merged
merged 2 commits into from
Apr 21, 2021
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
36 changes: 30 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ A Flutter widget for rendering HTML and CSS as Flutter widgets.
- [Constructors](#constructors)

- [Parameters Table](#parameters)

- [Getters](#getters)

- [Data](#data)

Expand All @@ -51,7 +53,7 @@ A Flutter widget for rendering HTML and CSS as Flutter widgets.

- [onImageTap](#onimagetap)

- [blacklistedElements](#blacklistedelements)
- [tagsList](#tagslist)

- [style](#style)

Expand Down Expand Up @@ -161,11 +163,15 @@ If you would like to modify or sanitize the HTML before rendering it, then `Html
| `omMathError` | A function that defines what the widget should do when a math fails to render. The function exposes the parsed Tex `String`, as well as the error and error with type from `flutter_math` as a `String`. |
| `shrinkWrap` | A `bool` used while rendering different widgets to specify whether they should be shrink-wrapped or not, like `ContainerSpan` |
| `onImageTap` | A function that defines what the widget should do when an image is tapped. The function exposes the `src` of the image as a `String` to use in your implementation. |
| `blacklistedElements` | A list of elements the `Html` widget should not render. The list should contain the tags of the HTML elements you wish to blacklist. |
| `tagsList` | A list of elements the `Html` widget should render. The list should contain the tags of the HTML elements you wish to include. |
| `style` | A powerful API that allows you to customize the style that should be used when rendering a specific HTMl tag. |
| `navigationDelegateForIframe` | Allows you to set the `NavigationDelegate` for the `WebView`s of all the iframes rendered by the `Html` widget. |
| `customImageRender` | A powerful API that allows you to fully customize how images are loaded. |

### Getters:

Currently the only getter is `Html.tags`. This provides a list of all the tags the package renders. The main use case is to assist in blacklisting elements using `tagsList`. See an [example](#example-usage---tagslist---excluding-tags) below.

### Data:

The HTML data passed to the `Html` widget as a `String`. This is required and cannot be null when using `Html`.
Expand Down Expand Up @@ -375,25 +381,43 @@ Widget html = Html(
);
```

### blacklistedElements:
### tagsList:

A list of elements the `Html` widget should not render. The list should contain the tags of the HTML elements you wish to blacklist.
A list of elements the `Html` widget should render. The list should contain the tags of the HTML elements you wish to whitelist.

#### Example Usage - blacklistedElements:
#### Example Usage - tagsList - Excluding Tags:
You may have instances where you can choose between two different types of HTML tags to display the same content. In the example below, the `<video>` and `<iframe>` elements are going to display the same content.

The `blacklistedElements` parameter allows you to change which element is rendered. Iframes can be advantageous because they allow parallel loading - Flutter just has to wait for the webview to be initialized before rendering the page, possibly cutting down on load time. Video can be advantageous because it provides a 100% native experience with Flutter widgets, but it may take more time to render the page. You may know that Flutter webview is a little janky in its current state on Android, so using `blacklistedElements` and a simple condition, you can get the best of both worlds - choose the video widget to render on Android and the iframe webview to render on iOS.

```dart
Widget html = Html(
data: """
<video controls>
<source src="https://www.w3schools.com/html/mov_bbb.mp4" />
</video>
<iframe src="https://www.w3schools.com/html/mov_bbb.mp4"></iframe>""",
blacklistedElements: [Platform.isAndroid ? "iframe" : "video"]
tagsList: Html.tags..remove(Platform.isAndroid ? "iframe" : "video")
);
```

`Html.tags` provides easy access to a list of all the tags the package can render, and you can remove specific tags from this list to blacklist them.

#### Example Usage - tagsList - Allowing Tags:
You may also have instances where you would only like the package to render a handful of html tags. You can do that like so:
```dart
Widget html = Html(
data: """
<p>Render this item</p>
<span>Do not render this item or any other item</span>
<img src='https://flutter.dev/images/flutter-mono-81x100.png'/>
""",
tagsList: ['p']
);
```

Here, the package will only ever render `<p>` and ignore all other tags.

### style:

A powerful API that allows you to customize the style that should be used when rendering a specific HTMl tag.
Expand Down
2 changes: 2 additions & 0 deletions example/lib/generated_plugin_registrant.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

// ignore_for_file: lines_longer_than_80_chars

import 'package:url_launcher_web/url_launcher_web.dart';
import 'package:video_player_web/video_player_web.dart';
import 'package:wakelock_web/wakelock_web.dart';

import 'package:flutter_web_plugins/flutter_web_plugins.dart';

// ignore: public_member_api_docs
void registerPlugins(Registrar registrar) {
UrlLauncherPlugin.registerWith(registrar);
VideoPlayerPlugin.registerWith(registrar);
WakelockWeb.registerWith(registrar);
registrar.registerMessageHandler();
Expand Down
18 changes: 13 additions & 5 deletions lib/flutter_html.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export 'package:flutter_html/src/interactable_element.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/html_parser.dart';
import 'package:flutter_html/image_render.dart';
import 'package:flutter_html/src/html_elements.dart';
import 'package:flutter_html/style.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:html/dom.dart' as dom;
Expand All @@ -41,7 +42,7 @@ class Html extends StatelessWidget {
///
/// **onImageTap** This is called whenever an image is tapped.
///
/// **blacklistedElements** Tag names in this array are ignored during parsing and rendering.
/// **tagsList** Tag names in this array will be the only tags rendered. By default all tags are rendered.
///
/// **style** Pass in the style information for the Html here.
/// See [its wiki page](https://github.com/Sub6Resources/flutter_html/wiki/Style) for more info.
Expand All @@ -55,7 +56,7 @@ class Html extends StatelessWidget {
this.onMathError,
this.shrinkWrap = false,
this.onImageTap,
this.blacklistedElements = const [],
this.tagsList = const [],
this.style = const {},
this.navigationDelegateForIframe,
}) : document = null,
Expand All @@ -72,7 +73,7 @@ class Html extends StatelessWidget {
this.onMathError,
this.shrinkWrap = false,
this.onImageTap,
this.blacklistedElements = const [],
this.tagsList = const [],
this.style = const {},
this.navigationDelegateForIframe,
}) : data = null,
Expand Down Expand Up @@ -108,7 +109,7 @@ class Html extends StatelessWidget {
final OnTap? onImageTap;

/// A list of HTML tags that defines what elements are not rendered
final List<String> blacklistedElements;
final List<String> tagsList;

/// Either return a custom widget for specific node types or return null to
/// fallback to the default rendering.
Expand All @@ -122,6 +123,13 @@ class Html extends StatelessWidget {
/// to use NavigationDelegate.
final NavigationDelegate? navigationDelegateForIframe;

static List<String> get tags => new List<String>.from(STYLED_ELEMENTS)
..addAll(INTERACTABLE_ELEMENTS)
..addAll(REPLACED_ELEMENTS)
..addAll(LAYOUT_ELEMENTS)
..addAll(TABLE_CELL_ELEMENTS)
..addAll(TABLE_DEFINITION_ELEMENTS);

@override
Widget build(BuildContext context) {
final dom.Document doc = data != null ? HtmlParser.parseHTML(data!) : document!;
Expand All @@ -141,7 +149,7 @@ class Html extends StatelessWidget {
imageRenders: {}
..addAll(customImageRenders)
..addAll(defaultImageRenders),
blacklistedElements: blacklistedElements,
tagsList: tagsList.isEmpty ? Html.tags : tagsList,
navigationDelegateForIframe: navigationDelegateForIframe,
),
);
Expand Down
16 changes: 8 additions & 8 deletions lib/html_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class HtmlParser extends StatelessWidget {
final Map<String, Style> style;
final Map<String, CustomRender> customRender;
final Map<ImageSourceMatcher, ImageRender> imageRenders;
final List<String> blacklistedElements;
final List<String> tagsList;
final NavigationDelegate? navigationDelegateForIframe;

HtmlParser({
Expand All @@ -56,7 +56,7 @@ class HtmlParser extends StatelessWidget {
required this.style,
required this.customRender,
required this.imageRenders,
required this.blacklistedElements,
required this.tagsList,
required this.navigationDelegateForIframe,
});

Expand All @@ -65,7 +65,7 @@ class HtmlParser extends StatelessWidget {
StyledElement lexedTree = lexDomTree(
htmlData,
customRender.keys.toList(),
blacklistedElements,
tagsList,
navigationDelegateForIframe,
);
StyledElement inlineStyledTree = applyInlineStyles(lexedTree);
Expand Down Expand Up @@ -113,7 +113,7 @@ class HtmlParser extends StatelessWidget {
static StyledElement lexDomTree(
dom.Document html,
List<String> customRenderTags,
List<String> blacklistedElements,
List<String> tagsList,
NavigationDelegate? navigationDelegateForIframe,
) {
StyledElement tree = StyledElement(
Expand All @@ -127,7 +127,7 @@ class HtmlParser extends StatelessWidget {
tree.children.add(_recursiveLexer(
node,
customRenderTags,
blacklistedElements,
tagsList,
navigationDelegateForIframe,
));
});
Expand All @@ -142,7 +142,7 @@ class HtmlParser extends StatelessWidget {
static StyledElement _recursiveLexer(
dom.Node node,
List<String> customRenderTags,
List<String> blacklistedElements,
List<String> tagsList,
NavigationDelegate? navigationDelegateForIframe,
) {
List<StyledElement> children = <StyledElement>[];
Expand All @@ -151,14 +151,14 @@ class HtmlParser extends StatelessWidget {
children.add(_recursiveLexer(
childNode,
customRenderTags,
blacklistedElements,
tagsList,
navigationDelegateForIframe,
));
});

//TODO(Sub6Resources): There's probably a more efficient way to look this up.
if (node is dom.Element) {
if (blacklistedElements.contains(node.localName)) {
if (!tagsList.contains(node.localName)) {
return EmptyContentElement();
}
if (STYLED_ELEMENTS.contains(node.localName)) {
Expand Down