问题
最近将一个Silverlight应用从一个域挪到另外一个域下之后,出了一些莫名其妙的问题。在加载完Silverlight之后,整个Silverlight UI一闪就消失了。左下角没有异常信息,异常报错的窗口——系统会捕获到所有异常,将其使用Alert方法展示给用户——也没有弹出来。
后来使用HTTP Watch观察了一下,发现在调用web service的时候服务端返回了错误(服务端会将异常封装成一个自定义的数据结构返回给客户端)。奇怪的是,既然返回了错误,按理应该会弹出窗口显示错误信息才是,但是却什么信息也没弹出来,整个UI界面一闪就消失了。
查了很久之后终于找到相关的资料,原来Silverlight和宿主(承载Silverlight应用的网页)Dom元素和脚本的交互在跨域下也有一些限制。
Silverlight HTML桥的安全设置
在Silverlight2中,有三个参数控制着Silverlight和HTML的交互行为。
- enableHtmlAccess —— Silverlight插件属性,禁止恶意的基于 Silverlight 的跨域应用程序访问主页面的 JavaScript 和 DOM 代码。
- ExternalCallersFromCrossDomain —— 部署清单属性,禁止恶意的跨域宿主访问由基于 Silverlight 的应用程序公开的可编写脚本的属性、方法或事件。
- AllowHtmlPopupwindow —— Silverlight插件属性,控制基于 Silverlight 的跨域应用程序打开的弹出窗口。在此属性设置为 false 时(在从包含页或承载 iframe 以外的其他域加载 Silverlight 控件时的默认值),开发人员无法调用 [HtmlPage.PopupWindow]。
enableHtmlAccess
这个属性是在Silverlight插件中配置的。它使到Xap包中的托管代码可以访问宿主页面中的DOM元素以及Javascript代码。此插件一旦初始化之后就是只读的。在同域情况下,此属性默认值为true,而在跨域情况下(宿主和Xap包不在同个域下)默认值为false。这也就是为什么我们一般情况下不需要设置此参数的缘故。
<div id="silverlightControlHost">
<object data="data:application/x-silverlight," type="application/x-silverlight-2" width="300" height="100">
<param name="source" value="http://www.domain1.com/silverlightapp.xap"/>
<param name="enableHtmlAccess" value="true" />
object>
div>
将此属性设置为true之后才可以访问以下对象:
这也就解释了文章开头提到的问题。
因为在出问题的Silverlight应用中,会捕获所有的异常,并将异常描述信息通过HtmlWindow.Alert方法展示给用户。而Alert的实现应该也是通过调用Javascript的alert函数来完成的,因此在跨域情况下,如果直接调用跨域宿主的Javascript代码是会抛出异常的。不过对于此异常Silverlight的处理行为还是很糟糕的。
- 如果出于某种目的需要禁用此属性,防止Silverlight意外篡改宿主Dom元素,或者限制Silverlight可访问的Dom元素,那么可以采用以下步骤实现:
- 1. 通过 ScriptableTypeAttribute或者 ScriptableMemberAttribute属性声明一个可以被Javascript访问的Silverlight对象
- 2. 通过 RegisterScriptableObject显式注册此对象以供Javascript访问
- 3. Javascript调用此对象接口,将所需要的Dom元素传给Silverlight托管代码
- 需要注意的是,这种方式是由Javascript发起的操作,需要配置ExternalCallersFromCrossDomain属性为true。
ExternalCallersFromCrossDomain
ExternalCallersFromCrossDomain属性是在部署清单appmanifest.xaml中配置的。它是用来限制跨域情况下脚本对Silverlight托管代码的访问的,在同域情况下此属性无效。配置示例如下:
<Deployment xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
EntryPointAssembly="MyAppAssembly"
EntryPointType="MyNamespace.MyApplication"
ExternalCallersFromCrossDomain="ScriptableOnly"
>
<Deployment.Parts>
<AssemblyPart Source="MyAppAssembly.dll” />
<AssemblyPart Source="MyUserControl.dll" />
Deployment.Parts>
Deployment>
此属性有两个取值,ScriptableOnly和NoAccess(beta2的FullAccess取值在正式版的时候已经废弃了)。当设置为ScriptableOnly时,Javascript可以访问Silverlight托管代码显式注册的脚本入口,而不能查询或设置其他对象的属性。另外,它也不接受事件通知。如果设置为NoAccess,那么Javascript无法访问托管代码里的任何对象接口。
关于这两个属性的详细信息,参见SilverlightHTML 桥中的安全设置。