向现有的TWebBrowser文档添加脚本元素。

4

受到这个周末一些SO问题的启发,我决定看看我是否能够弄清楚如何将一些JavaScript添加到加载在TWebBrowser中的HTML元素中。 在这样做时,我遇到了一个奇怪的问题,也许有人可以解释一下。

这是我的HTML文档:

<html>
  <body>
  Something
  <br>
  <div id="forscript">some more text</div>
  </body>
</html>

我想将我的Javascript元素添加到其中

<script type="text/javascript" defer="false">{alert('hello');}</script>

我将其作为脚本参数传递给下面的AddScript()函数。

这是我的代码(doc2是从WebBrowser获取的IHtmlDocument2):

procedure TForm1.AddScript(const Script : String);
var
  Element : IHtmlElement;
  Doc3 : IHtmlDocument3;
begin
  Doc2.QueryInterface(IHtmlDocument3, Doc3);
  Element := Doc3.GetElementByID('forscript');
  Element.innerHTML := Element.innerHTML + Script;
end;

这个方法很好用,但是……

在赋值的右侧使用Element.innerHTML的原因是我发现如果右侧只有Script js,js就不会执行。我的问题是,为什么不会执行?是否有更简单的替代方法,比如在代码中创建一个IHtmlScriptElement并将其插入DOM中?显然,我这个简单的解决方法只是在元素已经包含文本的情况下添加文本,但我有点难以相信那些真正知道自己在做什么的人会认为这是必要的。

FWIW #1:我尝试使用Element.insertAdjacentHtml添加脚本,但得到了与我刚才描述的相同的行为,需要在脚本之外插入其他内容才能使脚本执行,所以我想知道这是否与对DOM进行更改后如何处理有关。

FWIW #2:我使用Element.innerHtml路线,因为TWebBrowser/MSHTML.Pas抵制了我在代码中创建IHtmlScriptElement的尝试;据我所知,尝试使用MSHTML.Pas中的任何CohtmlXXXElement例程都会引发“未注册类”异常,这似乎证实了我在某个地方遇到的@kobik的评论。

(顺便说一句,在Win7 64位上使用D7 + IE11)

1个回答

6

下面是一个完整的示例,展示如何使用 IHtmlScriptElement。 HTML 在应用程序启动时加载。 在 Button1Click 下的代码将 JavaScript 添加到 DOM 并执行:

unit u_frm_SO27091639;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, OleCtrls, SHDocVw, MsHtml, ActiveX;

type
  TForm1 = class(TForm)
    WebBrowser1: TWebBrowser;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure LoadDocFromString(Webbrowser : TWebbrowser);

var
  Strm    : TStringStream;
  Adapter : TStreamAdapter;

begin
 WebBrowser.Navigate('about:blank');
 // warning - don't use this trick in production code, use the `OnDocumentComplete` event
 while WebBrowser.ReadyState <> READYSTATE_INTERACTIVE do
  Application.ProcessMessages;
 // end of warning
 Strm := TStringStream.Create;
 try
  Strm.WriteString('<html><body>Something<br></body></html>');
  Strm.Seek(0, 0);
  Adapter := TStreamAdapter.Create(Strm);
  (WebBrowser.Document as IPersistStreamInit).Load(Adapter) ;
 finally
  Strm.Free;
 end;
end;

procedure TForm1.Button1Click(Sender: TObject);

var
  Doc2     : IHtmlDocument2;
  Script   : IHtmlDOMNode;
  HTMLWindow: IHTMLWindow2;

begin
 Doc2 := Webbrowser1.Document as IHtmlDocument2;
 if Assigned(Doc2.body) then
  begin
   Script := Doc2.createElement('script') as IHTMLDOMNode;
   (Script as IHTMLScriptElement).text := 'function helloWorld() { alert("hello world!") }';
   (Doc2.body as IHtmlDomNode).appendChild(Script);
   HTMLWindow := Doc2.parentWindow;
   if Assigned(HTMLWindow) then
    HTMLWindow.execScript('helloWorld()', 'JavaScript')
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
 LoadDocFromString(Webbrowser1);
end;

end.

太棒了。我漏掉的是“Script:= Doc2.createElement('script') as IHTMLDOMNode;(Script as IHTMLScriptElement).text ...”。仍有一个问题,我的代码会立即执行脚本,而这个方法使用HTMLWindow.execScript的显式调用。我会查一下是否有办法避免这种需要... - MartynA
如果我理解正确的话,您想要从网页上执行脚本,而不是从 Delphi 上执行? - whosrdaddy
是的,我的意思是让它留给WebBrowser后面的DOM引擎。 - MartynA
那就继续写你的代码吧,没问题。我只是关注于你的问题,那就是关于 IHtmlScriptElement 的使用 :) - whosrdaddy
@MartynA:你知道你可以直接使用HTMLWindow.execScript而不需要将js添加到DOM中吗? - whosrdaddy
是的,现在我知道了,感谢您的回答:)。 顺便说一下,我没有特别的需求,我的好奇心被这个问题引起了:http://stackoverflow.com/questions/27081532/executing-js-in-twebbrowser 和另一个问题。 - MartynA

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