core: Implement url handling (+asfunction) in textedits

This commit is contained in:
Nathan Adams 2023-08-17 21:53:16 +02:00
parent d32867d992
commit 658ac2dd9f
9 changed files with 127 additions and 6 deletions

View File

@ -4,7 +4,7 @@ use crate::avm1::function::{Avm1Function, ExecutionReason, FunctionObject};
use crate::avm1::object::{Object, TObject};
use crate::avm1::property::Attribute;
use crate::avm1::runtime::skip_actions;
use crate::avm1::scope::Scope;
use crate::avm1::scope::{Scope, ScopeClass};
use crate::avm1::{fscommand, globals, scope, ArrayObject, ScriptObject, Value};
use crate::backend::navigator::{NavigationMethod, Request};
use crate::context::UpdateContext;
@ -2970,6 +2970,17 @@ impl<'a, 'gc> Activation<'a, 'gc> {
self.scope = scope;
}
pub fn set_scope_to_display_object(&mut self, object: DisplayObject<'gc>) {
self.scope = Gc::new(
self.context.gc_context,
Scope::new(
self.scope,
ScopeClass::Target,
object.object().coerce_to_object(self),
),
);
}
/// Whether this activation operates in a local scope.
pub fn in_local_scope(&self) -> bool {
let mut current_scope = Some(self.scope);

View File

@ -1600,6 +1600,45 @@ impl<'gc> EditText<'gc> {
x: union_bounds.offset_x() + Twips::from_pixels(EditText::INTERNAL_PADDING),
})
}
fn execute_avm1_asfunction(
self,
context: &mut UpdateContext<'_, 'gc>,
address: &WStr,
) -> Result<(), crate::avm1::Error<'gc>> {
let mut activation = Avm1Activation::from_nothing(
context.reborrow(),
ActivationIdentifier::root("[EditText URL]"),
self.avm1_root(),
);
// [NA]: Should all `from_nothings` be scoped to root? It definitely should here.
activation.set_scope_to_display_object(self.avm1_root());
let this = self.avm1_root().object().coerce_to_object(&mut activation);
if let Some((name, args)) = address.split_once(b',') {
let name = AvmString::new(activation.context.gc_context, name);
let args = AvmString::new(activation.context.gc_context, args);
let function = activation.get_variable(name)?;
function.call_with_default_this(this, name, &mut activation, &[args.into()])?;
} else {
let name = AvmString::new(activation.context.gc_context, address);
let function = activation.get_variable(name)?;
function.call_with_default_this(this, name, &mut activation, &[])?;
}
Ok(())
}
fn open_url(self, context: &mut UpdateContext<'_, 'gc>, url: &WStr, target: &WStr) {
if let Some(address) = url.strip_prefix(WStr::from_units(b"asfunction:")) {
if let Err(e) = self.execute_avm1_asfunction(context, address) {
error!("Couldn't execute URL \"{url:?}\": {e:?}");
}
} else {
context
.navigator
.navigate_to_url(&url.to_utf8_lossy(), &target.to_utf8_lossy(), None);
}
}
}
impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
@ -1955,11 +1994,19 @@ impl<'gc> TInteractiveObject<'gc> for EditText<'gc> {
let tracker = context.focus_tracker;
tracker.set(Some(self.into()), context);
}
if let Some(position) = self
.screen_position_to_index(*context.mouse_position)
.map(TextSelection::for_position)
{
self.0.write(context.gc_context).selection = Some(position);
if let Some(position) = self.screen_position_to_index(*context.mouse_position) {
self.0.write(context.gc_context).selection =
Some(TextSelection::for_position(position));
let format = self.0.read().text_spans.get_text_format(position, position);
if let Some(url) = format.url {
if !url.is_empty() {
// TODO: This fires on mouse DOWN but it should be mouse UP...
// but only if it went down in the same span.
// Needs more advanced focus handling than we have at time of writing this comment.
self.open_url(context, &url, &format.target.unwrap_or_default());
}
}
} else {
self.0.write(context.gc_context).selection =
Some(TextSelection::for_position(self.text_length()));

View File

@ -0,0 +1,42 @@
[
{
"type": "MouseDown",
"pos": [134, 69],
"btn": "Left"
},
{
"type": "MouseUp",
"pos": [134, 69],
"btn": "Left"
},
{
"type": "MouseDown",
"pos": [166, 95],
"btn": "Left"
},
{
"type": "MouseUp",
"pos": [166, 95],
"btn": "Left"
},
{
"type": "MouseDown",
"pos": [143, 128],
"btn": "Left"
},
{
"type": "MouseUp",
"pos": [143, 128],
"btn": "Left"
},
{
"type": "MouseDown",
"pos": [142, 160],
"btn": "Left"
},
{
"type": "MouseUp",
"pos": [142, 160],
"btn": "Left"
}
]

View File

@ -0,0 +1,6 @@
alert1 called with input: undefined
// this
_level0
_level0.callback with input: Second Test
text1.callback with input: a,b,c,d

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
num_ticks = 1

View File

@ -382,6 +382,12 @@ impl WStr {
(&self[..index], &self[index..])
}
/// Analogue of [`str::split_once`].
#[inline]
pub fn split_once<'a, P: Pattern<'a>>(&'a self, pattern: P) -> Option<(&'a WStr, &'a WStr)> {
super::ops::str_split_once(self, pattern)
}
/// Analogue of [`str::rsplit_once`].
#[inline]
pub fn rsplit_once<'a, P: Pattern<'a>>(&'a self, pattern: P) -> Option<(&'a WStr, &'a WStr)> {

View File

@ -327,6 +327,14 @@ pub fn str_split<'a, P: Pattern<'a>>(string: &'a WStr, pattern: P) -> Split<'a,
}
}
pub fn str_split_once<'a, P: Pattern<'a>>(
string: &'a WStr,
pattern: P,
) -> Option<(&'a WStr, &'a WStr)> {
let (start, end) = pattern.into_searcher(string).next_match()?;
Some((&string[..start], &string[end..]))
}
pub fn str_rsplit_once<'a, P: Pattern<'a>>(
string: &'a WStr,
pattern: P,