在不受主机页面 CSS 影响的跨域主机页面中创建嵌入式 JavaScript?

Create an embedded JavaScript in a Cross Domain Host Page which is not affected by the Host Page CSS?

大多数 javascript 可以嵌入网站的小部件使用以下结构。首先,您嵌入一段代码,如下所示:

<script type="text/javascript">
window.$zopim||(function(d,s){var z=$zopim=function(c){
z._.push(c)},
$=z.s=d.createElement(s),
e=d.getElementsByTagName(s)[0];
z.set=function(o){
    z.set._.push(o)
};
z._=[];
z.set._=[];
$.async=!0;
$.setAttribute('charset','utf-8');
$.src='//v2.zopim.com/?2342323423434234234';
z.t=+new Date;
$.type='text/javascript';
e.parentNode.insertBefore($,e)})(document,'script');
</script>

然后,当加载您的页面时,此脚本会创建一个 html 结构,如下所示:

<div class="widget-class">
  <iframe src="about:blank">
    // the content of the widget
  </iframe>
</div

我在许多聊天服务中看到了相同的结构,例如:

https://en.zopim.com/ 
http://banckle.com/
https://www.livechatinc.com/

它们的 iframe 都没有 src,即附加的 URL。

更新:这是我用来将小部件代码加载到第三方网站的脚本:

<script type="text/javascript">
(function(d){
    var f = d.getElementsByTagName('SCRIPT')[0], p = d.createElement('SCRIPT');
    window.WidgetId = "1234";   
    p.type = 'text/javascript';
    p.setAttribute('charset','utf-8');
    p.async = true;     
    p.src = "//www.example.com/assets/clientwidget/chatwidget.nocache.js";     
    f.parentNode.insertBefore(p, f);
}(document));
</script>    

我希望集成 GWT 小部件的站点的 CSS 不应影响 GWT 小部件的 CSS。我将防止主机页面的 CSS 影响我的 GWT 小部件的 CSS。

注意:我也想从我的 GWT 小部件访问主机网站。
主机页面的域是 www.example.com,iframe 的域是 www.widget.com。我还想从 iframe 设置主机域的 cookie。

在这样的结构上构建小部件的过程是什么运行? iframe的内容是怎么设置的?有这样的模式吗?我如何使用 GWT

做到这一点

我不知道 GWT,但你可以很容易地用简单的方式实现它 JavaScript。

假设您正在创建一个 online-count 小部件。首先,创建一个 iframe:

<script id="your-widget">
  // Select the script tag used to load the widget.
  var scriptElement = document.querySelector("your-widget");
  // Create an iframe.
  var iframe = document.createElement("iframe");
  // Insert iframe before script's next sibling, i.e. after the script.
  scriptElement.parentNode.insertBefore(iframe, scriptElement.nextSibling);
  // rest of the code
</script>

然后使用 JSONP 获取在线计数(参见 What is JSONP all about?),例如:

// The URL of your API, without JSONP callback parameter.
var url = "your-api-url";
// Callback function used for JSONP.
// Executed as soon as server response is received.
function callback(count) {
  // rest of code
}
// Create a script.
var script = document.createElement("script");
// Set script's src attribute to API URL + JSONP callback parameter.
// It makes browser send HTTP request to the API.
script.src = url + "?callback=callback";

然后处理服务器响应(在callback()函数内):

// Create a div element
var div = document.createElement("div");
// Insert online count to this element.
// I assume that server response is plain-text number, for example 5.
div.innerHTML = count;
// Append div to iframe's body.
iframe.contentWindow.document.body.appendChild(div);

就是这样。您的整个代码可能如下所示:

要插入第三方网站的代码段:

<script type="text/javascript">
(function(d){
    var f = d.getElementsByTagName('SCRIPT')[0], p = d.createElement('SCRIPT');
    window.WidgetId = "1234";   
    p.type = 'text/javascript';
    p.setAttribute('charset','utf-8');
    p.async = true;
    p.id = "your-widget";
    p.src = "//www.example.com/assets/clientwidget/chatwidget.nocache.js";     
    f.parentNode.insertBefore(p, f);
}(document));
</script>    

JavaScript 服务器上的文件:

// Select the script tag used to load the widget.
var scriptElement = document.querySelector("#your-widget");
// Create an iframe.
var iframe = document.createElement("iframe");
// Insert iframe before script's next sibling, i.e. after the script.
scriptElement.parentNode.insertBefore(iframe, scriptElement.nextSibling);

// The URL of your API, without JSONP callback parameter.
var url = "your-api-url";
// Callback function used for JSONP.
// Executed as soon as server response is received.
function callback(count) {
  // Create a div element
  var div = document.createElement("div");
  // Insert online count to this element.
  // I assume that server response is plain-text number, for example 5.
  div.innerHTML = count;
  // Append div to iframe's body.
  iframe.contentWindow.document.body.appendChild(div);
}
// Create a script.
var script = document.createElement("script");
// Set script's src attribute to API URL + JSONP callback parameter.
// It makes browser send HTTP request to the API.
script.src = url + "?callback=callback";

编辑:
如果您希望您的小部件不受 "outside" 中任何 css 的影响,您必须加载到 iframe 中。

代码添加到您的网站以加载任何 gwt project/widget:

<iframe id="1234" src="//www.example.com/assets/Chatwidget.html" style="border: 1px solid black;" tabindex="-1"></iframe>

注意: 我加载的不是 nocache.js 而是 yourwidget.html 文件。 像这样你的所有课程都不会受到来自外部的任何 class 的影响。

要访问此 iframe 之外的任何内容,您可以使用 jsni methods. this will only work if the domain of your iframe and the thirdpartysite are the same. otherwise youve to use window.postMessage:

public native static void yourMethod() /*-{
     $wnd.parent.someMethodFromOutsideTheIframe(); 
}-*/;

编辑2:

通过使用上面的代码片段,您可以确保您的小部件不会受到来自主页的任何 css 的影响。 要从小部件内部获取主页 url,只需添加此函数:

private native static String getHostPageUrl() /*-{
    return $wnd.parent.location.hostname;
}-*/;

编辑3:

由于您在 2 个不同的域中,因此您必须使用 window.postMessage。 这里有一个小例子可以帮助您:

除了 iframe 之外,您还必须向 example.com 的 window 添加一个事件侦听器,用于侦听来自您的 iframe 的消息。您还要检查消息是否来自正确的来源。

<script>
    // Create IE + others compatible event handler
    var eventMethod = window.addEventListener ? "addEventListener"
            : "attachEvent";
    var eventer = window[eventMethod];
    var messageEvent = eventMethod == "attachEvent" ? "onmessage"
            : "message";

    // Listen to message from child window
    eventer(messageEvent, function(e) {
        //check for the correct origin, if wanted
        //if ( e.origin !== "http://www.widget.com" )
        //        return
        console.log('parent received message!:  ', e.data);
        //here you can set your cookie
        document.cookie = 'cookie=widget; expires=Fri, 1 Feb 2016 18:00:00 UTC; path=/'
    }, false);
</script>

从您的小部件内部调用此方法:

public native static void postMessageToParent(String message) /*-{
    //message to sent, the host that is supposed to receive it
    $wnd.parent.postMessage(message, "http://www.example.com");
}-*/;

我在 pastebin 上放了一个工作示例:

javascript 插入您的页面:http://pastebin.com/Y0iDTntw
gwt class 与 onmoduleload: http://pastebin.com/QjDRuPmg

这是我用javascript在cloud9(在线IDE)写的一个功能齐全的简单小部件示例项目,如果你想编辑它,请随时请求访问,查看是公开的(对于注册用户 - 注册是免费的)。

来源: https://ide.c9.io/nmlc/widget-example, 结果: https://widget-example-nmlc.c9users.io/index.html

关于他们是怎么做到的问题:

zopim 似乎在客户端逐渐构建它们的小部件,定义并需要基本模块(如这些 __$$__meshim_widget_components_mobileChatWindow_MainScreen),这些模块由子模块组成,然后使用 __$$__jx_ui_HTMLElement 构建器处理所有内容创建 HTML 元素并将它们附加到提供的父节点。所有这些都会编译成聊天框的结果 HTML。顺便说一句,从一些组件的名称来看,他们似乎使用一些 "meshim" 库来构建他们的小部件,但我从未听说过这个库。

this.dom.src='about:blank'
this.appendToParent(!0)
var H=this.iwin=this.dom.contentWindow
var I=this.idoc=r.extend(H.document)
I.write(G)
I.close()

我猜这是 zopim 服务为其小部件创建 iframe 的地方。我不确定他们为什么使用 document.write 而不是 appendChild(document.write 删除事件绑定),但我已经实现了两个版本 - 除了 setIframeContentsaddHtmlElement 函数。

希望有人会觉得这有用 :)。

1) 有许多不同的方法可以将内容加载到 iframe。 Iframe 具有独立的内容。你放在主机页面的 iframe 没有 src,因为浏览器安全策略,你不能简单地从其他域加载内容。但是你可以从其他 domain.For 这个 porpuse 加载 js 你需要 usw JSONP

2) 要与主机页面和小部件 iframe 共享 cookie,您需要像 post

中那样使用 postMessage api