This page looks plain and unstyled because you're using a non-standard compliant browser. To see it in its best form, please visit upgrade to a browser that supports web standards. It's free and painless.

Fillano's Learning Notes 會員登入 會員註冊

同樣是在ajaxpatterns.org網站上看到的,他是假設一篇blog文章有200個manual comments與50個trackback comments,每個comments使用一個物件實體來掌控的狀況下,有可能會耗費比較大量的記憶體。與其用250個物件來掌控這些comments,不如參考flyweight pattern的方法,用兩個物件來掌控。

因為只要符合flyweight的結構就可以,所以稍微簡化ajaxpatterns.org所講的狀況,假設manual comments只顯示comment的文字,trackback則會顯示trackback的網址。如果沒有應用flyweight pattern,程式可能會像這樣:

function manual (content) {
	this.content = content;
	this.draw = function (target) {
		var a = document.createElement("div");
		a.innerHTML = this.content;
		.........
		target.appendChild(a);
		a = null;
		delete a;
	}
}
var manuals = [];
for (key in comments) {
	manuals.push(new manual(comments[key]));
}
for (key in manuals) {
	manuals[key].draw(target);
}

(假設comments資料放在comments陣列變數裡面,最後會顯示在target變數參考的物件內)然後用類似的方法處理trackback。

function trackback (content) {
	this.content = content;
	this.draw = function (target) {
		var a = document.createElement("div");
		a.innerHTML = "<a href='"+this.content+"' target='_blank'>"+this.content+"</a>";
		.........
		target.appendChild(a);
		a = null;
		delete a;
	}
}
var trackbacks= [];
for (key in comments) {
	trackbacks.push(new trackback(comments[key]));
}
for (key in trackbacks) {
	trackbacks[key].draw(target);
}

從以上的程式可以看出來,總共產生了非常多的manual物件實例還有trackback物件實例來顯示所有的comments,會佔用比較多記憶體。

改成用flyweight pattern來實作的話,就要稍微調整一下。首先設計一個commentManager函數物件,用pool陣列來存放相應的comment物件,用draw方法來顯示comments,用_commentFactory方法來從pool中取得comment物件參考。如果物件不在pool陣列中,則加入。這樣可以保證每一種物件只使用一個實例。以下是flyweight.js的內容:

function commentManager () {
	this.pool = {};
	this._commentFactory = function (type) {
		if (this.pool[type]) {
			return this.pool[type];
		} else {
			if (eval(type)) {
				this.pool[type] = eval("new "+type+"()");
				return this.pool[type];
			} else {
				return new Flyweight();
			}
		}
	}
	this.draw = function (target, comments) {
		for (obj in comments) {
			this._commentFactory(comments[obj][0]).draw(target, comments[obj][1]);
		}
	}
}

function Flyweight () {
	this.draw = function (target, content) {
	}
}

var comments = [];

接下來設計把外部狀態移出後的manual物件,以及所有manual comments的資料,放在manual.js裡面:

function manual () {
	this.draw = function (target, content) {
		var a = document.createElement("div");
		a.style.border = "dashed gray 1px";
		a.style.backgroundColor = "#DDDDDD";
		a.style.margin = "5px 5px 5px 5px";
		a.style.padding = "5px 5px 5px 5px";
		a.innerHTML = content;
		target.appendChild(a);
		a = null;
		delete a;
	}
}
manual.prototype = new Flyweight;

comments.push(
["manual", "這是第1篇comment,其他還有很多comments。"],
["manual", "這是第2篇comment,其他還有很多comments。"],
..........
["manual", "這是第199篇comment,其他還有很多comments。"],
["manual", "這是第200篇comment,其他還有很多comments。"]);

用類似的方式設計trackback.js:

function trackback () {
	this.draw = function (target, content) {
		var a = document.createElement("div");
		a.style.border = "dashed #334455 1px";
		a.style.backgroundColor = "#99CC99";
		a.style.margin = "5px 5px 5px 5px";
		a.style.padding = "5px 5px 5px 5px";
		a.innerHTML = "<a href='"+content+"' target='_blank'>"+content+"</a>";
		target.appendChild(a);
		a = null;
		delete a;
	}
}
trackback.prototype = new Flyweight;

comments.push (
["trackback", "http://www.test.com/trackback?id=0"],
["trackback", "http://www.test.com/trackback?id=1"],
..................
["trackback", "http://www.test.com/trackback?id=48"],
["trackback", "http://www.test.com/trackback?id=49"]);

稍微說明一下,這裡面用到一些耦合來讓不同種的comment可以動態,模組化地載入。在commentManager._commentFactory函數中,傳入的type變數與manual.js中的manual函數與comments陣列中的"manual"是有關係的,因為名稱相同,所以可以用eval來將manual函數物件的實例放進pool中。因為有這樣的耦合,還有需要用comments變數存放comments資料,所以flyweight.js必須在manual.js以及trackback.js之前載入。

接下來做一個網頁來測試一下效果:

<html>
<script src="flyweight.js"></script>
<body>
<div style="border: solid 1px black; vertical-align: top">Comments 
<input type="button" value="產生" onclick="show()">
<div id="comment">
</div>
</div>
<script src="manual.js"></script>
<script src="trackback.js"></script>
<script>
function show() {
	var tmp = document.getElementById("comment");
	var commentManager1 = new commentManager();
	commentManager1.draw(tmp, comments);
}
</script>
</body>
<html>

(上面的「<」是全形的喔,直接拷貝不能用,請注意)

接下來看一下執行的效果,第一張是還未顯示comments前:

第二張是顯示comments:

第三章可以看到有200個manual comments,接下來是50個trackback comments:

有興趣試試的話,以下是試作的網址:

http://www.fillano.idv.tw/test82.html

 


[補充說明] 2007-6-29

為了比較使用flyweight跟不使用flyweight到底有什麼差別,做了一下小小的測試:

  1. 產生五千個manual comments跟五千個trackback comments
  2. 盡量讓兩個方法不會差太多.....(這比較難精確)
  3. 動態產生完html以後,中途使用的變數用delete刪除(後來發現沒有用)
  4. 在show前後使用Date.getTime()來粗略估計動作執行時間
  5. 用windows的工作管理員粗略估計使用的記憶體
  6. 使用firefox的安全模式,首頁設為空白,然後再開啟檔案,分別紀錄打開firefox後、檔案載入後、動作執行後的記憶體使用量

.....結果大致符合預期:使用flyweight速度稍慢,沒有使用flyweight記憶體用比較多。記憶體的差距大約有3932K,速度差距大約有584.8ms。

ajax patterns網站介紹了非常多的ajax design patterns,其中lazy load pattern具有相當的重要性,可以讓應用程式視需要載入適當的javascript模組,減少第一次載入網頁的時間。

這裡嘗試結合xmlhttp/xmlhttprequest來實作lazy load pattern,要載入的模組最後會透過eval()函數來執行。

在開始做以前,先說一下幾個原則:

  1. 在網頁中執行javascript時,global object就是window物件。
  2. 盡量使用匿名函數來把功能載入,這樣可以避免變數名稱的衝突。
  3. 考慮到函數/物件之間的相依性來規劃javascript模組。
  4. 在載入模組的函數中實作簡單的same site policy,稍微提高安全性。

根據以上的原則,我試作了一個簡單的模組載入功能(init.js),以及一個以ajax的方式送出post請求的物件,叫做ajaxform(base.js)。

接下來,就來嘗試吧...先做init.js:

try {
	window.execScript("null");
} catch (e) {
	window.execScript = function (statement) {
		eval(statement, window);
	}
}
function load(path) {
	try {
		if (path.indexOf(":") > -1) {
			throw "ERROR: ':' in path is not allowed.";
			// an implement of a simplest "same site" policy
		}
		var loader = xmlhttp();
		loader.open("GET", path, false);
		loader.send(null);
		if (loader.status == 200) {
			window.execScript(loader.responseText);
		} else {
			throw "ERROR: " + loader.status;
		}
	} catch (e) {
		alert(e);
	}
}
function xmlhttp() {
	try{return new ActiveXObject("Msxml2.XMLHTTP");} catch(e){}
	try{return new ActiveXObject("Microsoft.XMLHTTP");} catch(e){}
	try{return new XMLHttpRequest();} catch(e){}
	alert("XMLHttpRequest Object not existed!!");
	return null;
}

接下來試作form.js(這裡偷懶了,從以前做過的東西改的):

(function () {
//-- start
window.ajaxform = function (_host) {

// Public Properties

	this.xmlhttp = new xmlhttp();
	this.result = "";
	this.resultXml = "";
	this.callback = new Function();

// Private Methods

	this._createBoundary = function () {
		var tmp = Math.random();
		var thisDate = new Date();
		tmp = Math.abs(tmp*thisDate.getTime());
		tmp = "--------" + tmp + "--------";
		return tmp;
	};

	this._createField = function (_field, _value) {
		var tmp = "--" + this._boundary + this.CRLF;
		tmp += "Content-Disposition: form-data; name="" + _field + """ + this.CRLF;
		tmp += "Content-Transfer-Encoding: binary" + this.CRLF + this.CRLF;
		tmp += _value + this.CRLF
		tmp += "--" + this._boundary + "--" + this.CRLF + this.CRLF;
		return tmp;
	};

	var formObj = this;
	this.xmlhttp.onreadystatechange = function () {
		if (formObj.xmlhttp.readyState == 4) {
			if (formObj.xmlhttp.status == 200) {
				formObj.result = formObj.xmlhttp.responseText;
				formObj.resultXml = formObj.xmlhttp.responseXML;
				(formObj.callback)();
			}
			else {
				formObj.errCode = formObj.xmlhttp.status;
				formObj.result = formObj.xmlhttp.responseText;
				alert("Error in request!! HTTP RESPONSE STATUS: " + formObj.errCode);
			}
		}
	};

// Private Properties

	this._boundary = this._createBoundary();
	this._host = _host;
	this._fields = {};
	this.CRLF = "rn";

// Public Methods

	this.addField = function (_field, _value) {
		this._fields[_field] = _value;
	};

	this.removeField = function (_field) {
		if (this._fields[_field])
			delete this._fields[_field];
	};

	this.emptyField = function () {
		this._fields = {};
	};

	this.send = function () {
		this.xmlhttp.open("POST", this._host);
		var msgBody = "";
		for (key in this._fields){
			msgBody += this._createField(key, this._fields[key]);
		}
		this.xmlhttp.setRequestHeader("Content-Type","multipart/form-data; charset=BIG5; boundary="+this._boundary);
		this.xmlhttp.setRequestHeader("Connection","Keep-Alive");
		this.xmlhttp.setRequestHeader("Content-Length",msgBody.length);
		this.xmlhttp.send(msgBody);
	};
	this.setCallback = function (obj) {
		if (obj instanceof Function) {
			this.callback = obj;
		}
	};
};
//-- end
})();

說明一下,ajaxform物件有幾個函數:

  1. addField:用來加入表單的欄位跟值
  2. constructor:用參數傳入action的網址
  3. send:送出表單
  4. setCallback:指定xmlhttp的callback函數(間接執行),可以直接傳一個匿名函數或是函數的參考給它

為了要測試,寫了一個簡單的php來檢驗傳進來的東西:

<?php
print_r($_POST);
?>

最後用一個簡單的網頁把這些組合起來測試:

<html>
<head>
</head>
<script language="javascript" src="init.js"></script>
<script language="javascript">
	load("form.js");
</script>
<body>
<script>
function sendpost () {
	try {
		var form1 = new ajaxform("test74.php");
		form1.addField("title","中庸-經一章");
		form1.addField("field1","天命之謂性,率性之謂道,修道之謂教。道也者,不可須臾離也;可離,非道也。");
		form1.addField("field2","是故君子戒慎乎其所不睹,恐懼乎其所不聞。莫見乎隱,莫顯乎微,故君子慎其獨也。");
		form1.addField("field3","喜怒哀樂之未發,謂之中;發而皆中節,謂之和。");
		form1.addField("field4","中也者,天下之大本也;和也者,天下之達道也。");
		form1.addField("field5","致中和,天地位焉,萬物育焉。");
		form1.setCallback(function(){alert(form1.result);delete form1;});
		form1.send();
	} catch (e) {alert(e);}
}
</script>
<input type="button" value="test" onclick="sendpost()">
</body>
</html>

執行的結果如下,按下「test」按鍵,然後透過ajax送出post request,再用alert秀出傳回來的response。

測試的連結:http://www.fillano.idv.tw/test74.html

今天在論壇看到,在style中使用特別的語法可以嵌入script....真是危險呢。想知道的話,上google找關鍵字「xss」還有「expression」就可以找到不少例子跟討論。(不過好像不影響Firefox)

自己試寫了一下,果然可以執行,這有可能造成安全的問題耶?!

語法像這樣:

style="font-size: 18px; xss:expression(程式碼)"
或是
div {font-size: 18px; xss:expression(程式碼)}

都有相同的效果。

不過在style裡面藏稍微複雜的程式,ie會提示有錯誤。看起來在程式裡面使用「;」(分號)會有問題,所以寫比較複雜的程式會比較困難。不過後來發現有人用一種方式實作,就是把要執行的程式碼當作字串,把字串轉成數字序列,然後用String.fromCharCode()把數字序列轉成字串,再用eval來執行。這種方式不會出現錯誤訊息,而且足以達到隱藏程式以及執行較複雜程式的功能。另外,在google找到的資料裡面看起來,還會影響BBCode等語法。如果網站程式有設計filter來過濾post/get傳進來的資料,最好再加上攔截xss:expression的關鍵字吧。

還好我用的是Firefox....

 


[修改] 2007-6-27

補充一下,我發現其實只要在style裡面用「屬性: expression(程式碼)」的方式就有效,例如

<div style="border: expression(程式碼)">
或是
div {border: expression(程式碼)}

 


[修改] 2007-6-27

嗯嗯,剛剛終於發現了,原來expression()是IE才支援的功能(IE5以上),讓css的屬性值可以用expression language計算(類似JSP、JSTL、JSF吧?)。之前沒發現.....汗

不過即使是這樣,問題還是沒解決.....應該要限制在expression中可以執行的script比較好。因為理論上expression只需要簡單計算的功能就可以了,但是在IE的實作中,它可是擁有完整的javascript功能。而且在expression中間使用alert()來測試,可以發現IE根本就會不定期執行內嵌在style中的script(我用的是IE7),可能在所有網頁事件觸發時都會作用(我發現只要滑鼠移過就...),這樣實在可以為惡意的程式開一道後門耶。

我在前幾篇文章裡面試做decorator pattern(用javascript試作decorator),但是總覺得不滿意,所以再試著弄更清楚。

幾個不清楚的地方是,我沒有嚴格地按照GoF的方式做,因為原本只是想初步達到效果(利用constructor彈性地賦予職責),但是class的關係不太明顯,說是decorator pattern有一點勉強,所以這一次再加強一下。

大致想做到幾點要求:

  1. decorator與被裝飾的對象,繼承同一個上層類別(觀念是這樣啦,但是用javascript實作其實感覺......類別關係不那麼強,沒辦法,先求有)
  2. 同樣用constructor可以動態賦予更多裝飾效果(職責)

程式很簡單,有一個最頂層的function叫做man,youngman跟decorator直接繼承man,singer跟conductor繼承decorator。youngman的shout方法會印出"I'm a young man"訊息,使用singer及conductor時則會為youngman加上"I'm a singer too."以及"I'm a conductor too."訊息。

function man () {
	this.shout = function () {
	}
}

function youngman () {
	this._init = man;
	this._init();
	this._msg = "I'm a young man.";
	this.shout = function () {
		alert(this._msg);
	}
}
youngman.prototype = new man;

function decorator (obj) {
	this._init = man;
	this._init();
	this._obj = obj;
	this.shout = function () {
	}
}
decorator.prototype = new man;

function singer (obj) {
	this._init = decorator;
	this._init(obj);
	this._msg = " I'm a singer too.";
	this.shout = function () {
		this._obj._msg += this._msg;
		this._obj.shout();
	}
}
singer.prototype = new decorator;

function conductor (obj) {
	this._init = decorator;
	this._init(obj);
	this._msg = " I'm a conductor too.";
	this.shout = function () {
		this._obj._msg += this._msg;
		this._obj.shout();
	}
}
conductor.prototype = new decorator;

測試了一下:

var tmp1=new youngman();
tmp1.shout();
//印出"I'm a young man."
var tmp2=new singer(new youngman());
tmp2.shout();
//印出"I'm a young man. I'm a singer too."
var tmp3=new conductor(new youngman());
tmp3.shout();
//印出"I'm a young man. I'm a conductor too."
var tmp4=new conductor(new singer(new youngman()));
tmp4.shout();
//印出"I'm a young man. I'm a singer too. I'm a conductor too."

大概就是這樣了吧?希望沒大錯。

今天又看到一個有趣的想法,就是用類似的方式來做AOP,有空再來研究看看。(可以參考這篇blog文章:The Decorator Pattern for JavaScript

ECMA-262 Edition 4(Javascript 2.0)規格似乎正在緊鑼密鼓地開發中。今天偶爾翻到一個新聞,就是ECMAScript組織剛發佈了一個用standard ml開發的Reference Implementation,可以讓我們試用es4的功能。馬上就去下載了:

ECMAScript Group的網站:http://www.ecmascript-lang.org(沒有打直接下載的連結,因為還有一些操作跟說明:))

接下來就來試試看。

仔細看了一下說明,ES4目前的RI版本似乎得在Cygwin下面執行,所以就先安裝Cygwin。(下載:http://www.cygwin.com/,網頁中有最新版本安裝程式(setup.exe)的連結)

安裝cygwin不難,但是因為要從網路上抓安裝的package,所以最好選擇本地的mirror site,我看到有ftp.ntu.edu.tw,就用這個選項了。其他大致上只要用預設值就可以。但是ES4需要sml/nj來執行,所以必須另外手動安裝sml/nj。預防萬一,所以在安裝cygwin的時候,選擇packages->Devel底下把可能會用到的都選了,應該至少要有make,但是我還選了autoconf、automake、binutils、bison、byacc、flex、gcc、gccmingw、libtool等等,另外為了可以在文字模式下編輯,還安裝了Editors->vim。

安裝sml/nj是比較麻煩的事情,如果常在linux手動安裝東西當然就不算難啦....。首先當然要下載sml/nj,EMCAScript的網站有一個連結:http://www.smlnj.org/dist/working/110.64/index.html,大致上按照他的說明就可以順利安裝。接下來就按照sml/nj網站上的說明來弄了,我的步驟如下:

  1. 打開cygwin,出現console
  2. 我想把東西安裝到/usr/share/smlnj目錄下,所以先 "cd /usr/share" 然後 "mkdir smlnj" 接著 "cd smlnj"
  3. 一些必要的檔案,包括config.tgz、runtime.tgz我放在c:/Downloads目錄下
  4. 在cygwin的/usr/share/smlnj目錄中,用 "tar zxf /cygdrive/c/Downloads/boot.x86-unix.tgz" "tar zxf /cygdrive/c/Downloads/config.tgz" "tar zxf /cygdrive/c/Downloads/runtime.tgz base" 等把必要的package解到目錄中
  5. "export SMLNJ_CYGWIN_RUNTIME=1" 設定好環境變數,讓安裝程式知道用的是cygwin
  6. 如果要改安裝的項目,用 "vi config/targets" ,但是我沒有改.....所以需要另外下載trace-debug-profile.tgz、smlnj-lib.tgz、MLRISC.tgz、ml-yacc.tgz、ml-lpt.tgz、ml-lex、cml.tgz等幾個檔案,同樣用 "tar zxf filename" 的方法解開到/usr/share/smlnj目錄中
  7. 接著用 "config/install.sh" 來安裝
  8. 安裝完畢後,要建立連結,方便使用: "cd /usr/bin" "ln -s /usr/share/smlnj/bin/sml"
  9. 然後解開下載的ECMA-262 Edition 4 RI,例如要解開到/home/fillano目錄中,則 "tar zxf /cygdrive/c/Downloads/es4-pre-release.M0.cygwin-x86.tar.gz /home/fillano"
  10. 進到es4的目錄: "cd /home/fillano/es4" ,然後執行: "./es4" 就可以了

安裝完畢!!接著來試用一下。這裡是用直譯的方式,我又不太熟悉操作......所以先把一些敘述放到同一行來測試一下:

測試的code:
interface Greetings {
	function hello();
	function goodmorning();
}
class Greeter implements Greetings {
	public function hello() {
		print("hello, world")
	};
	public function goodmorning() {
		print("goodmorning, world")
	};
}
var greeter : Greetings = new Greeter()
greeter.hello()
greeter.goodmorning()
稍微修改一下,讓他在直譯器裡面可以執行:
interface Greetings {    function hello();    function goodmorning();}
class Greeter implements Greetings {    public function hello() {  print("hello, world")  };    public function goodmorning() {  print("goodmorning, world")   };}
var greeter : Greetings = new Greeter()
greeter.hello()
greeter.goodmorning()

然後拿到es4裡面執行:
ES4 測試程式執行畫面

(這個ES4 RI直譯器有一些內建命令可以用,我還不太清楚他的作用,不過用 ":help" 可以看一下哪一些命令可以使用)

JSCookMenu是一個相當容易用的JavaScript Base選單程式,可以很簡單地用陣列來製作選單,支援不同的佈景主題,作者也持續有在更新(目前最新版本到2.0.3)。

作者的網頁裡面有詳細的說明:http://jscook.yuanheng.org/JSCookMenu/

最近使用上卻遇到一點小問題,就是選單有時會失靈....花了一些時間嘗試,發現了一點小問題,問題發生在防毒軟體、瀏覽器toolbar上面的攔阻彈出視窗功能。

JSCookMenu用cmItemMouseUp函數來處理mouseup動作,click之後的處理也是用到這一個函數。這個函數中處理link的方法大致如下:

	if (link != null)
	{
		_cmClicked = false;
		window.open (link, target);
	}

Symantec Internet Security 2006(我的筆記本預裝的防毒軟體)會在網頁中自動加上一段JavaScript,用來攔截彈出視窗:

function SymError()
{
  return true;
}

window.onerror = SymError;

var SymRealWinOpen = window.open;

function SymWinOpen(url, name, attributes)
{
  return (new Object());
}

window.open = SymWinOpen;

結果就是window.open被攔截了,當然無法使用。先嘗試用幾個方法來對付,在firefox下global scope的變數會放在window物件中,所以可以用這樣的方法來試探究竟window.open被改到哪裡去了(不直接使用SymRealWinOpen變數,是因為希望對所有用這個方法封鎖彈出視窗的方式都可用),然後再把他改回來:

	for (obj in window) {
		str1 = "";
		str1 += obj;
		str2 = "";
		str2 += window[str1];
		if (str2.indexOf("function open()")>-1) {
//懶得用re,先用indexOf比較快
			window.open = window[str1];
		}
	}

很可惜,這個方法在ie7裡面沒有作用....似乎ie7的javascript引擎並沒有把global scope的變數放在window物件中....(沒仔細測試,但是至少用firefox可行的方法出不來)

後來試用了一下google toolbar,發現他根本不依靠javascrpt來攔阻彈出視窗,所以即使用window.open=SymRealWinOpen也沒用,照樣攔截。這樣JSCookMenu就會有問題了....

袁衡的JSCookMenu支援可以在click選單時依照target指定的目標來開啟連結,通常的應用是指定"_self"來在原來的網頁視窗開啟,指定"_blank"開啟新視窗,指定其他的目標(frame、iframe等等)則會在相應的目標開啟。光用window.location.href=link沒辦法支援這麼多應用。

後來想到一個方法理論上可以兩全其美(還是有缺點,後面會提到),就是form。因為form一樣有target屬性,可以利用這個方法來彈出新視窗也不會被攔阻。我稍微改了一下JSCookMenu的程式:

	if (link != null)
	{
		_cmClicked = false;
		tmp = document.createElement("form");
//動態產生form物件
		tmp.target = target;
		tmp.method = "post";
//用post方法網址尾巴才不會出現?
		tmp.action = link;
		document.body.appendChild(tmp);
		tmp.submit();
//送出post
		document.body.removeChild(tmp);
		delete tmp;
//form不用了,把他移除
	}

試了一下,果然可以動了。不過因為是用post,連結的網頁必須避免錯誤處理這個post,另外有一些網站還是可能用referer來攔阻不恰當來源的post,也有可能有問題。我在寫的東西全部在自己網頁裡,就不需要擔心了:D

另外需要考慮的是動態改動node tree其實是會給瀏覽器帶來一些額外的負擔,比較起來用window.location.href=link速度的確快了一些。不過因為原本的目的就是要跳到不同的網頁或是開啟新視窗,所以這樣的延遲就比較不要緊了。

其實並沒有測試過所有的彈出視窗攔阻程式,所以不一定都可以通過啦。我還有注意到,用滑鼠右鍵點JSCookMenu選單時,會被google toolbar攔截.....不過用左鍵點選沒問題。感覺google toolbar攔阻的policy似乎必須用滑鼠左鑑,並且用form來開新視窗才能通過的樣子。