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

Posted

技术标签:

【中文标题】在不受主机页面 CSS 影响的跨域主机页面中创建嵌入式 JavaScript?【英文标题】:Create an embedded JavaScript in a Cross Domain Host Page which is not affected by the Host Page CSS? 【发布时间】:2016-04-08 13:34:40 【问题描述】:

大多数可以嵌入网站的 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 不太擅长,这对你有帮助吗? Embedding HTML Document in an IFRAME With GWT 不,这对我没有帮助。 Iframe without src but still has content?的可能重复 【参考方案1】:

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

2) 要与主机页面和小部件 iframe 共享 cookie,您需要使用 postMessage api,如 post

【讨论】:

【参考方案2】:

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

添加到您的网站以加载任何 gwt 项目/小部件的代码:

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

注意: 我没有加载 nocache.js 而是加载 yourwidget.html 文件。 像这样,框架内的所有类都不会受到外部任何类的影响。

要访问此 iframe 之外的任何内容,您可以使用 jsni methods。这仅在您的 iframe 和第三方站点的域相同时才有效。否则你必须使用window.postMessage:

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

EDIT2:

通过使用上面的 sn-p,您可以确保您的小部件不受主机页面中任何 css 的影响。 要从小部件内部获取主机页面 url,只需添加以下函数:

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

EDIT3:

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

除了 iframe,您还必须向 example.com 的窗口添加一个事件侦听器,用于侦听来自 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 带 onmoduleload 的 gwt 类:http://pastebin.com/QjDRuPmg

【讨论】:

我之前都试过了。问题是,如果您在主站点上定义 CSS,您的 GWT 小部件将使用相同的 CSS 规则。我希望集成 GWT 小部件的网站的 CSS 不应该影响 GWT 小部件的 CSS。 加载css的方式是什么?只是将它包含在host.html中? 由于我的代码在主机站点上被剪断,我没有其他影响,他们可能在标题中有一个 css。 我将防止主机页面的 CSS 影响我的 GWT 小部件的 CSS。有什么想法吗? @confile 请编辑您的问题并提及所有这些要求。【参考方案3】:

这是我用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 函数之外,它们几乎相同。

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

【讨论】:

我没明白。这与我的问题和 GWT 有什么关系? @confile 我的示例是否涵盖了您对此类小部件和您提供的列表中的一个特定小部件的构建过程的一些问题?不管最终的 js 脚本是如何构建的,总体上实现仍然是相同的。看到之前的一个回答也没有贴在GWT上,你也没有说不需要js的解决方案,所以我决定带一些基本但完整的a-z例子。【参考方案4】:

我不知道 GWT,但你可以用纯 JavaScript 轻松实现。

假设您正在创建一个在线计数小部件。首先,创建一个 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";

【讨论】:

我对@9​​87654330@ 感到困惑,错过了您添加的评论。 JSONP 是否比 CORS 有一些优势? 没有人应该支持 IE @Gothdo 看看livechatinc.com 和他们的聊天小部件。 iframe 有src="javascript:false"。这是什么意思? @Gothdo 使用p.setAttribute('crossorigin','*');怎么样? 让我们continue this discussion in chat.

以上是关于在不受主机页面 CSS 影响的跨域主机页面中创建嵌入式 JavaScript?的主要内容,如果未能解决你的问题,请参考以下文章

使用ajax请求遇到的跨域问题

非常全的跨域实现方案

JS 中的跨域请求

使用window.postMessage()方法跨域通信

使用window.postMessage()方法跨域通信

使用window.postMessage()方法跨域通信