selma 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 077b3f7e2c1d6cbab948b99f181927dd78b5203369d587fe9846df84d9b36a6a
4
+ data.tar.gz: 0406b8c996a81fd4da834ddfb822b12939d47f043d8cdd2bca19d400ce8aa4a4
5
+ SHA512:
6
+ metadata.gz: 94c05988094d794703695b198dfd6e0ebd4e9f69fd60fb78a2e32c1409bbbb2db03575ba73ae8beaca3ffb9810a9ced7a4022d1212275482fba37e2d594f94fd
7
+ data.tar.gz: a10e65625f55134356859175834f82ecc6b6d38881029c97703f00a70cff74152c8508b017601980101e23fab5a1fd73f94e8c8cb5f4033d403f8fb629424c3c
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Garen J. Torikian
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # Selma
2
+
3
+ Selma **sel**ects and **ma**tches HTML nodes using CSS rules. (It can also reject/delete nodes, but then the name isn't as cool.) It's mostly an idiomatic wrapper around Cloudflare's [lol-html](https://github.com/cloudflare/lol-html) project.
4
+
5
+ ![Principal Skinner asking Selma after their date: 'Isn't it nice we hate the same things?'](https://user-images.githubusercontent.com/64050/207155384-14e8bd40-780c-466f-bfff-31a8a8fc3d25.jpg)
6
+
7
+ Selma's strength (aside from being backed by Rust) is that HTML content is parsed _once_ and can be manipulated multiple times.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'selma'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle install
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install selma
24
+
25
+ ## Usage
26
+
27
+ Selma can perform two different actions:
28
+
29
+ - Sanitize HTML, through a [Sanitize](https://github.com/rgrove/sanitize)-like allowlist syntax; and
30
+ - Select HTML using CSS rules, and manipulate elements and text
31
+
32
+ The basic API for Selma looks like this:
33
+
34
+ ```ruby
35
+ rewriter = Selma::Rewriter.new(sanitizer: sanitizer_config, handlers: [MatchAttribute.new, TextRewrite.new])
36
+ rewriter(html)
37
+ ```
38
+
39
+ Let's take a look at each part individually.
40
+
41
+ ### Sanitization config
42
+
43
+ Selma sanitizes by default. That is, even if the `sanitizer` kwarg is not passed in, sanitization occurs. If you want to disable HTML sanitization (for some reason), pass `nil`:
44
+
45
+ ```ruby
46
+ Selma::Rewriter.new(sanitizer: nil) # dangerous and ill-advised
47
+ ```
48
+
49
+ The configuration for the sanitization process is based on the follow key-value hash allowlist:
50
+
51
+ ```ruby
52
+ # Whether or not to allow HTML comments.
53
+ allow_comments: false,
54
+
55
+ # Whether or not to allow well-formed HTML doctype declarations such as
56
+ # "<!DOCTYPE html>" when sanitizing a document.
57
+ allow_doctype: false,
58
+
59
+ # HTML attributes to allow in specific elements. The key is the name of the element,
60
+ # and the value is an array of allowed attributes. By default, no attributes
61
+ # are allowed.
62
+ attributes: {
63
+ "a" => ["href"],
64
+ "img" => ["src"],
65
+ },
66
+
67
+ # HTML elements to allow. By default, no elements are allowed (which means
68
+ # that all HTML will be stripped).
69
+ elements: ["a", "b", "img", ],
70
+
71
+ # URL handling protocols to allow in specific attributes. By default, no
72
+ # protocols are allowed. Use :relative in place of a protocol if you want
73
+ # to allow relative URLs sans protocol.
74
+ protocols: {
75
+ "a" => { "href" => ["http", "https", "mailto", :relative] },
76
+ "img" => { "href" => ["http", "https"] },
77
+ },
78
+
79
+ # An Array of element names whose contents will be removed. The contents
80
+ # of all other filtered elements will be left behind.
81
+ remove_contents: ["iframe", "math", "noembed", "noframes", "noscript"],
82
+
83
+ # Elements which, when removed, should have their contents surrounded by
84
+ # whitespace.
85
+ whitespace_elements: ["blockquote", "h1", "h2", "h3", "h4", "h5", "h6", ]
86
+ ```
87
+
88
+ ### Defining handlers
89
+
90
+ The real power in Selma comes in its use of handlers. A handler is simply an object with various methods:
91
+
92
+ - `selector`, a method which MUST return instance of `Selma::Selector` which defines the CSS classes to match
93
+ - `handle_element`, a method that's call on each matched element
94
+ - `handle_text`, a method that's called on each matched text node; this MUST return a string
95
+
96
+ Here's an example which rewrites the `href` attribute on `a` and the `src` attribute on `img` to be `https` rather than `http`.
97
+
98
+ ```ruby
99
+ class MatchAttribute
100
+ SELECTOR = Selma::Selector(match_element: "a, img")
101
+
102
+ def handle_element(element)
103
+ if element.tag_name == "a" && element["href"] =~ /^http:/
104
+ element["href"] = rename_http(element["href"])
105
+ elsif element.tag_name == "img" && element["src"] =~ /^http:/
106
+ element["src"] = rename_http(element["src"])
107
+ end
108
+ end
109
+
110
+ private def rename_http(link)
111
+ link.sub("http", "https")
112
+ end
113
+ end
114
+
115
+ rewriter = Selma::Rewriter.new(handlers: [MatchAttribute.new])
116
+ ```
117
+
118
+ The `Selma::Selector` object has three possible kwargs:
119
+
120
+ - `match_element`: any element which matches this CSS rule will be passed on to `handle_element`
121
+ - `match_text_within`: any element which matches this CSS rule will be passed on to `handle_text`
122
+ - `ignore_text_within`: this is an array of element names whose text contents will be ignored
123
+
124
+ You've seen an example of `match_element`; here's one for `match_text` which changes strings in various elements which are _not_ `pre` or `code`:
125
+
126
+ ```ruby
127
+
128
+ class MatchText
129
+ SELECTOR = Selma::Selector.new(match_text_within: "*", ignore_text_within: ["pre", "code"])
130
+
131
+ def selector
132
+ SELECTOR
133
+ end
134
+
135
+ def handle_text(text)
136
+ string.sub(/@.+/, "<a href=\"www.yetto.app/#{Regexp.last_match}\">")
137
+ end
138
+ end
139
+
140
+ rewriter = Selma::Rewriter.new(handlers: [MatchText.new])
141
+ ```
142
+
143
+ #### `element` methods
144
+
145
+ The `element` argument in `handle_element` has the following methods:
146
+
147
+ - `tag_name`: The element's name
148
+ - `[]`: get an attribute
149
+ - `[]=`: set an attribute
150
+ - `remove_attribute`: remove an attribute
151
+ - `attributes`: list all the attributes
152
+ - `ancestors`: list all the ancestors
153
+ - `append(content, content_type)`: appends `content` to the element's inner content, i.e. inserts content right before the element's end tag. `content_type` is either `:text` or `:html` and determines how the content will be applied.
154
+ - `wrap(start_text, end_text, content_type)`: adds `start_text` before an element and `end_text` after an element. `content_type` is either `:text` or `:html` and determines how the content will be applied.
155
+ - `set_inner_content`: replaces inner content of the element with `content`. `content_type` is either `:text` or `:html` and determines how the content will be applied.
156
+
157
+ ## Benchmarks
158
+
159
+ TBD
160
+
161
+ ## Contributing
162
+
163
+ Bug reports and pull requests are welcome on GitHub at https://github.com/gjtorikian/selma. This project is a safe, welcoming space for collaboration.
164
+
165
+ ## Acknowledgements
166
+
167
+ - https://github.com/flavorjones/ruby-c-extensions-explained#strategy-3-precompiled and [Nokogiri](https://github.com/sparklemotion/nokogiri) for hints on how to ship precompiled cross-platform gems
168
+ - @vmg for his work at GitHub on goomba, from which some design patterns were learned
169
+ - [sanitize](https://github.com/rgrove/sanitize) for a comprehensive configuration API and test suite
170
+
171
+ ## License
172
+
173
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,14 @@
1
+ [package]
2
+ name = "selma"
3
+ version = "1.0.0"
4
+ edition = "2021"
5
+
6
+ [dependencies]
7
+ enum-iterator = "1.2"
8
+ escapist = "0.0.1"
9
+ magnus = "0.4"
10
+ lol_html = { git = "https://github.com/cloudflare/lol-html", rev = "b09b7afbbcecb944f4bf338b0e669c430d91061e" }
11
+
12
+ [lib]
13
+ name = "selma"
14
+ crate-type = ["cdylib"]
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ RUBY_MAJOR, RUBY_MINOR = RUBY_VERSION.split(".").collect(&:to_i)
4
+
5
+ PACKAGE_ROOT_DIR = File.expand_path(File.join(File.dirname(__FILE__), "..", ".."))
6
+ PACKAGE_EXT_DIR = File.join(PACKAGE_ROOT_DIR, "ext", "selma")
7
+
8
+ OS = case os = RbConfig::CONFIG["host_os"].downcase
9
+ when /linux/
10
+ # The official ruby-alpine Docker containers pre-build Ruby. As a result,
11
+ # Ruby doesn't know that it's on a musl-based platform. `ldd` is the
12
+ # a more reliable way to detect musl.
13
+ # See https://github.com/skylightio/skylight-ruby/issues/92
14
+ if ENV["SKYLIGHT_MUSL"] || %x(ldd --version 2>&1).include?("musl")
15
+ "linux-musl"
16
+ else
17
+ "linux"
18
+ end
19
+ when /darwin/
20
+ "darwin"
21
+ when /freebsd/
22
+ "freebsd"
23
+ when /netbsd/
24
+ "netbsd"
25
+ when /openbsd/
26
+ "openbsd"
27
+ when /sunos|solaris/
28
+ "solaris"
29
+ when /mingw|mswin/
30
+ "windows"
31
+ else
32
+ os
33
+ end
34
+
35
+ # Normalize the platform CPU
36
+ ARCH = case cpu = RbConfig::CONFIG["host_cpu"].downcase
37
+ when /amd64|x86_64|x64/
38
+ "x86_64"
39
+ when /i?86|x86|i86pc/
40
+ "x86"
41
+ when /ppc|powerpc/
42
+ "powerpc"
43
+ when /^aarch/
44
+ "aarch"
45
+ when /^arm/
46
+ "arm"
47
+ else
48
+ cpu
49
+ end
50
+
51
+ def windows?
52
+ OS == "windows"
53
+ end
54
+
55
+ def solaris?
56
+ OS == solaries
57
+ end
58
+
59
+ def darwin?
60
+ OS == "darwin"
61
+ end
62
+
63
+ def macos?
64
+ darwin? || OS == "macos"
65
+ end
66
+
67
+ def openbsd?
68
+ OS == "openbsd"
69
+ end
70
+
71
+ def aix?
72
+ OS == "aix"
73
+ end
74
+
75
+ def nix?
76
+ !(windows? || solaris? || darwin?)
77
+ end
78
+
79
+ def x86_64?
80
+ ARCH == "x86_64"
81
+ end
82
+
83
+ def x86?
84
+ ARCH == "x86"
85
+ end
86
+
87
+ def abs_path(path)
88
+ File.join(PACKAGE_EXT_DIR, path)
89
+ end
90
+
91
+ def find_header_or_abort(header, *paths)
92
+ find_header(header, *paths) || abort("#{header} was expected in `#{paths.join(", ")}`, but it is missing.")
93
+ end
94
+
95
+ def find_library_or_abort(lib, func, *paths)
96
+ find_library(lib, func, *paths) || abort("#{lib} was expected in `#{paths.join(", ")}`, but it is missing.")
97
+ end
98
+
99
+ def concat_flags(*args)
100
+ args.compact.join(" ")
101
+ end
102
+
@@ -0,0 +1,6 @@
1
+ require "mkmf"
2
+ require "rb_sys/mkmf"
3
+
4
+ require_relative "_util"
5
+
6
+ create_rust_makefile("selma")
@@ -0,0 +1,195 @@
1
+ use std::borrow::Cow;
2
+
3
+ use crate::native_ref_wrap::NativeRefWrap;
4
+ use lol_html::html_content::{ContentType, Element};
5
+ use magnus::{exception, method, Error, Module, RArray, RClass, RHash, RString, Symbol};
6
+
7
+ struct HTMLElement {
8
+ element: NativeRefWrap<Element<'static, 'static>>,
9
+ ancestors: Vec<String>,
10
+ }
11
+
12
+ #[magnus::wrap(class = "Selma::HTML::Element")]
13
+ pub struct SelmaHTMLElement(std::cell::RefCell<HTMLElement>);
14
+
15
+ /// SAFETY: This is safe because we only access this data when the GVL is held.
16
+ unsafe impl Send for SelmaHTMLElement {}
17
+
18
+ impl SelmaHTMLElement {
19
+ pub fn new(element: &mut Element, ancestors: &[String]) -> Self {
20
+ let (ref_wrap, _anchor) = NativeRefWrap::wrap_mut(element);
21
+
22
+ Self(std::cell::RefCell::new(HTMLElement {
23
+ element: ref_wrap,
24
+ ancestors: ancestors.to_owned(),
25
+ }))
26
+ }
27
+
28
+ fn tag_name(&self) -> Result<String, Error> {
29
+ let binding = self.0.borrow();
30
+
31
+ if let Ok(e) = binding.element.get() {
32
+ Ok(e.tag_name())
33
+ } else {
34
+ Err(Error::new(
35
+ exception::runtime_error(),
36
+ "`tag_name` is not available",
37
+ ))
38
+ }
39
+ }
40
+
41
+ fn get_attribute(&self, attr: String) -> Option<String> {
42
+ let binding = self.0.borrow();
43
+ let element = binding.element.get();
44
+ element.unwrap().get_attribute(&attr)
45
+ }
46
+
47
+ fn set_attribute(&self, attr: String, value: String) -> Result<String, Error> {
48
+ let mut binding = self.0.borrow_mut();
49
+ if let Ok(element) = binding.element.get_mut() {
50
+ match element.set_attribute(&attr, &value) {
51
+ Ok(_) => Ok(value),
52
+ Err(err) => Err(Error::new(
53
+ exception::runtime_error(),
54
+ format!("AttributeNameError: {}", err),
55
+ )),
56
+ }
57
+ } else {
58
+ Err(Error::new(
59
+ exception::runtime_error(),
60
+ "`tag_name` is not available",
61
+ ))
62
+ }
63
+ }
64
+
65
+ fn remove_attribute(&self, attr: String) {
66
+ let mut binding = self.0.borrow_mut();
67
+
68
+ if let Ok(e) = binding.element.get_mut() {
69
+ e.remove_attribute(&attr)
70
+ }
71
+ }
72
+
73
+ fn get_attributes(&self) -> Result<RHash, Error> {
74
+ let binding = self.0.borrow();
75
+ let hash = RHash::new();
76
+
77
+ if let Ok(e) = binding.element.get() {
78
+ e.attributes()
79
+ .iter()
80
+ .for_each(|attr| match hash.aset(attr.name(), attr.value()) {
81
+ Ok(_) => {}
82
+ Err(err) => Err(Error::new(
83
+ exception::runtime_error(),
84
+ format!("AttributeNameError: {}", err),
85
+ ))
86
+ .unwrap(),
87
+ });
88
+ }
89
+ Ok(hash)
90
+ }
91
+
92
+ fn get_ancestors(&self) -> Result<RArray, Error> {
93
+ let binding = self.0.borrow();
94
+ let array = RArray::new();
95
+
96
+ binding
97
+ .ancestors
98
+ .iter()
99
+ .for_each(|ancestor| match array.push(RString::new(ancestor)) {
100
+ Ok(_) => {}
101
+ Err(err) => {
102
+ Err(Error::new(exception::runtime_error(), format!("{}", err))).unwrap()
103
+ }
104
+ });
105
+
106
+ Ok(array)
107
+ }
108
+
109
+ fn append(&self, text_to_append: String, content_type: Symbol) -> Result<(), Error> {
110
+ let mut binding = self.0.borrow_mut();
111
+ let element = binding.element.get_mut().unwrap();
112
+
113
+ let text_str = text_to_append.as_str();
114
+
115
+ let content_type = Self::find_content_type(content_type);
116
+
117
+ element.append(text_str, content_type);
118
+
119
+ Ok(())
120
+ }
121
+
122
+ fn wrap(
123
+ &self,
124
+ start_text: String,
125
+ end_text: String,
126
+ content_type: Symbol,
127
+ ) -> Result<(), Error> {
128
+ let mut binding = self.0.borrow_mut();
129
+ let element = binding.element.get_mut().unwrap();
130
+
131
+ let before_content_type = Self::find_content_type(content_type);
132
+ let after_content_type = Self::find_content_type(content_type);
133
+ element.before(&start_text, before_content_type);
134
+ element.after(&end_text, after_content_type);
135
+
136
+ Ok(())
137
+ }
138
+
139
+ fn set_inner_content(&self, text_to_set: String, content_type: Symbol) -> Result<(), Error> {
140
+ let mut binding = self.0.borrow_mut();
141
+ let element = binding.element.get_mut().unwrap();
142
+
143
+ let text_str = text_to_set.as_str();
144
+
145
+ let content_type = Self::find_content_type(content_type);
146
+
147
+ element.set_inner_content(text_str, content_type);
148
+
149
+ Ok(())
150
+ }
151
+
152
+ fn find_content_type(content_type: Symbol) -> ContentType {
153
+ match content_type.name() {
154
+ Ok(name) => match (name) {
155
+ Cow::Borrowed("as_text") => ContentType::Text,
156
+ Cow::Borrowed("as_html") => ContentType::Html,
157
+ _ => Err(Error::new(
158
+ exception::runtime_error(),
159
+ format!("unknown symbol `{}`", name),
160
+ ))
161
+ .unwrap(),
162
+ },
163
+ Err(err) => Err(Error::new(
164
+ exception::runtime_error(),
165
+ format!("Could not unwrap symbol"),
166
+ ))
167
+ .unwrap(),
168
+ }
169
+ }
170
+ }
171
+
172
+ pub fn init(c_html: RClass) -> Result<(), Error> {
173
+ let c_element = c_html
174
+ .define_class("Element", Default::default())
175
+ .expect("cannot find class Selma::Element");
176
+
177
+ c_element.define_method("tag_name", method!(SelmaHTMLElement::tag_name, 0))?;
178
+ c_element.define_method("[]", method!(SelmaHTMLElement::get_attribute, 1))?;
179
+ c_element.define_method("[]=", method!(SelmaHTMLElement::set_attribute, 2))?;
180
+ c_element.define_method(
181
+ "remove_attribute",
182
+ method!(SelmaHTMLElement::remove_attribute, 1),
183
+ )?;
184
+ c_element.define_method("attributes", method!(SelmaHTMLElement::get_attributes, 0))?;
185
+ c_element.define_method("ancestors", method!(SelmaHTMLElement::get_ancestors, 0))?;
186
+
187
+ c_element.define_method("append", method!(SelmaHTMLElement::append, 2))?;
188
+ c_element.define_method("wrap", method!(SelmaHTMLElement::wrap, 3))?;
189
+ c_element.define_method(
190
+ "set_inner_content",
191
+ method!(SelmaHTMLElement::set_inner_content, 2),
192
+ )?;
193
+
194
+ Ok(())
195
+ }
@@ -0,0 +1,35 @@
1
+ use crate::native_ref_wrap::NativeRefWrap;
2
+ use lol_html::html_content::EndTag;
3
+ use magnus::{method, Error, Module, RClass};
4
+
5
+ struct HTMLEndTag {
6
+ end_tag: NativeRefWrap<EndTag<'static>>,
7
+ }
8
+
9
+ #[magnus::wrap(class = "Selma::HTML::Element")]
10
+ pub struct SelmaHTMLEndTag(std::cell::RefCell<HTMLEndTag>);
11
+
12
+ /// SAFETY: This is safe because we only access this data when the GVL is held.
13
+ unsafe impl Send for SelmaHTMLEndTag {}
14
+
15
+ impl SelmaHTMLEndTag {
16
+ pub fn new(end_tag: &mut EndTag) -> Self {
17
+ let (ref_wrap, _anchor) = NativeRefWrap::wrap(end_tag);
18
+
19
+ Self(std::cell::RefCell::new(HTMLEndTag { end_tag: ref_wrap }))
20
+ }
21
+
22
+ fn tag_name(&self) -> String {
23
+ self.0.borrow().end_tag.get().unwrap().name()
24
+ }
25
+ }
26
+
27
+ pub fn init(c_html: RClass) -> Result<(), Error> {
28
+ let c_end_tag = c_html
29
+ .define_class("EndTag", Default::default())
30
+ .expect("cannot find class Selma::EndTag");
31
+
32
+ c_end_tag.define_method("tag_name", method!(SelmaHTMLEndTag::tag_name, 0))?;
33
+
34
+ Ok(())
35
+ }
@@ -0,0 +1,17 @@
1
+ use magnus::{Error, Module, RModule};
2
+
3
+ #[derive(Clone, Debug)]
4
+ #[magnus::wrap(class = "Selma::HTML")]
5
+ pub(crate) struct SelmaHTML {}
6
+
7
+ pub fn init(m_selma: RModule) -> Result<(), Error> {
8
+ let c_html = m_selma.define_class("HTML", Default::default()).unwrap();
9
+
10
+ element::init(c_html).expect("cannot define Selma::HTML::Element class");
11
+ end_tag::init(c_html).expect("cannot define Selma::HTML::EndTag class");
12
+
13
+ Ok(())
14
+ }
15
+
16
+ pub mod element;
17
+ pub mod end_tag;
@@ -0,0 +1,23 @@
1
+ extern crate core;
2
+
3
+ use magnus::{define_module, Error};
4
+
5
+ pub mod html;
6
+ pub mod native_ref_wrap;
7
+ pub mod rewriter;
8
+ pub mod sanitizer;
9
+ pub mod selector;
10
+ pub mod tags;
11
+ pub mod wrapped_struct;
12
+
13
+ #[magnus::init]
14
+ fn init() -> Result<(), Error> {
15
+ let m_selma = define_module("Selma").expect("cannot define ::Selma module");
16
+
17
+ sanitizer::init(m_selma).expect("cannot define Selma::Sanitizer class");
18
+ rewriter::init(m_selma).expect("cannot define Selma::Rewriter class");
19
+ html::init(m_selma).expect("cannot define Selma::HTML class");
20
+ selector::init(m_selma).expect("cannot define Selma::Selector class");
21
+
22
+ Ok(())
23
+ }
@@ -0,0 +1,79 @@
1
+ use std::{cell::Cell, marker::PhantomData, mem, rc::Rc};
2
+
3
+ // NOTE: My Rust isn't good enough to know what any of this does,
4
+ // but it was taken from https://github.com/cloudflare/lol-html/blob/1a1ab2e2bf896f815fe8888ed78ccdf46d7c6b85/js-api/src/lib.rs#LL38
5
+
6
+ pub struct Anchor<'r> {
7
+ poisoned: Rc<Cell<bool>>,
8
+ lifetime: PhantomData<&'r mut ()>,
9
+ }
10
+
11
+ impl<'r> Anchor<'r> {
12
+ pub fn new(poisoned: Rc<Cell<bool>>) -> Self {
13
+ Anchor {
14
+ poisoned,
15
+ lifetime: PhantomData,
16
+ }
17
+ }
18
+ }
19
+
20
+ // impl Drop for Anchor<'_> {
21
+ // fn drop(&mut self) {
22
+ // self.poisoned.replace(true);
23
+ // }
24
+ // }
25
+
26
+ // NOTE: wasm_bindgen doesn't allow structures with lifetimes. To workaround that
27
+ // we create a wrapper that erases all the lifetime information from the inner reference
28
+ // and provides an anchor object that keeps track of the lifetime in the runtime.
29
+ //
30
+ // When anchor goes out of scope, wrapper becomes poisoned and any attempt to get inner
31
+ // object results in exception.
32
+ pub struct NativeRefWrap<R> {
33
+ inner_ptr: *mut R,
34
+ poisoned: Rc<Cell<bool>>,
35
+ }
36
+
37
+ impl<R> NativeRefWrap<R> {
38
+ pub fn wrap<I>(inner: &I) -> (Self, Anchor) {
39
+ let wrap = NativeRefWrap {
40
+ inner_ptr: unsafe { mem::transmute(inner) },
41
+ poisoned: Rc::new(Cell::new(false)),
42
+ };
43
+
44
+ let anchor = Anchor::new(Rc::clone(&wrap.poisoned));
45
+
46
+ (wrap, anchor)
47
+ }
48
+
49
+ pub fn wrap_mut<I>(inner: &mut I) -> (Self, Anchor) {
50
+ let wrap = NativeRefWrap {
51
+ inner_ptr: unsafe { mem::transmute(inner) },
52
+ poisoned: Rc::new(Cell::new(false)),
53
+ };
54
+
55
+ let anchor = Anchor::new(Rc::clone(&wrap.poisoned));
56
+
57
+ (wrap, anchor)
58
+ }
59
+
60
+ pub fn get(&self) -> Result<&R, &'static str> {
61
+ self.assert_not_poisoned()?;
62
+
63
+ Ok(unsafe { self.inner_ptr.as_ref() }.unwrap())
64
+ }
65
+
66
+ pub fn get_mut(&mut self) -> Result<&mut R, &'static str> {
67
+ self.assert_not_poisoned()?;
68
+
69
+ Ok(unsafe { self.inner_ptr.as_mut() }.unwrap())
70
+ }
71
+
72
+ fn assert_not_poisoned(&self) -> Result<(), &'static str> {
73
+ if self.poisoned.get() {
74
+ Err("The object has been freed and can't be used anymore.")
75
+ } else {
76
+ Ok(())
77
+ }
78
+ }
79
+ }