如何有意义地对来自WebView的委托传递的changeAttributes做出反应?

8

WebView通过WebEditingDelegate支持一种机制,使代理可以为WebView(或私有的WebHTMLView)接收到的各种操作实现自定义行为。当诸如以下操作之一发生时:

-(void)changeAttributes:(id)sender

WebHTMLView 接收到时,它将被传递到委托方法中:

-(BOOL)webView:(WebView *)webView doCommandBySelector:(SEL)command

不幸的是,该机制不提供在原始操作方法中传递“sender”的功能。
对于绝大多数操作,发送者并不重要,但对于例如changeAttributes和changeFont等操作,合同要求接收方调用“sender”以便执行convertAttributes:或convertFont:等操作。
对于changeFont情况,调用[[NSFontManager sharedFontManager] convertFont:]就足够了,因为恰好这就是发送者。
在changeAttributes情况下,特别是当删除线被更改时,发送者可能是一个名为“NSFontEffectsBox”的私有类,它可能对应于负责更改删除线/等设置的字体面板的子部分。
不幸的是,调用[[NSFontManager sharedFontManager] convertAttributes:]不能获得预期的属性更改。这使得一个有兴趣有意义地实现此方法的委托陷入了困境:
1.WebKit不传递发送者,因此委托无法进行合同性的[sender convertAttributes:]调用。
2.changeAttributes:调用被发送到一个私有的WebKit类WebHTMLView,无法对其进行子类化,以自定义changeAttributes:的行为。
3.changeAttributes:调用的发送者NSFontEffectsBox是一个私有类,无法访问,例如[NSFontEffectsBox sharedFontEffectsBox]。
简而言之:似乎没有办法让开发人员有意义地覆盖WebView的changeAttributes:行为。
有什么想法吗?

一个如何提问的典范。 - Steve Madsen
你能否发布不同场景下的堆栈跟踪信息? - Mike Abdullah
2个回答

4
这是一个邪恶的例子。一对相应邪恶的行为(都不是特别干净或理想)如下:
1. 使用内联汇编来查看调用者的堆栈,以读取发送方参数(或调用者的调用者,视情况而定)。当然,这假设在调用WebHTMLView时,发送方被放置在堆栈上,而不是在%eax中。但是,在PowerPC代码中始终适用,因此可能无法启动。
2. 在WebHTMLView上放置一个类别,其中包含一个名为__my_evil_hacky_nasty_ugly_changeAttributes_thing:的方法,并在运行时使用ObjC运行时的method_exchangeImplementations()交换您的类别实现与它们的实现。您的方法变成了changeAttributes:,他们的方法变成了__my_evil_hacky_nasty_ugly_changeAttributes_thing:,然后您可以调用它们来传递原始调用。
正如我所说,两者都不是特别理想的解决方案,但第二种具有完整的运行时支持(即运行时明确设计为让您这样做),并且由于在运行时查找类和方法,所以具有容错能力。但是,如果出现故障,则会使您回到原点。
实际上,需要在WebKit中记录一个错误以使其传递发送方才有意义。您重写的版本可以潜在地寻找一个名为-(BOOL)webView:(WebView*)webView doCommandBySelector:(SEL)selector sender:(id)sender的方法,并在找到时调用该方法,否则只需调用原始方法即可。这就是苹果的代码应该做的事情。

谢谢您的回复。我希望避免使用交换方法,因为我不确定这会给我带来什么风险,特别是在苹果Mac应用商店禁止“私有API”方面。 - danielpunkass
在交换方法时不应该有风险。我在iOS应用程序中使用它没有问题。他们甚至不会在二进制文件上运行strings,所以NSClassFromString(@“WebHTMLView”)不会被检测到。ObjC运行时也是公开的,所以您可以自由地使用其中的任何内容-我还在许多类中使用它来在运行时使用-performSelectorOnMainThread:dispatch_async()版本交换方法,尽管仅限于我的自己的类。 - Jim Dovey
顺便说一句,我最终报告了这个 bug:http://www.openradar.me/radar?id=4965931952373760。我花了这么长时间是因为我让潜在的问题被忽视了。我想在短期内进行调整,同时希望未来能有更持久的解决方案。 - danielpunkass

3
你看过源代码了吗? WebHTMLView.mm
我不明白 -changeAttributes: 是如何调用 -webView:doCommandBySelector: 的,因为在这个类中它只被调用在自己的 -doCommandBySelector: 方法内部。
- (void)changeAttributes:(id)sender
{
    [self _applyStyleToSelection:[self _styleForAttributeChange:sender] withUndoAction:EditActionChangeAttributes];
}


- (void)doCommandBySelector:(SEL)aSelector
{
…
    if (![[webView _editingDelegateForwarder] webView:webView doCommandBySelector:aSelector] && coreFrame) {
…
}

此外,为什么您不能对WebHTMLView进行子类化?是因为Mac App Store对API的限制吗?WebKit算作私有的吗?我以为它是开源的。 -Wil

威尔,我不知道那是源代码的哪个版本。在我看的最新的主干版本中,WebHTMLView中的许多这些操作方法都有一个宏在顶部,“COMMAND_PROLOGUE”,它负责将消息分派给委托。至于子类化WebHTMLView,有一个事实就是它是一个私有API,但是WebView没有公开访问WebHTMLView用于的文档视图的实现细节。至于WebKit是开源的,我认为这并不重要。苹果希望防止针对系统的私有依赖关系。 - danielpunkass

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接