本问题由吴越洋同学首先解决,在阅读了吴越洋同学的博客和他编写的Tempermonkey插件脚本代码后,完成了本项目。
吴越洋同学的博客地址为修复NENU教务系统“不能创建对象”等兼容性问题 - YorkWu's Blog
吴越洋同学的插件地址为东北师范大学本科生、研究生教务系统修复
该项目以Tampermonkey脚本的形式发布,链接为东北师范大学研究生教务系统网站问题修复插件,可以在Chrome浏览器和Firefox浏览器中安装使用。
一直以来,使用Chrome浏览器或者Firefox浏览器访问学校教务系统网站都会被告知“系统不能创建对象”,并且提示信息还会推荐使用360浏览器的兼容模式(图1)。不过偶然间得知QQ浏览器也能正常访问学校教务系统,所以只在计算机中下载了QQ浏览器专门访问学校的教务系统来查成绩。
使用QQ浏览器访问时,教务系统界面正常显示,所有功能都可以正常使用。如图1所示。
使用Chrome浏览器访问时,出现情况如图2所示。
点击弹窗的“确定”按钮后,教务网站显示情况如图3所示,“培养方案”、“教学周历管理”、“学生选课”和“成绩信息”的文字和上方图标消失,用户无法点击,查询成绩、查看教学周历等功能也无法使用。
使用Firefox浏览器访问教务系统网站时,出现的现象与Chrome浏览器访问教务系统网站时的现象相同,如图4和图5所示。
为了解决以上问题,使用编写Tempermonkey脚本的方法,在网页的DOM树中注入脚本,以实现对网页某些元素的修改,并且不影响研究生教务系统网站任何其他功能的正常使用。
先展示Tampermonkey脚本完成后启用的效果,解决过程随后分章节描述。
在Chrome浏览器中启用已完成的Tampermonkey脚本,再次通过Chrome浏览器访问教务系统,系统可以正确显示且网站功能都可正常使用,如图6所示;
在Firefox浏览器中启用脚本,再次通过Firefox浏览器访问教务系统,系统可以正确显示且网站功能都可正常使用,如图7所示;
为了保证使用脚本时不影响研究生教务系统网站任何其他功能的正常使用,在完成的Tampermonkey脚本过程中做了自动化回归测试。自动化测试过程见下一篇文章。
下面分章节描述出现问题的原因以及问题的解决过程。
1.解决“系统不能创建对象”问题
1.1 正确的现象
教务网站正确显示时,如图8所示。
预期的正确现象如下。
(1)正确显示 NENU研究生教务系统网站(http://dsyjs.nenu.edu.cn);
(2)网站中“培养方案”、“教学周历管理”、“学生选课”和“成绩信息”4行文字和其上方的图标,按照图2所示的方式排列;
(3)网页下方的导航栏以及导航栏中的图标可以正常显示;
(4)网页右边的侧边栏可以正常显示。
1.2 错误的现象
使用Chrome浏览器打开教务网站时,教务网站显示如图9所示。
点击弹窗的“确定”按钮后,教务网站显示情况如图10所示。
在Chrome浏览器打开NENU研究生教务系统网站时产生的错误现象如下。
(1)弹出窗口,窗口显示信息“系统不能创建对象(请使用360浏览器的兼容模式)”;
(2)弹出窗口时,网页中不显示“培养方案”、“教学周历管理”、“学生选课”和“成绩信息”4行文字和其上方的图标;
(3)点击“确定”按钮后,网页中不显示“培养方案”、“教学周历管理”、“学生选课”和“成绩信息”4行文字和其上方的图标;
(4)弹出窗口时以及点击“确定”按钮后,底部导航栏和右侧侧边栏都可以正常显示。
1.3 错误的原因
为了确定在什么条件下会触发图1所示的错误信息 ,检查网页的源代码。
在core.js文件中的send_request()方法中找到了图1所示的提示信息文字。
为了方便分析在Chrome浏览器中打开教务网站时总会提示图1所示的错误信息的原因,将包含图1所示错误信息文字的代码以及上下文展示在下方,send_request()方法的完整代码见附录。
http_request = false;
if(window.XMLHttpRequest && 1==2 )
{
http_request = new XMLHttpRequest();
if (http_request.overrideMimeType)
{
http_request.overrideMimeType("text/xml");
}
}
else if (window.ActiveXObject)
{
try
{
http_request = new ActiveXObject("Msxml2.XMLHTTP");
}
catch (e)
{
try
{
http_request = new ActiveXObject("Microsoft.XMLHTTP");
}
catch (ei)
{}
}
}
if (!http_request)
{
window.alert("系统不能创建对象!(请使用360浏览器的兼容模式)!");
return false;
}
下面分析以上代码的逻辑。
http_request的初始值为布尔类型的值,false;
第一个if的判断条件为“window.XMLHttpRequest && 1==2 ”,由于1 == 2的值为false,所以一定无法执行if结构体中的代码;
接着判断else if 的判断条件“window.ActiveXObject”是否为真;
通过访问微软官网以及查看JavaScript的API参考文档了解到,ActiveXObject只支持IE浏览器以及以IE浏览器为内核的浏览器。
所以在Chrome浏览器和Firefox浏览器中访问时,else if的判断条件不满足,else if结构体中的代码无法编译执行。
http_reques的值仍然为false;
第2个if的判断条件为“!http_request” ,“!http_request”的值true;
第2个if结构体中的代码成功执行,弹出弹窗提示用户“系统不能创建对象!(请使用360浏览器的兼容模式)!”,且函数返回false。
以上为在Chrome浏览器和Firefox浏览器中访问 NENU研究生教务系统网站(http://dsyjs.nenu.edu.cn)时产生错误提示的分析过程,总结原因为在NENU研究生教务系统网站的源代码中,使用的API仅支持IE内核的浏览器,导致使用非IE内核的浏览器访问网站时,提示错误信息。
1.4 解决过程
在Tempermonkey插件中新建脚本“修复demo”,脚本编写方法和网址匹配方法见Tempermonkey官方文档(Documentation | Tampermonkey)。
由于在send_request()方法中,ActiveXObject的使用使得http_request没有被实例化,进而产生错误信息。
那么我们可以想到在脚本中新建一个方法send_reuqest_replace(),代替教务网站中的send_reuqest()方法,在新建的方法中实例化http_request,但若想网站可以正常运行,则不能丢失send_reuqest()方法中所实现的核心功能,所以,send_reuqest()方法中除了http_request实例化的其他功能代码都必须在send_reuqest_replace()中实现。
首先,在send_reuqest_replace()方法中实例化http_request,将send_reuqest()方法中除了http_request实例化的其他功能代码复制到send_reuqest_replace()方法中;
此时,保存并运行后,会报新的错误(加载编号为0的应用系统失败,可能是网络延迟问题!);
再次检查代码,发现selectNodes方法也只能支持IE内核的浏览器,而getElementsByTagName方法可以支持除IE浏览器之外的其他浏览器;
保存代码并刷新页面后,控制台依然出现错误,打印topXml数组,发现代码topXml[i].attributes.getNamedItem("parentid").text获取到的信息为undefined。检查网页元素,发现text中的内容对应属性名为textContent,将“.text”替换为“. textContent”后,保存代码并运行,教务系统不弹出错误窗口,且网页可以正常显示。send_reuqest_replace()方法的完整代码见附录。
2.关闭成绩认定“设置”窗口
成绩信息包括成绩查询和交流成绩认定2个板块,点击“交流成绩认定”,出现交流成绩认定板块的功能页面,如图11所示。
2.1 正确的现象
点击图11所示页面中的“设置”按钮,弹出窗口如图12所示。
点击“确定”或“取消”按钮时,窗口关闭。
2.2 错误的现象
点击图11所示页面中的“设置”按钮,弹出弹窗如图12所示;
点击“确定”或“取消”按钮时,页面无响应,弹窗无法关闭,控制台打印错误信息如图13所示。
2.3 错误的原因
检查页面源代码后,发现在jspublic.js文件中的doCanCelTableSet方法中,使用了removeNode方法;
经查阅资料了解到,removeNode方法只支持IE内核的浏览器,所以在Chrome浏览器中无法正常使用。
2.4 解决过程
经查阅资料了解到removeChild方法同样可以实现removeNode方法的功能,且removeChild法支持非IE内核的浏览器。
在脚本中新建doCancelTableSet_replace方法,使用removeChild方法代替removeNode方法实现doCanCelTableSet方法所实现的功能,保存并运行代码,问题解决。
doCancelTableSet_replace方法关键代码如下。
function doCancelTableSet_replace(url, SystemBh){
if (document.getElementById('TblShowSetDiv') != null)
{
document.getElementById('TblShowSetDiv').parentNode.removeChild(document.getElementById('TblShowSetDiv'));
console.log(document.getElementById('TblShowSetDiv'));
}
document.getElementById('alldiv').disabled = false;
}
将网页中的doCancelTableSet方法替换为doCancelTableSet_replace方法。
window.doCanCelTableSet = doCancelTableSet_replace
3.新增成绩认定记录
在图5所示的交流成绩认定板块,点击“增加”按钮,可以新增成绩认定记录。
3.1 正确的现象
点击“增加”按钮,弹出窗口如图14所示。
在弹窗中填写信息,并保存,如图15所示。
在成绩交流页面成功增加一条记录,如图16所示。
3.2 错误的现象
点击“增加”按钮,网页没有任何反应;
检查网页,发现控制台打印错误信息如图17所示。
3.3 错误的原因
检查网页源代码,在jls_cjrd.jsp文件中存在JsMod方法,在该方法中使用了showMadalDialog方法;
经查阅资料了解到showMadalDialog方法只支持IE浏览器,不支持非IE内核的浏览器。JsMod方法代码如图18所示。
3.4 解决过程
在脚本中使用JsMod_replace方法实现JsMod方法代码的功能;
经查阅资料了解到可以使用支持Chrome浏览器的open()方法替代showMadalDialog方法。
JsMod_replace方法代码如下。
function JsMod_replace(htmlurl, tmpWidth, tmpHeight) {
htmlurl = getRandomUrl(htmlurl);
var newwin = window.open(htmlurl, window, "width=" + tmpWidth + "px; status=no;height=" + tmpHeight + "px");
if (newwin != null && newwin == "ok") {
window.location.href = window.location.href;
}
}
将网页中的JsMod方法替换为JsMod_replace方法。
window.JsMod = JsMod_replace;
总结
总体来说,之所以在Chrome浏览器和Firefox浏览器访问教务系统网站会出现各种错误,是由于网站使用了一些只有IE支持的API。使用目前主流浏览器都支持的API替代那些只有IE浏览器支持的API,问题就迎刃而解。
解决了本文中的3个问题后,研究生教务系统基本可以正常使用,但由于本项目周期较长,目前网站与刚做本项目时相比有很大改变,比如“成绩交流认定”板块被取消等。
致谢
感谢吴越洋同学提供解决学校网站一直以来存在的“不能创建对象”问题的想法以及解决问题的思路;
感谢我的师兄田洪轩帮助我学习如何通过控制台定位问题,帮助我在控制台打印问题所在的内容,为我提供遇到问题如何解决的思路;
感谢我的导师杨贵福为我提供自动化测试的思路,及时指出我在项目过程中的错误并给出建议。
附录
1.send_request()方法
function send_request(url,SystemBh)
{
http_request = false;
if(window.XMLHttpRequest && 1==2 )
{
http_request = new XMLHttpRequest();
if (http_request.overrideMimeType)
{
http_request.overrideMimeType("text/xml");
}
}
else if (window.ActiveXObject)
{
try
{
http_request = new ActiveXObject("Msxml2.XMLHTTP");
}
catch (e)
{
try
{
http_request = new ActiveXObject("Microsoft.XMLHTTP");
}
catch (ei)
{}
}
}
if (!http_request)
{
window.alert("系统不能创建对象!(请使用360浏览器的兼容模式)!");
return false;
}
try
{
http_request.open("POST",url, false);
http_request.setRequestHeader("CONTENT-TYPE","application/x-www-form-urlencoded");
http_request.send(null);
var tmpxml = http_request.responseXML;
//加载顶层菜单开始
var topXml = tmpxml.selectNodes("/Menus/topMenus/Menu");
for(i=0;i<topXml.length;i++)
{
topMenuItems[topMenuLength] = new Array();
topMenuItems[topMenuLength][0] = topXml[i].attributes.getNamedItem("parentid").text;
topMenuItems[topMenuLength][1] = SystemBh + "_" + topXml[i].attributes.getNamedItem("id").text;
topMenuItems[topMenuLength][2] = topXml[i].attributes.getNamedItem("name").text;
topMenuItems[topMenuLength][3] = topXml[i].attributes.getNamedItem("title").text;
topMenuItems[topMenuLength][4] = topXml[i].attributes.getNamedItem("path").text;
topMenuItems[topMenuLength][5] = topXml[i].attributes.getNamedItem("imageUrl").text;
topMenuItems[topMenuLength][6] = topXml[i].attributes.getNamedItem("defaultPage").text;
topMenuLength++;
}
//加载顶层菜单结束
//加载一层菜单开始
var menuXml = tmpxml.selectNodes("/Menus/Level1Menus/Menu");
for(i=0;i<menuXml.length;i++)
{
menuItems[menuLength] = new Array();
menuItems[menuLength][0] = SystemBh + "_" + menuXml[i].attributes.getNamedItem("parentid").text;
menuItems[menuLength][1] = SystemBh + "_" + menuXml[i].attributes.getNamedItem("id").text;
menuItems[menuLength][2] = ' '+menuXml[i].attributes.getNamedItem("name").text;
menuItems[menuLength][3] = menuXml[i].attributes.getNamedItem("title").text;
menuItems[menuLength][4] = menuXml[i].attributes.getNamedItem("path").text;
menuItems[menuLength][5] = menuXml[i].attributes.getNamedItem("imageUrl").text;
menuLength++;
}
//加载一层菜单结束
//加载二层菜单开始
var linkXml = tmpxml.selectNodes("/Menus/Level2Menus/Menu");
for(i=0;i<linkXml.length;i++)
{
linkItems[linkLength] = new Array();
linkItems[linkLength][0] = SystemBh + "_" + linkXml[i].attributes.getNamedItem("parentid").text;
linkItems[linkLength][1] = SystemBh + "_" + linkXml[i].attributes.getNamedItem("id").text;
linkItems[linkLength][2] = ' '+linkXml[i].attributes.getNamedItem("name").text;
linkItems[linkLength][3] = linkXml[i].attributes.getNamedItem("title").text;
linkItems[linkLength][4] = linkXml[i].attributes.getNamedItem("path").text;
linkItems[linkLength][5] = linkXml[i].attributes.getNamedItem("imageUrl").text;
linkLength++;
}
//加载二层菜单结束
}
catch(eii)
{alert("加载编号为"+SystemBh+"的应用系统失败,可能是网络延迟问题!");}
}
2.send_request_replace()方法
function send_request_replace(url, SystemBh){
var http_request = new XMLHttpRequest();
try
{
http_request.open("POST",url, false);
http_request.setRequestHeader("CONTENT-TYPE","application/x-www-form-urlencoded");
http_request.send(null);
var tmpxml = http_request.responseXML;
console.log(tmpxml);
//加载顶层菜单开始
var topXml = tmpxml.getElementsByTagName("topMenus")[0].getElementsByTagName("Menu");
console.log(topXml);
for(let i=0;i<topXml.length;i++)
{
topMenuItems[topMenuLength] = new Array();
topMenuItems[topMenuLength][0] = topXml[i].attributes.getNamedItem("parentid").textContent;
topMenuItems[topMenuLength][1] = SystemBh + "_" + topXml[i].attributes.getNamedItem("id").textContent;
topMenuItems[topMenuLength][2] = topXml[i].attributes.getNamedItem("name").textContent;
topMenuItems[topMenuLength][3] = topXml[i].attributes.getNamedItem("title").textContent;
topMenuItems[topMenuLength][4] = topXml[i].attributes.getNamedItem("path").textContent;
topMenuItems[topMenuLength][5] = topXml[i].attributes.getNamedItem("imageUrl").textContent;
console.log(topMenuItems[topMenuLength][0]);
console.log(topMenuItems[topMenuLength][1]);
console.log(topMenuItems[topMenuLength][2]);
console.log(topMenuItems[topMenuLength][3]);
console.log(topMenuItems[topMenuLength][4]);
topMenuItems[topMenuLength][6] = topXml[i].attributes.getNamedItem("defaultPage").textContent;
console.log(topMenuItems[topMenuLength][6]);
topMenuLength++;
}
//加载顶层菜单结束
//加载一层菜单开始
var menuXml = tmpxml.getElementsByTagName("Level1Menus")[0].getElementsByTagName("Menu");
console.log(menuXml);
for(let i=0;i<menuXml.length;i++)
{
menuItems[menuLength] = new Array();
menuItems[menuLength][0] = SystemBh + "_" + menuXml[i].attributes.getNamedItem("parentid").textContent;
menuItems[menuLength][1] = SystemBh + "_" + menuXml[i].attributes.getNamedItem("id").textContent;
menuItems[menuLength][2] = ' '+menuXml[i].attributes.getNamedItem("name").textContent;
menuItems[menuLength][3] = menuXml[i].attributes.getNamedItem("title").textContent;
menuItems[menuLength][4] = menuXml[i].attributes.getNamedItem("path").textContent;
menuItems[menuLength][5] = menuXml[i].attributes.getNamedItem("imageUrl").textContent;
menuLength++;
}
//加载一层菜单结束
//加载二层菜单开始
var linkXml = tmpxml.getElementsByTagName("Level2Menus")[0].getElementsByTagName("Menu");
for(let i=0;i<linkXml.length;i++)
{
linkItems[linkLength] = new Array();
linkItems[linkLength][0] = SystemBh + "_" + linkXml[i].attributes.getNamedItem("parentid").textContent;
linkItems[linkLength][1] = SystemBh + "_" + linkXml[i].attributes.getNamedItem("id").textContent;
linkItems[linkLength][2] = ' '+linkXml[i].attributes.getNamedItem("name").textContent;
linkItems[linkLength][3] = linkXml[i].attributes.getNamedItem("title").textContent;
linkItems[linkLength][4] = linkXml[i].attributes.getNamedItem("path").textContent;
linkItems[linkLength][5] = linkXml[i].attributes.getNamedItem("imageUrl").textContent;
linkLength++;
}
//加载二层菜单结束
}
catch(e)
{
console.log(e);
alert("加载编号为"+SystemBh+"的应用系统失败,可能是网络延迟问题!");
}
}