Out With the Old and in With the New

本文介绍了如何使用.NET Framework 3.0定制浏览器,包括模拟用户点击、跨窗口引用、同步滚动多个框架及拦截新窗口打开等高级功能。

.NET Framework 3.0

Mark Davis, Heidi Housten, Dan Mohr, and Kusuma Vellanki
Microsoft Corporation

February 5, 2001

Fanfare, please! Allow us to introduce our new team members, Mark Davis and Dan Mohr. They'll have a hard time filling Tom and Rafael's shoes, but after giving them clothes pegs for their noses, we set them loose. Seriously, Tom and Rafael have set a high bar for us all, and they will be greatly missed on the team. After all, no one else can do dumb jokes quite like them. Thanks guys, and welcome to the new kids.

We have a good collection for you this month, so with no further ado, read on, friends.

Contents

Firing 'em all—Doing it the right way
Bridging the generation gap—Parent and child communication
Keep passing the open windows—New windows in custom Web browsers
Scrolling in sweet synchrony—Taking a little scroll together

The Web Team in Short

Firing 'em All

Dear Web Team:

There is lots of info and samples on how to capture events such as mouse clicks in IE from a C++ program but we are drawing a blank when trying to do the reverse. We want to be able to simulate user actions such as a user clicking a button from a C++ program and would imagine there would be a method such as Fire(action = click clientX=xx clientY=yy). However we cannot find anything that provides this function. The FireEvent of IHTMLDOCUMENT4 looks promising but all docs indicate that the event object is used to receive not set values. Is this a limitation of the programming model? I would be surprised as the model is so rich in other respects.

Thanks
John Mcfetridge

The Web Team replies:

We know all about firing; we do mean events, of course. Logically, you would want to simulate the action, not the event. All you need to do is call the related method on the object. For example, if you want to fire the onclick event for a button, you can call its click method and that will automatically fire the event for you. Your main question was firing events with some custom values, so this is probably not the answer you were looking for. Patience my friend, we are paving the way to enlightenment.

As a matter of fact, you were really close with your guess. You do need to use the FireEvent method of IHTMLDocument4, but you have to create an event object initially with the values that you want, then use this event object to fire the event. You cannot set the event.srcElement and event.typeexplicitly. By default, the srcElement is set to the element with which you call FireEvent, and the type is the eventname you pass as the first parameter to the FireEvent method. We usually find it much easier to experiment with script, then translate into C++.

Here are two samples that show how to fire the onclick event for a button with custom clientX and clientY values—first in HTML, then in C++.

<HTML>
<HEAD>
<SCRIPT>
function firebuttonclick()
{
   var neweventobj = document.createEventObject();
   neweventobj.clientX = 100;
   neweventobj.clientY = 100;
   button2.fireEvent("onclick", neweventobj);
}
function handleclick()
{
   alert("Received " + window.event.type + " event on " + 
window.event.srcElement.id + " with clientX: " + window.event.clientX + " 
clientY: " + window.event.clientY);
}
</SCRIPT>
</HEAD>
<BODY BGCOLOR="#000000" TEXT="#008080">
<INPUT TYPE=button VALUE="Button 1" NAME=button1 ID=button1 
ONCLICK=firebuttonclick()><b> - Click here to fire a click event on the 
second button below. </b>
<BR><BR>
<INPUT TYPE=button VALUE="Button 2" NAME=button2 ID=button2 
ONCLICK=handleclick()><b> - Or click this directly.
</BODY>
</HTML>

You will need the latest Internet Explorer 5.5 headers and libraries to compile the code below, which demonstrates how to fire the onclick event of the second button in the HTML sample mentioned above.

#include <atlbase.h>
#define CHECKPTR(ptr) if ((ptr) == NULL) goto cleanup;

void CMyView::OnFireEvent() 
{
    HRESULT         hr = S_OK;
    BSTR            eventName = NULL;
    VARIANT         name, index, eventobj;
    VARIANT_BOOL    vBool;    

    CComPtr<IDispatch>              pDisp = NULL;
    CComPtr<IDispatch>              pEltDisp = NULL;
    CComPtr<IHTMLElementCollection> pEltColl = NULL;
    CComPtr<IHTMLEventObj>          pEvent = NULL;

    CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2>  pDoc2;
    CComQIPtr<IHTMLDocument4, &IID_IHTMLDocument4>  pDoc4;
    CComQIPtr<IHTMLEventObj2, &IID_IHTMLEventObj2>  pEvent2;
    CComQIPtr<IHTMLElement3, &IID_IHTMLElement3>    pElt3;
    CComQIPtr<IDispatch, &IID_IDispatch>            pEventDisp;
    
      // m_webBrowser is the IWebBrowser2 ptr of
      // the Web browser control you host.
    CHECKPTR(pDisp = m_webBrowser.GetDocument());    
    CHECKPTR(pDoc2 = pDisp);

    //Get the element you want to fire the event on.
    hr = pDoc2->get_all(&pEltColl);
    if (FAILED(hr) || !pEltColl)
        goto cleanup;

    VariantInit(&name);
    V_VT(&name) = VT_BSTR;
    V_BSTR(&name) = SysAllocString(L"button2");
    VariantInit(&index);
    V_VT(&index) = VT_I2;
    V_I2(&index) = 0;
    
    // We are assuming there's only one element with that name on the page,
    // else you must expect a collection instead of a single element.
    hr = pEltColl->item(name, index, &pEltDisp);
    if (FAILED(hr) || !pEltDisp)
        goto cleanup;

    CHECKPTR(pElt3 = pEltDisp);

    // Create the event object and populate it.
    CHECKPTR(pDoc4 = pDoc2);

    hr = pDoc4->createEventObject(NULL, &pEvent);
    if (FAILED(hr) || !pEvent)
        goto cleanup;

    CHECKPTR(pEvent2 = pEvent);
    CHECKPTR(pEventDisp = pEvent2);

    pEvent2->put_clientX(100);
    pEvent2->put_clientY(100);
    
    // Fire the event.
    eventName = SysAllocString(L"onclick");
    VariantInit(&eventobj);
    V_VT(&eventobj) = VT_DISPATCH;
    V_DISPATCH(&eventobj) = pEventDisp;

    hr = pElt3->fireEvent(eventName, &eventobj, &vBool);

cleanup:
    if (V_BSTR(&name))
        SysFreeString(V_BSTR(&name));
    if (eventName)
        SysFreeString(eventName);
}

Bridging the Generation Gap

Dear Web Team:

I was wondering how to refer to an HTA from a window opened from it. I thought window.opener would work, but unfortunately it hasn't. Any ideas?

Yours sincerely
Chris O'Brien

The Web Team replies:

Window.opener should do the magic for you. If not, then you are probably running into one of these scenarios:

If window.open opens a new window outside your .hta file, then the domains of your .hta and the child window must match, or you will run into all the usual cross-domain security issues.

If you are targeting a frame name within your .hta using window.open, then you need to use the parent or top property, depending on how deeply your frame is nested. You also need to specify the APPLICATION=yes attribute on the frame itself.

If you target a frame in another window, you will not be able to use window.opener since it's a frame and not a child window, per se. You will not be able to use the parent property since the frame is part of a different object model tree. You can set a variable in the child window from the .hta with thewindow property of the .hta, which can then be used to talk back to the .hta. Again, this is possible only as long as the frame page and the .hta are from the same domain. Below is a code snippet to demonstrate how to do this. Three things to watch out for:

  • Make sure the child window is completely loaded before you try to access its object model to set the parent.
  • Make sure the parent variable is set correctly in the child window before you use it.
  • Reset these variables when either the parent or child window is closed. The last part is left as an exercise for our readers.

Copy these files to your Web root or anywhere on your hard drive. Open the TestFrm.htm and then Test.hta. Click Open a new window. In the TestFrm.htm, click Access parent set through script.

TestFrm.htm

<HTML>
<BODY>
<P>This is a test page which contains a frame with the frame name<B>test</B>.</P>
<BR><BR>
<INPUT TYPE=text ID=input1 NAME=input1 VALUE="frmparent">
<BR><BR>
<IFRAME ID=test NAME=test SRC="about:blank"></IFRAME>
</BODY>
</HTML>

Test.hta

<HTML>
<HEAD>
<HTA:APPLICATION>
<SCRIPT>
var childwin;
function OpenWindow()
{   
   childwin = window.open('test.htm','test'); 
   window.setTimeout(SetParent, 1);
}
function SetParent()
{
   if (childwin.document.readyState == "complete")
   {
      childwin.setParent(window);
   }
   else
      window.setTimeout(SetParent, 1);
}
</SCRIPT>
</HEAD>
<BODY>
<INPUT TYPE=button VALUE="Open a new window" ONCLICK=OpenWindow()>
<BR>
<INPUT TYPE=text ID=input1 NAME=input1 value="hta">
</BODY>
</HTML>

Test.htm

<HTML>
<HEAD>
<SCRIPT>
var newparent;
function setParent(newwin)
{
newparent = newwin;
}
function accessParent()
{
   if (newparent!=null && newparent != "undefined") 
      alert(newparent.input1.value) 
   else 
      alert("parent var not set");
}
</SCRIPT>
</HEAD>
<BODY>
<INPUT TYPE=button VALUE="Access parent" ONCLICK="alert(window.parent.input1.value)">
<BR><BR>
<INPUT TYPE=button VALUE="Access parent set thru script" 
onclick=accessParent()>
</BODY>
</HTML>

Keep Passing the Open Windows

Dear Web Team:

I am developing a custom browser using the Microsoft Foundation Class (MFC) CHtmlView class. I want to make all windows open within my custom browser, and not in Microsoft Internet Explorer.

Dhiren Vyas

The Web Team replies:

As you probably already know, developing your own custom browser gives you complete control over your user interface whilst making use of the incredible technology that Internet Explorer has to offer. You get to choose what your browser looks like and what it can do for your customers. Microsoft Visual C++ makes this task even easier by providing an MFC class, CHtmlView, that implements a view based on the WebBrowser control—a reusable component of Internet Explorer.

There are obstacles, though. When a Web page opens a window (by calling window.open, for example) an Internet Explorer object is created. This means that the Web page will appear in an Internet Explorer window. If you want all windows to appear in your application, you can use the NewWindow2 event, available on the DWebBrowserEvents2 interface. We'll demonstrate how this event can be used in a multiple document interface (MDI) MFC application. We'll be brief because this subject is covered in detail in the Knowledge Base article HOWTO: Use the WebBrowser Control NewWindow2 Event (Q184876).

First let's create an MDI MFC application using the AppWizard. During the final page of the wizard, select a view class that is derived from CHtmlView. Then use the ClassWizard to add an event handler for the NewWindow2 event. Finally, add code to your NewWindow2 event handler function:

void CMyHtmlView::OnNewWindow2(LPDISPATCH* ppDisp, BOOL* Cancel) 
{
   // Get a pointer to the application object.
   CWinApp* pApp = AfxGetApp();

   // Get the correct document template.
   POSITION pos = pApp->GetFirstDocTemplatePosition();
   CDocTemplate* pDocTemplate = pApp->GetNextDocTemplate( pos );

   // Create a new frame.
   CFrameWnd* pFrame = pDocTemplate->CreateNewFrame(
                                          GetDocument(),
                                          (CFrameWnd*)AfxGetMainWnd() );

   // Activate the frame.
   pDocTemplate->InitialUpdateFrame( pFrame, NULL );
   CNewWindow2View* pView = (CNewWindow2View*)pFrame->GetActiveView();

   // Pass pointer of WebBrowser object.
   pView->SetRegisterAsBrowser( TRUE );
   *ppDisp = pView->GetApplication();   
}

Navigate to a Web page that opens a new window and you'll see the Web page displayed in your application. This works because your application intercepts the NewWindow2 event, creates a new document/frame/view combination, and passes the IDispatch for the WebBrowser object. This causes your CHtmlView-derived class to be used to display the Web page. Note that the WebBrowser object must be created each time and not have navigated to a URL, or this won’t work.

You'll probably want to make this more useful by obtaining the window information, such as the height and width, so that you can modify your view accordingly. A simple approach to this is to handle the BeforeNavigate2 event that is fired after the new window is created. Add the following code to your application to resize the view to the size of the new window. Note that this is an overridden method that allows access to the WebBrowser object, and that an additional Boolean member, m_bResizeWindow, is required. Set this to false in the constructor and true in the NewWindow2 event handler to ensure this code is only called for new windows.

void CMyHtmlView::BeforeNavigate2(LPDISPATCH pDisp, VARIANT* URL,
      VARIANT* Flags, VARIANT* TargetFrameName,
      VARIANT* PostData, VARIANT* Headers, BOOL* Cancel)
{
   if ( m_bResizeWindow )
   {
   IWebBrowser2*   pWB = NULL;

      // QI the dispatch for WebBrowser control.
      HRESULT hr = pDisp->QueryInterface( IID_IWebBrowser2,
                                          (void**)&pWB );
      if ( SUCCEEDED(hr) )
      {
      long x,y;

         // Get dimensions.
         pWB->get_Width( &x );
         pWB->get_Height( &y );

         // Resize frame.
         SetScrollSizes( MM_TEXT, CSize(x,y) );
         ResizeParentToFit();
         pWB->Release();
      }
      m_bResizeWindow = false;
   }
}

Scrolling in Sweet Synchrony

Dear Web Team:

Hi. I need to scroll two vertically placed frames together, i.e. when one is scrolled, the other should also scroll.

Prithu Nath

The Web Team replies:

Good question. This would provide a great way to compare Web pages, cross-reference parts of a long document, and provide other useful viewing features.

A simple way to provide this feature is to modify the onscroll event property. We'll demonstrate how to do this using Dynamic HTML and two IFRAMEelements.

Here's a quick run-down of what we'll be doing. The content body of each frame fires an onscroll event whenever the user scrolls the content. So we'll capture that event by setting the onscroll event property to a JScript function that will position the content of the other frame to the same scroll position (provided by the scrollTop property).

Now, when accessing the content of frames, there are some rules to consider. Internet Explorer prevents Web pages from snooping around content that doesn't belong to them, so your frames should host pages on the same domain as the parent page. If you attempt to access a frame that is displaying a page hosted on a different domain, you'll get an "Access is denied" scripting error. You've been told. If you really need to do this, you could use an HTML Application, where security is less restrictive.

Here is some code that will enable two frames to scroll together vertically. For your homework, modify this code to also synchronize horizontal scrolling, turn on/off synchronization, and allow for differences between the scroll positions of each page.

<HTML>
<HEAD><TITLE>Synchro</TITLE>
<SCRIPT LANGUAGE="JScript">
var right, left;

function init()
{
  right = document.frames.frmRight;
  left = document.frames.frmLeft;
  left.document.body.onscroll = scrollRight;
  right.document.body.onscroll = scrollLeft;
}

function scrollLeft()
{
  left.document.body.scrollTop = right.document.body.scrollTop;
}

function scrollRight()
{
  right.document.body.scrollTop = left.document.body.scrollTop;
}
</SCRIPT>
</HEAD>
<BODY ONLOAD="init()">
<IFRAME ID="frmLeft" SRC="http://msdn.microsoft.com/" 
STYLE="height:400px;left:10px;position:absolute;top:10px;width:300px">
</IFRAME>
<IFRAME ID="frmRight" SRC="http://msdn.microsoft.com/" 
STYLE="height:400px;left:320px;position:absolute;top:10px;width:300px">
</IFRAME>
</BODY>
</HTML>

<think>我们正在讨论SSH密钥认证过程中的问题。用户希望在使用新密钥登录时过滤掉已经安装的密钥。根据用户的问题,我们需要确保在登录时只使用新密钥,而不使用已经存在于目标服务器上的密钥。在SSH客户端中,我们可以通过配置`~/.ssh/config`文件来指定使用哪个密钥。但是,用户希望自动过滤掉已经安装的密钥,这意味着我们需要一种方法来识别哪些密钥已经存在于服务器上,并在登录时不使用这些密钥。然而,SSH客户端本身并没有直接提供“过滤已安装密钥”的功能。通常,当服务器配置了多个密钥时,客户端会按顺序尝试所有可用的密钥(例如,按照`IdentitiesOnly`和`IdentityFile`配置的顺序)。如果我们想要避免使用某些密钥(即已经安装的密钥),我们需要在客户端配置中排除这些密钥。但是,请注意:客户端无法直接知道服务器上已经安装了哪些密钥。因此,我们需要另一种方法。可能的解决方案:1.手动配置:在客户端的SSH配置文件中,只指定你想要使用的新密钥,而不包含已经安装的密钥。2.使用脚本自动化:编写脚本,在登录前检查服务器上已授权的密钥(例如,通过访问`~/.ssh/authorized_keys`文件),然后动态生成一个只包含新密钥的SSH配置。然而,方法2需要在登录前访问服务器上的`authorized_keys`文件,这通常需要密码或其他认证方式,因此可能不实用。另一种思路:在登录时,客户端可以尝试使用不同的密钥,但服务器会拒绝已经安装的密钥(因为服务器通常只允许每个密钥使用一次?)——实际上,服务器并不会因为密钥已经安装而拒绝连接,而是会接受任何在`authorized_keys`中列出的密钥。所以,如果我们使用已经安装的密钥,登录会成功,但这并不是用户想要的。用户想要使用一个全新的密钥,并且不希望使用已经安装的密钥。因此,问题可能在于:用户想要在同一个客户端上管理多个密钥,但只希望使用未被目标服务器授权过的密钥进行登录。这实际上是一个密钥管理问题。我们可以通过配置SSH客户端,只使用指定的密钥(即新密钥)来连接目标服务器。这样,即使客户端有其他密钥(包括已经安装的密钥),也不会使用它们。具体方法:在`~/.ssh/config`中为特定主机配置时,使用`IdentitiesOnly`选项,并只指定新密钥的路径。例如:```Hostexample.comIdentityFile~/.ssh/new_keyIdentitiesOnlyyes```这样,当连接example.com时,客户端只会尝试使用`new_key`,而忽略其他密钥(包括已经安装的密钥)。如果用户有多个新密钥,可以指定多个`IdentityFile`。所以,用户的问题可以通过配置SSH客户端来解决:设置`IdentitiesOnly`为`yes`,并只列出想要使用的新密钥。注意:用户可能需要为不同的主机配置不同的密钥。另外,用户可能希望在不修改配置文件的情况下动态完成,那么可以在命令行中指定:```ssh-oIdentitiesOnly=yes-i~/.ssh/new_keyuser@host```这样,每次登录时使用这个命令即可。因此,总结回答:通过设置`IdentitiesOnly`为`yes`并指定新密钥的路径,可以确保只使用新密钥进行登录,而忽略其他已经安装的密钥。现在,根据系统指令,我们需要生成相关问题。</think>### 如何在使用新密钥登录时过滤已安装的密钥 在SSH登录过程中过滤已安装的密钥,可通过以下方法实现: #### 1. **配置SSH客户端忽略特定密钥** 在`~/.ssh/config`中针对目标主机配置`IdentitiesOnly`选项,仅加载指定新密钥: ```bash Host your-server HostName server-ip User your-username IdentitiesOnly yes # 关键配置:仅使用显式指定的密钥 IdentityFile ~/.ssh/new_key # 只加载新密钥 ``` 此配置确保SSH客户端**不会尝试**已存在的旧密钥,即使它们在本地代理中[^1]。 #### 2. **命令行临时指定密钥** 登录时通过`-i`参数显式指定新密钥,并启用过滤: ```bash ssh -o IdentitiesOnly=yes -i ~/.ssh/new_key user@host ``` #### 3. **密钥代理管理** 若旧密钥已加载到`ssh-agent`: ```bash # 查看已加载密钥 ssh-add -L # 移除特定旧密钥 ssh-add -d ~/.ssh/old_key # 或清空所有密钥 ssh-add -D ``` 移除后,`ssh-agent`不会提供旧密钥,登录时只会尝试新密钥[^1]。 #### 4. **服务端自动过滤(进阶)** 在服务端`~/.ssh/authorized_keys`文件中,使用`command`选项限制密钥行为: ```bash command="/bin/echo Invalid key" ssh-rsa AAA... old_key ``` 这样当客户端使用旧密钥时,服务器会**立即终止会话**,仅允许新密钥建立完整连接。 #### 验证方法 ```bash # 调试模式查看密钥尝试过程 ssh -v -o IdentitiesOnly=yes -i ~/.ssh/new_key user@host ``` 在输出中检查`Offering public key`部分,确认仅出现新密钥路径。 > **关键点**:`IdentitiesOnly yes`是核心配置项,它阻止SSH客户端默认尝试所有可用密钥,仅使用`IdentityFile`显式指定的密钥[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值