月度存档: 十月 2009

在 AIR 中突破同源策略访问 iframe 中的内容

本篇所讲关于 AIR 的内容是基本 HTML+JS,而不是 Flex 或其他。

在 AIR 中,如果当前页面有一个 iframe,并且我们需要获取这个 iframe 的内容,但是如果 iframe 里包含的是远程页面,在默认情况下,浏览器策略并不允许这种情况,这就是同源策略的限制。

在 Titanium 这个类似于 AIR 的平台中,默认情况下是可以直接从应用的页面中去读取 iframe 中的内容而没有任何限制,但是在 AIR 中不可以。在网上搜索了一番,再看了好几遍 AIR 的文档之后,终于找到解决问题的方法了。

在 AIR 中,iframe 标签拥有额外的几个属性:sandboxRoot、documentRoot、allowCrossDomainXHR 及 ondominitialize。这里为了解决读取 iframe 内容所用到的属性就是 sandboxRoot 及 documentRoot。

通过 sandboxRoot 及 documentRoot,可以将本地的页面映射为远程页面,并在沙盒中运行,另外,在沙盒中运行的页面就会拥有映射域名的域。例如,我们可以将本地的 proxy.html 映射为 http://example.com/local/proxy.html,这样,在实际运行时,proxy.html 中如果用 document.domain 获取页面所在的域,就会得到 example.com,这个时候如果在 proxy.html 中添加一个 iframe,这个 iframe 指向我们实际需要获取内容的地址,因为域相同,就可以直接获取 iframe 的内容了。

例如,我们要在 AIR 中获取 http://example.com/member/login.html 的内容,或者操作这个页面的元素,就可以先用一个 proxy.html,用来实现将本地文件运行在沙盒中,再用一个 login.html 来对实际网页进行操作。

<!– proxy.html –>
<iframe src=”http://example.com/local/login.html” id=”frame” width=”100%”
sandboxRoot=”http://example.com/local/”
documentRoot=”app:/” name=”frame” height=”100%”></iframe>

    <!– login.html –>
    <iframe id=’f’ frameborder=”0″ scrolling=”no”
    src=”http://example.com/member/login.html”
    width=”100%” height=”100%”></iframe>
    <script type=”text/javascript”>
    alert(document.domain);
    var f = document.getElementById(‘f’);
    f.onload = function() {
    alert(f.contentWindow.document.documentElement.innerHTML);
    };
    </script>

    在 proxy.html 中,虽然 iframe 的 src 属性是 http://example.com/locale/login.html,但是由于这个 iframe 具有 sandboxRoot 属性,所以实际请求会被重定向到相对于 sandboxRoot,并且使用 documentRoot 进行定位的实际地址 app://login.html,但是这个时候 login.html 中具有  document.domain=”example.com” 这个属性,所有可以直接使用 iframe.contentWindow 来访问 iframe 中的内容。

    虽然解决方法有些麻烦,但终归可以实现突破同源策略的限制来读取 iframe 的内容了。

    当然,如果你有更好的方法,请不吝赐教:)

    使用 arguments.caller 实现自动回调

    这是一篇介绍类似于 js hack 的文章,只是说明了一种可行的途径,并且可能会增加代码复杂程度,具体项目中是否可以使用还请自辨:)

    在前端开发过程中,有许多业务流程可能是需要用户进行登录的,并且登录过程是放在弹出层中,这样就可以不用刷新页面,增强用户体验。在登录时,用户的操作就会被打断,为了进一步增强用户体验,我们可能需要在登录完成后自动继续进行用户在登录前想进行的操作。

    假设有这样一个场景,用户需要发表一个留言,但是发表留言是需要登录的,而发表留言的输入框是一直显示的,这也就要求在用户点击了发表按钮时对用户登录状态进行验证,传统的做法是将在用户登录状态检查封装在一个函数之中,这个函数接收一个回调参数,如果登录验证通过,则执行回调函数。这样的逻辑可以用以下代码表示:

    function doAction() {
    checkLogin(function() {
    // 处理业务逻辑
    });
    }
    function checkLogin(callback) {
    if (isLogin) {
    callback();
    } else {
    showLogin(callback);
    }
    }
    function showLogin(callback) {
    document.getElementById(“login-btn”).onclick = function() {
    isLogin = true;
    callback();
    };
    }

      总觉得这样的方式会将业务逻辑放到一个匿名函数中,而不是放在了按钮的事件响应函数中,感觉上不是那么好。这对整个事件响应函数的改动比较大,主要的业务逻辑都放在了登录验证函数的回调中。而希望可以是下面这样:

      function doAction() {
      // 直接在函数开始进行登录验证,将业务逻辑独立出来
      if (!doLogin()) {
      return;
      }
      // 处理业务逻辑
      }
      function doLogin() {
      if (isLogin) {
      return true;
      } else {
      var callback = function() {}; // 自动组装 callback
      showLogin(callback);
      return false;
      }
      }
      function showLogin(callback) {
      document.getElementById(“login-btn”).onclick = function() {
      isLogin = true;
      callback();
      };
      }

        这里的关键在于怎么“自动组装 callback”,很幸运的是,JavaScript 的 Function 提供了一个属性 caller 可以用来获取调用当前函数的函数是什么。并且还可以用 caller 的 arguments 属性来获取 caller 执行时的参数是什么,这也就使我们自动组装 callback 并恢复 caller 的原参数成为可能。

        <div id=”result”></div>
        <input type=”button” value=”Sumit” onclick=”sendPost()” id=”post” />
        <input type=”button” value=”Clear login” onclick=”isLogin=false” id=”reset” />
        <div id=’login’ style=”border:1px solid #000;padding:10px;margin:10px;display:none”>
        <strong>Login</strong><br />
        <input type=”button” id=’loginbtn’ value=”Login” />
        </div>
        <script type=”text/javascript”>
        // 是否登录
        var isLogin = false;
        // 检查登录状态
        var checkLogin = function(cfg) {
        if (isLogin) {
        return true;
        }
        cfg = cfg || {};
        var callback = null;
        // 自动执行回调
        if (cfg.autoCallback && arguments.callee.caller) {
        try {
        var caller  = arguments.callee.caller,
        args    = caller.arguments || [],
        scope   = cfg.callbackScope || {},
        newArgs = [];
        for (var i = 0; i < args.length; ++i) {
        newArgs.push(args[i]);
        }
        callback = caller ? function() {
        try {
        caller && caller.apply(scope, newArgs);
        } catch (e) {
        //alert(e.message);
        }
        } : null;
        } catch (e) {
        callback = null;
        }
        }
        showLogin(callback);
        return false;
        };
        function sendPost() {
        if (!checkLogin({autoCallback:true})) {
        return;
        }
        document.getElementById(‘result’).innerHTML += ‘Hello<br />’;
        }
        function showLogin(callback) {
        document.getElementById(‘login’).style.display = ‘;
        document.getElementById(‘loginbtn’).onclick = function() {
        document.getElementById(‘login’).style.display = ‘none’;
        isLogin = true;
        callback && callback();
        }
        }
        </script>

        其实弄出这么个方法来实现登录自动回调也是为了方便写代码,不用每次去将一堆业务逻辑放到 callback 中,直接在函数开始回一个 if (!checkLogin({autoCallback:true})) 就行。

        caller 属性在 MDC 中被标记为“非标准”的,但是在主流浏览器中,都是支持这个属性的,也就是说,在浏览器兼容性上使用 caller 是没有问题的。

        不过,使用奇技淫巧容易伤身体,慎用:)

        REALbasic 中使用 ShellExecute 执行命令

        在 REALbasic 中,如果需要执行 cmd 命令,可以直接使用 Shell 类,但是这样的话,编译成 Windows 程序时会额外需要一个 Shell.dll 的动态链接库,这对于我这样的 1exe 爱好者是不能忍受的。但是对于 Mac OS X 和 Linux 的生成目标来说,是不存在这个问题的,因为 Mac OS X 的应用程序本身就是一个文件夹,而 Linux 的目标不会生成额外的链接库。因此,需要针对 Windows 进行特殊处理。于是在网上搜索解决方案,找到了 VB 中执行程序的几种方法:

        1. 使用 CreateProcess

        通过 CreateProcess 以及使用管道,可以执行外部程序并获取输出,但是这个方法过于烦琐,并且我也不需要外部进程执行完毕后的输出结果,因此不采用。

        2. 使用 Shell 方法

        VB 里有一个 Shell 方法,但是在 RB 中并没有,所以此路不通。

        3. 使用 ShellExecute

        这个方法同样是一个系统 API,可以直接通过 RB 的 declare 声明并调用它,在测试之后,使用 declare 来使用系统 API 不会生成额外的 dll,正是我需要的。

        首先在 RB 的某个模块中添加一个方法 ShellExecute,用来封装对系统 API 的请求:

        function ShellExecute(hWnd as Integer, lpOperation as String, _
        lpFile as String, lpParameters as String, lpDirectory as String, nShowCmd as Integer)

        注意,在 RB 中添加方法时,参数列表中的 _ 需要去掉,这里是为了排版的需要而加上的。

        这个 API 是定义在 shell32.dll 中的,在 ShellExecute 方法中先需要声明:

        soft declare function ShellExecuteA Lib “shell32.dll” _
        (ByVal hWnd As Integer, ByVal lpOperation As CString, _
        ByVal lpFile As CString, ByVal lpParameters As CString, _
        ByVal lpDirectory As CString, ByVal nShowCmd As Integer) As Integer

        soft declare function ShellExecuteW Lib “shell32.dll” _
        (ByVal hWnd As Integer, ByVal lpOperation As CString, _
        ByVal lpFile As CString, ByVal lpParameters As CString, _
        ByVal lpDirectory As CString, ByVal nShowCmd As Integer) As Integer

        然后来调用这个 API:

          dim ret as Integer
            Try
              ret = ShellExecuteW(hWnd, ConvertEncoding(lpOperation, Encodings.UTF16), _
              ConvertEncoding(lpFile, Encodings.UTF16), _
              ConvertEncoding(lpParameters, Encodings.UTF16), _
              ConvertEncoding(lpDirectory, Encodings.UTF16), nShowCmd)
            Catch
              ret = ShellExecuteA(hWnd, lpOperation, lpFile, lpParameters, lpDirectory, nShowCmd)
            End
            return ret

        把 ShellExecuteW 放在 Try … Catch 中是为了兼容的需要,因为像 Windows 98 这样比较老的系统中,系统内部编码还不是 Unicode,还没有 ShellExecuteW 这样结尾是 W 的 API。

        另外,因为 RB 内部是使用的 UTF-8 编码,而系统 API ShellExecuteW 使用的编码是 Unicode,所以参数在传递前需要进行转码,将字符串参数值转换为 Unicode 编码,否则在执行一个明明正确的命令时,会出现“找不到文件”的错误。

        好了,这样就可以使用 ShellExecute 方法来执行外部程序了,例如:

          #if TargetWin32
            dim ret as Integer
            ret = Win32Helper.ShellExecute(hWnd, "open", "cmd.exe", _
            "/c shutdown -t 10", "", 0)
          #endif

        上面的代码就调用 cmd.exe 执行了命令 shutdown -s -t 10,也就是 10 秒后关机。

        其实这个方法也可以用来打开文档或者网址,具体的用法可以去 MSDN 找一找。

        当然,如果不在乎生成的 Windows 目标还有额外的 dll 的话,完全可以使用 RB 自带的 Shell 类,功能也强上不少。

        解决 Finder 中挂载 Samba 出现“输入的文本似乎不是可识别的 URL 格式”错误

        在公司里为了让笔记本和台式机共享文档,决定用内部的 Samba 做中转,但是在 Finder 中直接使用“连接到服务器”时,会出现“输入的文本似乎不是可识别的 URL 格式”错误,但是我输入的地址明明是 smb:// 开头的。

        用这个错误信息在网上找了找,没有找到任何解决文案,遂放弃。

        今天决定再尝试一下,换了个关键字,直接用“iDeneb samba”作为关键字来搜索,慢慢发掘之后还真找到了有用的信息:http://www.hackint0sh.org/f179/81233.htm

        按照文中说明,到 /System/Library/Filesystems 目录下,把 afpfs.fs 删除,并重新创建到 /System/Library/Filesystems/AppleShare/afpfs.kext 的软链接,但是操作完之后还是会提示“输入的文本似乎不是可识别的 URL 格式”。

        再找了找,找到了这篇:http://www.insanelymac.com/forum/i … opic=92989&st=580,似乎说是系统安装完成时 afpfs.fs 到 /System/Library/Filesystems/AppleShare/afpfs.kext 的软链接少了开头的斜杠,也就是说它的软链接地址是 System/Library/Filesystems/AppleShare/afpfs.kext

        好吧,我在之前操作的时候为了省事,直接进入 Filesystems 目录用相对路径来创建软链接的,看了文章之后,老老实实的用全路径再次创建软链接,Command+K,双击,成功连上 Samba 服务器:)

        完整的操作步骤也只有两步:

        sudo rm /System/Library/Filesystems/afpfs.fs

        sudo ln -s /System/Library/Filesystems/AppleShare/afpfs.kext /System/Library/Filesystems/afpfs.fs

        注意:一定不能省略了路径最开始的斜杠(/)或者使用相对路径。

        所以,如果你也是用黑苹果的,也碰到了这个问题,不妨试试这个解决方法吧。