最近在所一款办公自动化的软件,需要可以静默上传文件,改造了cefsharp的dialoghanlder,代码如下

public bool OnFileDialog(IWebBrowser chromiumWebBrowser, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback)
        {
            /*拦截后,可以指定要返回的值
            if (!callback.IsDisposed)
            {
                using (callback)
                {
                    List<string> filepath = new List<string> { };
                    filepath.Add(@"C:\测试.txt");
                    callback.Continue(1, filepath);
                }
            }

            return true;*/

            return false;
        }
    }

网页前端采用的element,上传用的el-upload,直接用Browser.ExecuteScriptAsync执行Javascript脚本对指定的上传按钮进行click(),

执行的JavaScript脚本如下

elementsShareFamily = function (primaryEl, siblingEl) {
    if (primaryEl.tagName === siblingEl.tagName &&
        (!primaryEl.className || primaryEl.className === siblingEl.className) &&
        (!primaryEl.id || primaryEl.id === siblingEl.id)) {
        return true;
    }
    return false;
};

getElementIndex = function (el) {
    var className = el.className;
    var id = el.id;

    var index = 1;  // XPath is one-indexed
    var sib;
    for (sib = el.previousSibling; sib; sib = sib.previousSibling) {
        if (sib.nodeType === Node.ELEMENT_NODE && elementsShareFamily(el, sib)) {
            index++;
        }
    }
    if (index > 1) {
        return index;
    }
    for (sib = el.nextSibling; sib; sib = sib.nextSibling) {
        if (sib.nodeType === Node.ELEMENT_NODE && elementsShareFamily(el, sib)) {
            return 1;
        }
    }
    return 0;
};

//通过element获取xpath 方法2
getElementXpath2 = function (el, callback) {
    var query = '';
    for (; el && el.nodeType === Node.ELEMENT_NODE; el = el.parentNode) {
        var component = el.tagName.toLowerCase();
        var index = getElementIndex(el);
        if (el.id) {
            component += '[@id=\"' + el.id + '\"]';
        } else if (el.className) {
            component += '[@class=\"' + el.className + '\"]';
        }
        if (index >= 1) {
            component += '[' + index + ']';
        }
        // If the last tag is an img, the user probably wants img/@src.
        if (query === '' && el.tagName.toLowerCase() === 'img') {
            component += '/@src';
        }
        query = '/' + component + query;
    }
    return query;
};


//获取xpath 方2
function getElementXpath(element,callback) {
    if (element.id !== "" ) {
        //判断id属性,如果这个元素有id,则显 示//*[@id="xPath"]  形式内容
        return '//*[@id=\"' + element.id + '\"]';
    }
    //这里需要需要主要字符串转译问题,可参考js 动态生成html时字符串和变量转译(注意引号的作用) 
    if (element.tagName.toLowerCase() == "body") {//递归到body处,结束递归
        return '/html/' + element.tagName.toLowerCase();
    }
    var ix = 1,//在nodelist中的位置,且每次点击初始化
        siblings = element.parentNode.childNodes;//同级的子元素

    for (var i = 0, l = siblings.length; i < l; i++) {
        var sibling = siblings[i];
        //如果这个元素是siblings数组中的元素,则执行递归操作
        if (sibling == element) {
            return arguments.callee(element.parentNode) + '/' + element.tagName.toLowerCase() + '[' + (ix) + ']';
            //如果不符合,判断是否是element元素,并且是否是相同元素,如果是相同的就开始累加
        } else if (sibling.nodeType == 1 && sibling.tagName == element.tagName) {
            ix++;
        }
    }
}

//通过xpath获取element
function queryElementByXPath(e, path) { 
    var evaluator = new XPathEvaluator();
    var result = evaluator.evaluate(path, e.documentElement, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
    return result.singleNodeValue;
};

//获取页面当前活动的frame
function getFrame(frame,url,basejs)
{ 
  var status=0;
  var frames=frame.getElementsByTagName('iframe');
  //先从本节点去找,本节点找不到,递归找子节点
  for(var i=0;i<frames.length;i++)
   { 
       if(frames[i].src==url||frames[i].contentDocument.location.href.split("?")[0]==url.split("?")[0])
       {
          status=1;
          basejs+=".getElementsByTagName('iframe')["+i+"]";
          break;
       }
       else
       {
         var tmp=basejs;
         basejs+=".getElementsByTagName('iframe')["+i+"].contentDocument";
         var re= getFrame(frames[i].contentDocument,url,basejs);
         if(re.status==1)
         {
             status=re.status;
             basejs=re.js;
             break;
         }
         else
         {
           basejs=tmp;
         }
       }
   }  
   return {status:status,js:basejs};
} 
//执行js
function excuteJs(frameurl,xpath)
{
    //获取当前frame的js脚本
    var frameJs =getFrame(document,frameurl,"document");   
    var e=frameJs.js=='document'?eval(frameJs.js):eval(frameJs.js+'.contentDocument'); 

    var ret= queryElementByXPath(e,xpath);  
    ret.click(); //执行dom元素的点击事件
    return ret;
}                    
excuteJs("https://element.eleme.cn/#/zh-CN/component/upload",'//*[@id="app"]/div[2]/div[1]/div[1]/div[1]/div[1]/div[2]/section[1]/div[1]/div[1]/div[1]/div[1]/div[1]/button[1]/span[1]');
                

理论上所有逻辑都没有问题,但是在执行js脚本,无法打开选择文件对话框

在控制台可以看到“File chooser dialog can only be shown with a user activation”的警告文本

当鼠标点击住浏览器的任意网页空白处,则可以弹出对话框,

搜索全网,只有selenium的解决方案,但是不想再引入一套cef的driver,索性直接模拟浏览器点击事件解决。

方法如下:

在执行Browser.ExecuteScriptAsync 之前,增加模拟鼠标的点击事件

 //浏览器先点击一下,为了保持当前浏览器为活动状态,否则部分由弹窗(附件上传)打不开对话框
Browser.GetBrowserHost().SendMouseClickEvent(1, 1, MouseButtonType.Left, false, 1, CefEventFlags.None);

至此,问题解决。

Logo

更多推荐