mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-09-13 22:21:29 +00:00
Merge branch 'master' of git@github.com:280north/cappuccino
This commit is contained in:
@@ -861,7 +861,7 @@ var CTRL_KEY_CODE = 17;
|
||||
@end
|
||||
|
||||
var CLICK_SPACE_DELTA = 5.0,
|
||||
CLICK_TIME_DELTA = document.addEventListener ? 350.0 : 1000.0;
|
||||
CLICK_TIME_DELTA = (typeof document != "undefined" && document.addEventListener) ? 350.0 : 1000.0;
|
||||
|
||||
var CPDOMEventGetClickCount = function(aComparisonEvent, aTimestamp, aLocation)
|
||||
{
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,767 @@
|
||||
/*
|
||||
* Pure JavaScript Browser Environment
|
||||
* By John Resig <http://ejohn.org/>
|
||||
* Copyright 2008 John Resig, under the MIT License
|
||||
*/
|
||||
|
||||
// The window Object
|
||||
var window = this;
|
||||
|
||||
(function(){
|
||||
|
||||
// Browser Navigator
|
||||
|
||||
window.navigator = {
|
||||
get userAgent(){
|
||||
return "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3";
|
||||
}
|
||||
};
|
||||
|
||||
var curLocation = (new java.io.File("./")).toURL();
|
||||
|
||||
window.__defineSetter__("location", function(url){
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url);
|
||||
xhr.onreadystatechange = function(){
|
||||
curLocation = new java.net.URL( curLocation, url );
|
||||
window.document = xhr.responseXML;
|
||||
|
||||
var event = document.createEvent();
|
||||
event.initEvent("load");
|
||||
window.dispatchEvent( event );
|
||||
};
|
||||
xhr.send();
|
||||
});
|
||||
|
||||
window.__defineGetter__("location", function(url){
|
||||
return {
|
||||
get protocol(){
|
||||
return curLocation.getProtocol() + ":";
|
||||
},
|
||||
get href(){
|
||||
return curLocation.toString();
|
||||
},
|
||||
toString: function(){
|
||||
return this.href;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Timers
|
||||
|
||||
var timers = [];
|
||||
|
||||
window.setTimeout = function(fn, time){
|
||||
var num;
|
||||
return num = setInterval(function(){
|
||||
fn();
|
||||
clearInterval(num);
|
||||
}, time);
|
||||
};
|
||||
|
||||
window.setInterval = function(fn, time){
|
||||
var num = timers.length;
|
||||
|
||||
timers[num] = new java.lang.Thread(new java.lang.Runnable({
|
||||
run: function(){
|
||||
while (true){
|
||||
java.lang.Thread.currentThread().sleep(time);
|
||||
fn();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
timers[num].start();
|
||||
|
||||
return num;
|
||||
};
|
||||
|
||||
window.clearInterval = function(num){
|
||||
if ( timers[num] ) {
|
||||
timers[num].stop();
|
||||
delete timers[num];
|
||||
}
|
||||
};
|
||||
|
||||
// Window Events
|
||||
|
||||
var events = [{}];
|
||||
|
||||
window.addEventListener = function(type, fn){
|
||||
if ( !this.uuid || this == window ) {
|
||||
this.uuid = events.length;
|
||||
events[this.uuid] = {};
|
||||
}
|
||||
|
||||
if ( !events[this.uuid][type] )
|
||||
events[this.uuid][type] = [];
|
||||
|
||||
if ( events[this.uuid][type].indexOf( fn ) < 0 )
|
||||
events[this.uuid][type].push( fn );
|
||||
};
|
||||
|
||||
window.removeEventListener = function(type, fn){
|
||||
if ( !this.uuid || this == window ) {
|
||||
this.uuid = events.length;
|
||||
events[this.uuid] = {};
|
||||
}
|
||||
|
||||
if ( !events[this.uuid][type] )
|
||||
events[this.uuid][type] = [];
|
||||
|
||||
events[this.uuid][type] =
|
||||
events[this.uuid][type].filter(function(f){
|
||||
return f != fn;
|
||||
});
|
||||
};
|
||||
|
||||
window.dispatchEvent = function(event){
|
||||
if ( event.type ) {
|
||||
if ( this.uuid && events[this.uuid][event.type] ) {
|
||||
var self = this;
|
||||
|
||||
events[this.uuid][event.type].forEach(function(fn){
|
||||
fn.call( self, event );
|
||||
});
|
||||
}
|
||||
|
||||
if ( this["on" + event.type] )
|
||||
this["on" + event.type].call( self, event );
|
||||
}
|
||||
};
|
||||
|
||||
// DOM Document
|
||||
|
||||
window.DOMDocument = function(file){
|
||||
this._file = file;
|
||||
this._dom = Packages.javax.xml.parsers.
|
||||
DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder().parse(file);
|
||||
|
||||
if ( !obj_nodes.containsKey( this._dom ) )
|
||||
obj_nodes.put( this._dom, this );
|
||||
};
|
||||
|
||||
DOMDocument.prototype = {
|
||||
get nodeType(){
|
||||
return 9;
|
||||
},
|
||||
createTextNode: function(text){
|
||||
return makeNode( this._dom.createTextNode(
|
||||
text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")) );
|
||||
},
|
||||
createElement: function(name){
|
||||
return makeNode( this._dom.createElement(name.toLowerCase()) );
|
||||
},
|
||||
getElementsByTagName: function(name){
|
||||
return new DOMNodeList( this._dom.getElementsByTagName(
|
||||
name.toLowerCase()) );
|
||||
},
|
||||
getElementsByName: function(name){
|
||||
var elems = this._dom.getElementsByTagName("*"), ret = [];
|
||||
ret.item = function(i){ return this[i]; };
|
||||
ret.getLength = function(){ return this.length; };
|
||||
|
||||
for ( var i = 0; i < elems.length; i++ ) {
|
||||
var elem = elems.item(i);
|
||||
if ( elem.getAttribute("name") == name )
|
||||
ret.push( elem );
|
||||
}
|
||||
|
||||
return new DOMNodeList( ret );
|
||||
},
|
||||
getElementById: function(id){
|
||||
var elems = this._dom.getElementsByTagName("*");
|
||||
|
||||
for ( var i = 0; i < elems.length; i++ ) {
|
||||
var elem = elems.item(i);
|
||||
if ( elem.getAttribute("id") == id )
|
||||
return makeNode(elem);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
get body(){
|
||||
return this.getElementsByTagName("body")[0];
|
||||
},
|
||||
get documentElement(){
|
||||
return makeNode( this._dom.getDocumentElement() );
|
||||
},
|
||||
get ownerDocument(){
|
||||
return null;
|
||||
},
|
||||
addEventListener: window.addEventListener,
|
||||
removeEventListener: window.removeEventListener,
|
||||
dispatchEvent: window.dispatchEvent,
|
||||
get nodeName() {
|
||||
return "#document";
|
||||
},
|
||||
importNode: function(node, deep){
|
||||
return makeNode( this._dom.importNode(node._dom, deep) );
|
||||
},
|
||||
toString: function(){
|
||||
return "Document" + (typeof this._file == "string" ?
|
||||
": " + this._file : "");
|
||||
},
|
||||
get innerHTML(){
|
||||
return this.documentElement.outerHTML;
|
||||
},
|
||||
|
||||
get defaultView(){
|
||||
return {
|
||||
getComputedStyle: function(elem){
|
||||
return {
|
||||
getPropertyValue: function(prop){
|
||||
prop = prop.replace(/\-(\w)/g,function(m,c){
|
||||
return c.toUpperCase();
|
||||
});
|
||||
var val = elem.style[prop];
|
||||
|
||||
if ( prop == "opacity" && val == "" )
|
||||
val = "1";
|
||||
|
||||
return val;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
createEvent: function(){
|
||||
return {
|
||||
type: "",
|
||||
initEvent: function(type){
|
||||
this.type = type;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
function getDocument(node){
|
||||
return obj_nodes.get(node);
|
||||
}
|
||||
|
||||
// DOM NodeList
|
||||
|
||||
window.DOMNodeList = function(list){
|
||||
this._dom = list;
|
||||
this.length = list.getLength();
|
||||
|
||||
for ( var i = 0; i < this.length; i++ ) {
|
||||
var node = list.item(i);
|
||||
this[i] = makeNode( node );
|
||||
}
|
||||
};
|
||||
|
||||
DOMNodeList.prototype = {
|
||||
toString: function(){
|
||||
return "[ " +
|
||||
Array.prototype.join.call( this, ", " ) + " ]";
|
||||
},
|
||||
get outerHTML(){
|
||||
return Array.prototype.map.call(
|
||||
this, function(node){return node.outerHTML;}).join('');
|
||||
}
|
||||
};
|
||||
|
||||
// DOM Node
|
||||
|
||||
window.DOMNode = function(node){
|
||||
this._dom = node;
|
||||
};
|
||||
|
||||
DOMNode.prototype = {
|
||||
get nodeType(){
|
||||
return this._dom.getNodeType();
|
||||
},
|
||||
get nodeValue(){
|
||||
return this._dom.getNodeValue();
|
||||
},
|
||||
get nodeName() {
|
||||
return this._dom.getNodeName();
|
||||
},
|
||||
get childNodes(){
|
||||
return new DOMNodeList( this._dom.getChildNodes() );
|
||||
},
|
||||
cloneNode: function(deep){
|
||||
return makeNode( this._dom.cloneNode(deep) );
|
||||
},
|
||||
get ownerDocument(){
|
||||
return getDocument( this._dom.ownerDocument );
|
||||
},
|
||||
get documentElement(){
|
||||
return makeNode( this._dom.documentElement );
|
||||
},
|
||||
get parentNode() {
|
||||
return makeNode( this._dom.getParentNode() );
|
||||
},
|
||||
get nextSibling() {
|
||||
return makeNode( this._dom.getNextSibling() );
|
||||
},
|
||||
get previousSibling() {
|
||||
return makeNode( this._dom.getPreviousSibling() );
|
||||
},
|
||||
toString: function(){
|
||||
return '"' + this.nodeValue + '"';
|
||||
},
|
||||
get outerHTML(){
|
||||
return this.nodeValue;
|
||||
}
|
||||
};
|
||||
|
||||
window.DOMComment = function(node){
|
||||
this._dom = node;
|
||||
};
|
||||
|
||||
DOMComment.prototype = extend(new DOMNode(), {
|
||||
get nodeType(){
|
||||
return 8;
|
||||
},
|
||||
get outerHTML(){
|
||||
return "<!--" + this.nodeValue + "-->";
|
||||
}
|
||||
});
|
||||
|
||||
// DOM Element
|
||||
|
||||
window.DOMElement = function(elem){
|
||||
this._dom = elem;
|
||||
this.style = {
|
||||
get opacity(){ return this._opacity; },
|
||||
set opacity(val){ this._opacity = val + ""; }
|
||||
};
|
||||
|
||||
// Load CSS info
|
||||
var styles = (this.getAttribute("style") || "").split(/\s*;\s*/);
|
||||
|
||||
for ( var i = 0; i < styles.length; i++ ) {
|
||||
var style = styles[i].split(/\s*:\s*/);
|
||||
if ( style.length == 2 )
|
||||
this.style[ style[0] ] = style[1];
|
||||
}
|
||||
|
||||
if ( this.nodeName == "FORM" ) {
|
||||
this.__defineGetter__("elements", function(){
|
||||
return this.getElementsByTagName("*");
|
||||
});
|
||||
|
||||
this.__defineGetter__("length", function(){
|
||||
var elems = this.elements;
|
||||
for ( var i = 0; i < elems.length; i++ ) {
|
||||
this[i] = elems[i];
|
||||
}
|
||||
|
||||
return elems.length;
|
||||
});
|
||||
}
|
||||
|
||||
if ( this.nodeName == "SELECT" ) {
|
||||
this.__defineGetter__("options", function(){
|
||||
return this.getElementsByTagName("option");
|
||||
});
|
||||
}
|
||||
|
||||
this.defaultValue = this.value;
|
||||
};
|
||||
|
||||
DOMElement.prototype = extend( new DOMNode(), {
|
||||
get nodeName(){
|
||||
return this.tagName;
|
||||
},
|
||||
get tagName(){
|
||||
return this._dom.getTagName().toUpperCase();
|
||||
},
|
||||
toString: function(){
|
||||
return "<" + this.tagName + (this.id ? "#" + this.id : "" ) + ">";
|
||||
},
|
||||
get outerHTML(){
|
||||
var ret = "<" + this.tagName, attr = this.attributes;
|
||||
|
||||
for ( var i in attr )
|
||||
ret += " " + i + "='" + attr[i] + "'";
|
||||
|
||||
if ( this.childNodes.length || this.nodeName == "SCRIPT" )
|
||||
ret += ">" + this.childNodes.outerHTML +
|
||||
"</" + this.tagName + ">";
|
||||
else
|
||||
ret += "/>";
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
get attributes(){
|
||||
var attr = {}, attrs = this._dom.getAttributes();
|
||||
|
||||
for ( var i = 0; i < attrs.getLength(); i++ )
|
||||
attr[ attrs.item(i).nodeName ] = attrs.item(i).nodeValue;
|
||||
|
||||
return attr;
|
||||
},
|
||||
|
||||
get innerHTML(){
|
||||
return this.childNodes.outerHTML;
|
||||
},
|
||||
set innerHTML(html){
|
||||
html = html.replace(/<\/?([A-Z]+)/g, function(m){
|
||||
return m.toLowerCase();
|
||||
}).replace(/ /g, " ");
|
||||
|
||||
var nodes = this.ownerDocument.importNode(
|
||||
new DOMDocument( new java.io.ByteArrayInputStream(
|
||||
(new java.lang.String("<wrap>" + html + "</wrap>"))
|
||||
.getBytes("UTF8"))).documentElement, true).childNodes;
|
||||
|
||||
while (this.firstChild)
|
||||
this.removeChild( this.firstChild );
|
||||
|
||||
for ( var i = 0; i < nodes.length; i++ )
|
||||
this.appendChild( nodes[i] );
|
||||
},
|
||||
|
||||
get textContent(){
|
||||
return nav(this.childNodes);
|
||||
|
||||
function nav(nodes){
|
||||
var str = "";
|
||||
for ( var i = 0; i < nodes.length; i++ )
|
||||
if ( nodes[i].nodeType == 3 )
|
||||
str += nodes[i].nodeValue;
|
||||
else if ( nodes[i].nodeType == 1 )
|
||||
str += nav(nodes[i].childNodes);
|
||||
return str;
|
||||
}
|
||||
},
|
||||
set textContent(text){
|
||||
while (this.firstChild)
|
||||
this.removeChild( this.firstChild );
|
||||
this.appendChild( this.ownerDocument.createTextNode(text));
|
||||
},
|
||||
|
||||
style: {},
|
||||
clientHeight: 0,
|
||||
clientWidth: 0,
|
||||
offsetHeight: 0,
|
||||
offsetWidth: 0,
|
||||
|
||||
get disabled() {
|
||||
var val = this.getAttribute("disabled");
|
||||
return val != "false" && !!val;
|
||||
},
|
||||
set disabled(val) { return this.setAttribute("disabled",val); },
|
||||
|
||||
get checked() {
|
||||
var val = this.getAttribute("checked");
|
||||
return val != "false" && !!val;
|
||||
},
|
||||
set checked(val) { return this.setAttribute("checked",val); },
|
||||
|
||||
get selected() {
|
||||
if ( !this._selectDone ) {
|
||||
this._selectDone = true;
|
||||
|
||||
if ( this.nodeName == "OPTION" && !this.parentNode.getAttribute("multiple") ) {
|
||||
var opt = this.parentNode.getElementsByTagName("option");
|
||||
|
||||
if ( this == opt[0] ) {
|
||||
var select = true;
|
||||
|
||||
for ( var i = 1; i < opt.length; i++ )
|
||||
if ( opt[i].selected ) {
|
||||
select = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( select )
|
||||
this.selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var val = this.getAttribute("selected");
|
||||
return val != "false" && !!val;
|
||||
},
|
||||
set selected(val) { return this.setAttribute("selected",val); },
|
||||
|
||||
get className() { return this.getAttribute("class") || ""; },
|
||||
set className(val) {
|
||||
return this.setAttribute("class",
|
||||
val.replace(/(^\s*|\s*$)/g,""));
|
||||
},
|
||||
|
||||
get type() { return this.getAttribute("type") || ""; },
|
||||
set type(val) { return this.setAttribute("type",val); },
|
||||
|
||||
get defaultValue() { return this.getAttribute("defaultValue") || ""; },
|
||||
set defaultValue(val) { return this.setAttribute("defaultValue",val); },
|
||||
|
||||
get value() { return this.getAttribute("value") || ""; },
|
||||
set value(val) { return this.setAttribute("value",val); },
|
||||
|
||||
get src() { return this.getAttribute("src") || ""; },
|
||||
set src(val) { return this.setAttribute("src",val); },
|
||||
|
||||
get id() { return this.getAttribute("id") || ""; },
|
||||
set id(val) { return this.setAttribute("id",val); },
|
||||
|
||||
getAttribute: function(name){
|
||||
return this._dom.hasAttribute(name) ?
|
||||
new String( this._dom.getAttribute(name) ) :
|
||||
null;
|
||||
},
|
||||
setAttribute: function(name,value){
|
||||
this._dom.setAttribute(name,value);
|
||||
},
|
||||
removeAttribute: function(name){
|
||||
this._dom.removeAttribute(name);
|
||||
},
|
||||
|
||||
get childNodes(){
|
||||
return new DOMNodeList( this._dom.getChildNodes() );
|
||||
},
|
||||
get firstChild(){
|
||||
return makeNode( this._dom.getFirstChild() );
|
||||
},
|
||||
get lastChild(){
|
||||
return makeNode( this._dom.getLastChild() );
|
||||
},
|
||||
appendChild: function(node){
|
||||
this._dom.appendChild( node._dom );
|
||||
},
|
||||
insertBefore: function(node,before){
|
||||
this._dom.insertBefore( node._dom, before ? before._dom : before );
|
||||
|
||||
execScripts( node );
|
||||
|
||||
function execScripts( node ) {
|
||||
if ( node.nodeName == "SCRIPT" ) {
|
||||
if ( !node.getAttribute("src") ) {
|
||||
eval.call( window, node.textContent );
|
||||
}
|
||||
} else {
|
||||
var scripts = node.getElementsByTagName("script");
|
||||
for ( var i = 0; i < scripts.length; i++ ) {
|
||||
execScripts( node );
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
removeChild: function(node){
|
||||
this._dom.removeChild( node._dom );
|
||||
},
|
||||
|
||||
getElementsByTagName: DOMDocument.prototype.getElementsByTagName,
|
||||
|
||||
addEventListener: window.addEventListener,
|
||||
removeEventListener: window.removeEventListener,
|
||||
dispatchEvent: window.dispatchEvent,
|
||||
|
||||
click: function(){
|
||||
var event = document.createEvent();
|
||||
event.initEvent("click");
|
||||
this.dispatchEvent(event);
|
||||
},
|
||||
submit: function(){
|
||||
var event = document.createEvent();
|
||||
event.initEvent("submit");
|
||||
this.dispatchEvent(event);
|
||||
},
|
||||
focus: function(){
|
||||
var event = document.createEvent();
|
||||
event.initEvent("focus");
|
||||
this.dispatchEvent(event);
|
||||
},
|
||||
blur: function(){
|
||||
var event = document.createEvent();
|
||||
event.initEvent("blur");
|
||||
this.dispatchEvent(event);
|
||||
},
|
||||
get contentWindow(){
|
||||
return this.nodeName == "IFRAME" ? {
|
||||
document: this.contentDocument
|
||||
} : null;
|
||||
},
|
||||
get contentDocument(){
|
||||
if ( this.nodeName == "IFRAME" ) {
|
||||
if ( !this._doc )
|
||||
this._doc = new DOMDocument(
|
||||
new java.io.ByteArrayInputStream((new java.lang.String(
|
||||
"<html><head><title></title></head><body></body></html>"))
|
||||
.getBytes("UTF8")));
|
||||
return this._doc;
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// Helper method for extending one object with another
|
||||
|
||||
function extend(a,b) {
|
||||
for ( var i in b ) {
|
||||
var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i);
|
||||
|
||||
if ( g || s ) {
|
||||
if ( g )
|
||||
a.__defineGetter__(i, g);
|
||||
if ( s )
|
||||
a.__defineSetter__(i, s);
|
||||
} else
|
||||
a[i] = b[i];
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
// Helper method for generating the right
|
||||
// DOM objects based upon the type
|
||||
|
||||
var obj_nodes = new java.util.HashMap();
|
||||
|
||||
function makeNode(node){
|
||||
if ( node ) {
|
||||
if ( !obj_nodes.containsKey( node ) )
|
||||
obj_nodes.put( node, node.getNodeType() == 1?
|
||||
new DOMElement( node ) :
|
||||
node.getNodeType() == 8 ?
|
||||
new DOMComment( node ) :
|
||||
new DOMNode( node ) );
|
||||
|
||||
return obj_nodes.get(node);
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
// XMLHttpRequest
|
||||
// Originally implemented by Yehuda Katz
|
||||
|
||||
window.XMLHttpRequest = function(){
|
||||
this.headers = {};
|
||||
this.responseHeaders = {};
|
||||
};
|
||||
|
||||
XMLHttpRequest.prototype = {
|
||||
open: function(method, url, async, user, password){
|
||||
this.readyState = 1;
|
||||
if (async)
|
||||
this.async = true;
|
||||
this.method = method || "GET";
|
||||
this.url = url;
|
||||
this.onreadystatechange();
|
||||
},
|
||||
setRequestHeader: function(header, value){
|
||||
this.headers[header] = value;
|
||||
},
|
||||
getResponseHeader: function(header){ },
|
||||
send: function(data){
|
||||
var self = this;
|
||||
|
||||
function makeRequest(){
|
||||
var url = new java.net.URL(curLocation, self.url);
|
||||
|
||||
if ( url.getProtocol() == "file" ) {
|
||||
if ( self.method == "PUT" ) {
|
||||
var out = new java.io.FileWriter(
|
||||
new java.io.File( new java.net.URI( url.toString() ) ) ),
|
||||
text = new java.lang.String( data || "" );
|
||||
|
||||
out.write( text, 0, text.length() );
|
||||
out.flush();
|
||||
out.close();
|
||||
} else if ( self.method == "DELETE" ) {
|
||||
var file = new java.io.File( new java.net.URI( url.toString() ) );
|
||||
file["delete"]();
|
||||
} else {
|
||||
var connection = url.openConnection();
|
||||
connection.connect();
|
||||
handleResponse();
|
||||
}
|
||||
} else {
|
||||
var connection = url.openConnection();
|
||||
|
||||
connection.setRequestMethod( self.method );
|
||||
|
||||
// Add headers to Java connection
|
||||
for (var header in self.headers)
|
||||
connection.addRequestProperty(header, self.headers[header]);
|
||||
|
||||
connection.connect();
|
||||
|
||||
// Stick the response headers into responseHeaders
|
||||
for (var i = 0; ; i++) {
|
||||
var headerName = connection.getHeaderFieldKey(i);
|
||||
var headerValue = connection.getHeaderField(i);
|
||||
if (!headerName && !headerValue) break;
|
||||
if (headerName)
|
||||
self.responseHeaders[headerName] = headerValue;
|
||||
}
|
||||
|
||||
handleResponse();
|
||||
}
|
||||
|
||||
function handleResponse(){
|
||||
self.readyState = 4;
|
||||
self.status = parseInt(connection.responseCode) || undefined;
|
||||
self.statusText = connection.responseMessage || "";
|
||||
|
||||
var stream = new java.io.InputStreamReader(connection.getInputStream()),
|
||||
buffer = new java.io.BufferedReader(stream), line;
|
||||
|
||||
while ((line = buffer.readLine()) != null)
|
||||
self.responseText += line;
|
||||
|
||||
self.responseXML = null;
|
||||
|
||||
if ( self.responseText.match(/^\s*</) ) {
|
||||
try {
|
||||
self.responseXML = new DOMDocument(
|
||||
new java.io.ByteArrayInputStream(
|
||||
(new java.lang.String(
|
||||
self.responseText)).getBytes("UTF8")));
|
||||
} catch(e) {}
|
||||
}
|
||||
}
|
||||
|
||||
self.onreadystatechange();
|
||||
}
|
||||
|
||||
if (this.async)
|
||||
(new java.lang.Thread(new java.lang.Runnable({
|
||||
run: makeRequest
|
||||
}))).start();
|
||||
else
|
||||
makeRequest();
|
||||
},
|
||||
abort: function(){},
|
||||
onreadystatechange: function(){},
|
||||
getResponseHeader: function(header){
|
||||
if (this.readyState < 3)
|
||||
throw new Error("INVALID_STATE_ERR");
|
||||
else {
|
||||
var returnedHeaders = [];
|
||||
for (var rHeader in this.responseHeaders) {
|
||||
if (rHeader.match(new Regexp(header, "i")))
|
||||
returnedHeaders.push(this.responseHeaders[rHeader]);
|
||||
}
|
||||
|
||||
if (returnedHeaders.length)
|
||||
return returnedHeaders.join(", ");
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
getAllResponseHeaders: function(header){
|
||||
if (this.readyState < 3)
|
||||
throw new Error("INVALID_STATE_ERR");
|
||||
else {
|
||||
var returnedHeaders = [];
|
||||
|
||||
for (var header in this.responseHeaders)
|
||||
returnedHeaders.push( header + ": " + this.responseHeaders[header] );
|
||||
|
||||
return returnedHeaders.join("\r\n");
|
||||
}
|
||||
},
|
||||
async: true,
|
||||
readyState: 0,
|
||||
responseText: "",
|
||||
status: 0
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,340 @@
|
||||
var objjPath = OBJJ_LIB+'/Frameworks/Objective-J/Objective-J.js',
|
||||
bridgePath = OBJJ_LIB+'/bridge.js',
|
||||
envPath = "/Users/tlrobinson/280North/git/cappuccino/Tools/press/env.js";
|
||||
|
||||
/*
|
||||
param context includes
|
||||
scope: a global variable containing objj_files hash
|
||||
processedFiles: hash containing file paths which have already been analyzed
|
||||
dependencies: hash mapping from paths to an array of global variables defined by that file
|
||||
[importCallback]: callback function that is called for each imported file (takes importing file path, and imported file path parameters)
|
||||
[referencedCallback]: callback function that is called for each referenced file (takes referencing file path, referenced file path parameters, and list of tokens)
|
||||
[importedFiles]: hash that will contain a mapping of file names to a hash of imported files
|
||||
[referencedFiles]: hash that will contain a mapping of file names to a hash of referenced files (which contains a hash of tokens referenced)
|
||||
|
||||
param file is an objj_file object containing path, fragments, content, bundle, etc
|
||||
*/
|
||||
function traverseDependencies(context, file)
|
||||
{
|
||||
if (context.processedFiles[file.path])
|
||||
return;
|
||||
context.processedFiles[file.path] = true;
|
||||
|
||||
var ignoreImports = false;
|
||||
if (context.ignoreAllImports)
|
||||
{
|
||||
CPLog.warn("Ignoring all import fragments. ("+file.path+")");
|
||||
ignoreImports = true;
|
||||
}
|
||||
else if (context.ignoreFrameworkImports)
|
||||
{
|
||||
var matches = file.path.match(/([^\/]+)\/([^\/]+)\.j$/); // Matches "ZZZ/ZZZ.j" (e.x. AppKit/AppKit.j and Foundation/Foundation.j)
|
||||
if (matches && matches[1] === matches[2])
|
||||
{
|
||||
CPLog.warn("Framework import file! Ignoring all import fragments. ("+file.path+")");
|
||||
ignoreImports = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!file.fragments)
|
||||
{
|
||||
if (file.included)
|
||||
CPLog.warn(file.path + " is included but missing fragments");
|
||||
else
|
||||
CPLog.info("Preprocessing " + file.path);
|
||||
|
||||
file.fragments = objj_preprocess(file.contents, file.bundle, file);
|
||||
}
|
||||
|
||||
var referencedFiles = {},
|
||||
importedFiles = {};
|
||||
|
||||
CPLog.trace("Processing " + file.path + " fragments ("+file.fragments.length+")");
|
||||
for (var i = 0; i < file.fragments.length; i++)
|
||||
{
|
||||
var fragment = file.fragments[i];
|
||||
|
||||
if (fragment.type & FRAGMENT_CODE)
|
||||
{
|
||||
var lexer = new objj_lexer(fragment.info, NULL);
|
||||
|
||||
var token;
|
||||
while (token = lexer.skip_whitespace())
|
||||
{
|
||||
if (context.dependencies[token])
|
||||
{
|
||||
var files = context.dependencies[token]
|
||||
for (var j = 0; j < files.length; j++)
|
||||
{
|
||||
// don't record references to self
|
||||
if (files[j] != file.path)
|
||||
{
|
||||
if (!referencedFiles[files[j]])
|
||||
referencedFiles[files[j]] = {};
|
||||
|
||||
referencedFiles[files[j]][token] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (fragment.type & FRAGMENT_IMPORT)
|
||||
{
|
||||
if (ignoreImports)
|
||||
{
|
||||
fragment.conditionallyIgnore = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var importedFile = findImportInObjjFiles(context.scope, fragment);
|
||||
if (importedFile)
|
||||
{
|
||||
// should never import self, but just in case?
|
||||
if (importedFile != file.path)
|
||||
importedFiles[importedFile] = true;
|
||||
else
|
||||
CPLog.error("Ignoring self import (why are you importing yourself!?): " + file.path);
|
||||
}
|
||||
else
|
||||
CPLog.error("Couldn't find file for import " + fragment.info + "("+fragment.type+")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check each imported file
|
||||
for (var importedFile in importedFiles)
|
||||
{
|
||||
if (importedFile != file.path)
|
||||
{
|
||||
if (context.importCallback)
|
||||
context.importCallback(file.path, importedFile);
|
||||
|
||||
if (context.scope.objj_files[importedFile])
|
||||
traverseDependencies(context, context.scope.objj_files[importedFile]);
|
||||
else
|
||||
CPLog.error("Missing imported file: " + importedFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.importedFiles)
|
||||
context.importedFiles[file.path] = importedFiles;
|
||||
|
||||
// check each referenced file
|
||||
for (var referencedFile in referencedFiles)
|
||||
{
|
||||
if (referencedFile != file.path)
|
||||
{
|
||||
if (context.referenceCallback)
|
||||
context.referenceCallback(file.path, referencedFile, referencedFiles[referencedFile]);
|
||||
|
||||
if (context.scope.objj_files[referencedFile])
|
||||
traverseDependencies(context, context.scope.objj_files[referencedFile]);
|
||||
else
|
||||
CPLog.error("Missing referenced file: " + referencedFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.referencedFiles)
|
||||
context.referencedFiles[file.path] = referencedFiles;
|
||||
}
|
||||
|
||||
function findImportInObjjFiles(scope, fragment)
|
||||
{
|
||||
var importPath = null;
|
||||
|
||||
if (fragment.type & FRAGMENT_LOCAL)
|
||||
{
|
||||
//CPLog.debug("Importing local file: " + fragment.info);
|
||||
|
||||
var searchPath = fragment.info;
|
||||
if (scope.objj_files[searchPath])
|
||||
{
|
||||
importPath = searchPath;
|
||||
}
|
||||
}
|
||||
else if (fragment.type & FRAGMENT_FILE)
|
||||
{
|
||||
//CPLog.debug("Importing search-path file: " + fragment.info);
|
||||
|
||||
var count = scope.OBJJ_INCLUDE_PATHS.length;
|
||||
while (count--)
|
||||
{
|
||||
var searchPath = scope.OBJJ_INCLUDE_PATHS[count] + fragment.info;
|
||||
if (scope.objj_files[searchPath])
|
||||
{
|
||||
importPath = searchPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
CPLog.warn("Import fragment not local or file");
|
||||
|
||||
return importPath;
|
||||
}
|
||||
|
||||
// given a fresh scope and the path to a root source file, determine which files define each global variable
|
||||
function findGlobalDefines(context, scope, rootPath)
|
||||
{
|
||||
addMockBrowserEnvironment(scope);
|
||||
|
||||
var ignore = cloneProperties(scope, true);
|
||||
ignore['bundle'] = true;
|
||||
|
||||
var dependencies = {};
|
||||
|
||||
//scope.fragment_evaluate_file_original = scope.fragment_evaluate_file;
|
||||
//scope.fragment_evaluate_file = function(aFragment)
|
||||
//{
|
||||
// //CPLog.trace("Loading "+aFragment.info);
|
||||
//
|
||||
// var result = scope.fragment_evaluate_file_original(aFragment);
|
||||
//
|
||||
// return result;
|
||||
//}
|
||||
|
||||
scope.fragment_evaluate_code_original = scope.fragment_evaluate_code;
|
||||
scope.fragment_evaluate_code = function(aFragment)
|
||||
{
|
||||
CPLog.debug("Evaling "+aFragment.file.path + " / " + aFragment.bundle.path);
|
||||
|
||||
var before = cloneProperties(scope);
|
||||
|
||||
var result = scope.fragment_evaluate_code_original(aFragment);
|
||||
|
||||
var definedGlobals = {};
|
||||
diff(before, scope, ignore, definedGlobals, definedGlobals, null);
|
||||
dependencies[aFragment.file.path] = definedGlobals;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
runWithScope(context, scope, function(importName) {
|
||||
|
||||
print('Loading from '+OBJJ_INCLUDE_PATHS);
|
||||
|
||||
objj_import(importName, true, function() {
|
||||
print('Callback complete');
|
||||
});
|
||||
|
||||
print('Done');
|
||||
|
||||
}, [rootPath]);
|
||||
|
||||
//for (var i in scope.objj_included_files)
|
||||
// CPLog.debug(i + " ==> " + scope.objj_included_files[i]);
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
function coalesceGlobalDefines(globals)
|
||||
{
|
||||
var dependencies = {};
|
||||
for (var fileName in globals)
|
||||
{
|
||||
var fileGlobals = globals[fileName];
|
||||
|
||||
for (var globalName in fileGlobals)
|
||||
{
|
||||
if (!dependencies[globalName])
|
||||
dependencies[globalName] = [];
|
||||
dependencies[globalName].push(fileName);
|
||||
}
|
||||
}
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
// create a new scope loaded with Objective-J
|
||||
function makeObjjScope(context, debug)
|
||||
{
|
||||
// init standard js scope objects
|
||||
var scope = context.initStandardObjects();
|
||||
|
||||
if (debug)
|
||||
{
|
||||
scope.objj_alert = print;
|
||||
scope.debug = true;
|
||||
}
|
||||
|
||||
// give the scope "print"
|
||||
scope.print = function(value) { Packages.java.lang.System.out.println(value); };
|
||||
|
||||
// load and eval fake browser environment
|
||||
var envSource = readFile(envPath);
|
||||
if (envSource)
|
||||
context.evaluateString(scope, envSource, "env.js", 1, null);
|
||||
else
|
||||
CPLog.warn("Missing env.js");
|
||||
|
||||
// load and eval the bridge
|
||||
var bridgeSource = readFile(bridgePath);
|
||||
if (bridgeSource)
|
||||
context.evaluateString(scope, bridgeSource, "bridge.js", 1, null);
|
||||
else
|
||||
CPLog.warn("Missing bridge.js");
|
||||
|
||||
// load and eval obj-j
|
||||
var objjSource = readFile(objjPath);
|
||||
if (objjSource)
|
||||
context.evaluateString(scope, objjSource, "Objective-J.js", 1, null);
|
||||
else
|
||||
CPLog.warn("Missing Objective-J.js");
|
||||
|
||||
return scope;
|
||||
}
|
||||
|
||||
// run a function within the given scope (func can be a function object if the source of the function is returned by toString() as it is by default)
|
||||
function runWithScope(context, scope, func, arguments)
|
||||
{
|
||||
scope.__runWithScopeArgs = arguments || [];
|
||||
|
||||
var code = "("+func+").apply(this, this.__runWithScopeArgs); serviceTimeouts();";
|
||||
|
||||
return context.evaluateString(scope, code, "<cmd>", 1, null);
|
||||
}
|
||||
|
||||
// add a mock browser environment to the provided scope
|
||||
function addMockBrowserEnvironment(scope)
|
||||
{
|
||||
// TODO: complete this. or use env.js?
|
||||
|
||||
scope.Element = function() {
|
||||
this.style = {}
|
||||
}
|
||||
|
||||
scope.document = {
|
||||
createElement : function() {
|
||||
return new scope.Element();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// does a shallow copy of an object. if onlyList is true, it sets each property to "true" instead of the actual value
|
||||
function cloneProperties(object, onlyList)
|
||||
{
|
||||
var results = {}
|
||||
for (var memeber in object)
|
||||
results[memeber] = onlyList ? true : object[memeber];
|
||||
return results;
|
||||
}
|
||||
|
||||
function diff(objectA, objectB, ignore, added, changed, deleted)
|
||||
{
|
||||
for (var i in objectB)
|
||||
if (added && !ignore[i] && typeof objectA[i] == "undefined")
|
||||
added[i] = true;
|
||||
for (var i in objectB)
|
||||
if (changed && !ignore[i] && typeof objectA[i] != "undefined" && typeof objectB[i] != "undefined" && objectA[i] !== objectB[i])
|
||||
changed[i] = true;
|
||||
for (var i in objectA)
|
||||
if (deleted && !ignore[i] && typeof objectB[i] == "undefined")
|
||||
deleted[i] = true;
|
||||
}
|
||||
|
||||
function allKeys(object)
|
||||
{
|
||||
var result = [];
|
||||
for (var i in object)
|
||||
result.push(i)
|
||||
return result.sort();
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
import <Foundation/CPLog.j>
|
||||
|
||||
import "objj-analysis-tools.j"
|
||||
|
||||
CPLogRegister(CPLogPrint);
|
||||
|
||||
|
||||
function main()
|
||||
{
|
||||
if (args.length < 2)
|
||||
{
|
||||
print("Usage: press input_base_file.j output_directory");
|
||||
return;
|
||||
}
|
||||
|
||||
var rootPath = args[0],
|
||||
sourceDirectory = dirname(rootPath) || ".",
|
||||
outputDirectory = args[1];
|
||||
|
||||
var cx = Packages.org.mozilla.javascript.Context.enter(),
|
||||
scope = makeObjjScope(cx);
|
||||
|
||||
var frameworks = rootPath.substring(0, rootPath.lastIndexOf("/")+1) + "Frameworks/";
|
||||
scope.OBJJ_INCLUDE_PATHS = [frameworks];
|
||||
|
||||
CPLog.info("OBJJ_INCLUDE_PATHS="+scope.OBJJ_INCLUDE_PATHS);
|
||||
|
||||
// phase 1: get global defines
|
||||
|
||||
var globals = findGlobalDefines(cx, scope, rootPath);
|
||||
|
||||
// coalesce the results
|
||||
var dependencies = coalesceGlobalDefines(globals);
|
||||
|
||||
// phase 2: walk the import tree to determine exactly which files need to be included
|
||||
|
||||
var requiredFiles = {};
|
||||
|
||||
if (scope.objj_files[rootPath])
|
||||
{
|
||||
var context = {
|
||||
scope : scope,
|
||||
dependencies : dependencies,
|
||||
processedFiles : {},
|
||||
ignoreFrameworkImports : true,
|
||||
importCallback : function(importing, imported) {
|
||||
requiredFiles[imported] = true;
|
||||
},
|
||||
referenceCallback : function(referencing, referenced) {
|
||||
requiredFiles[referenced] = true;
|
||||
}
|
||||
}
|
||||
|
||||
traverseDependencies(context, scope.objj_files[rootPath]);
|
||||
|
||||
var count = 0,
|
||||
total = 0;
|
||||
for (var path in scope.objj_files)
|
||||
{
|
||||
if (requiredFiles[path])
|
||||
{
|
||||
CPLog.info("Included: " + path);
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
CPLog.warn("Excluded: " + path);
|
||||
}
|
||||
total++;
|
||||
}
|
||||
CPLog.error("Total required files: " + count + " out of " + total);
|
||||
}
|
||||
else
|
||||
{
|
||||
CPLog.error("Root file not loaded!");
|
||||
return;
|
||||
}
|
||||
|
||||
// phase 3: rebuild .sj files with correct imports, copy .j files
|
||||
|
||||
var outputFiles = {};
|
||||
for (var path in requiredFiles)
|
||||
{
|
||||
var file = scope.objj_files[path],
|
||||
filename = basename(path),
|
||||
directory = dirname(path);
|
||||
|
||||
if (file.path != path)
|
||||
CPLog.warn("Sanity check (file path): " + file.path + " vs. " + path);
|
||||
|
||||
if (file.bundle)
|
||||
{
|
||||
var bundleDirectory = dirname(file.bundle.path);
|
||||
|
||||
if (bundleDirectory != directory)
|
||||
CPLog.warn("Sanity check (directory path): " + directory + " vs. " + bundleDirectory);
|
||||
|
||||
// if it's in a .sj
|
||||
var dict = file.bundle.info,
|
||||
replacedFiles = [dict objectForKey:"CPBundleReplacedFiles"];
|
||||
if (replacedFiles && [replacedFiles containsObject:filename])
|
||||
{
|
||||
var staticPath = bundleDirectory + "/" + [dict objectForKey:"CPBundleExecutable"];
|
||||
if (!outputFiles[staticPath])
|
||||
{
|
||||
outputFiles[staticPath] = [];
|
||||
outputFiles[staticPath].push("@STATIC;1.0;");
|
||||
}
|
||||
outputFiles[staticPath].push("p;");
|
||||
outputFiles[staticPath].push(filename.length+";");
|
||||
outputFiles[staticPath].push(filename);
|
||||
|
||||
for (var i = 0; i < file.fragments.length; i++)
|
||||
{
|
||||
if (file.fragments[i].type & FRAGMENT_CODE)
|
||||
{
|
||||
outputFiles[staticPath].push("c;");
|
||||
outputFiles[staticPath].push(file.fragments[i].info.length+";");
|
||||
outputFiles[staticPath].push(file.fragments[i].info);
|
||||
}
|
||||
else if (file.fragments[i].type & FRAGMENT_IMPORT)
|
||||
{
|
||||
var ignoreFragment = false;
|
||||
if (file.fragments[i].conditionallyIgnore)
|
||||
{
|
||||
var importPath = findImportInObjjFiles(scope, file.fragments[i]);
|
||||
if (!importPath || !requiredFiles[importPath])
|
||||
{
|
||||
ignoreFragment = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ignoreFragment)
|
||||
{
|
||||
if (file.fragments[i].type & FRAGMENT_LOCAL)
|
||||
{
|
||||
var relativePath = pathRelativeTo(file.fragments[i].info, directory)
|
||||
|
||||
outputFiles[staticPath].push("i;");
|
||||
outputFiles[staticPath].push(relativePath.length+";");
|
||||
outputFiles[staticPath].push(relativePath);
|
||||
}
|
||||
else if (file.fragments[i].type & FRAGMENT_FILE)
|
||||
{
|
||||
outputFiles[staticPath].push("I;");
|
||||
outputFiles[staticPath].push(file.fragments[i].info.length+";");
|
||||
outputFiles[staticPath].push(file.fragments[i].info);
|
||||
}
|
||||
}
|
||||
else
|
||||
CPLog.warn("Ignoring import fragment " + file.fragments[i].info + " in " + path);
|
||||
}
|
||||
else
|
||||
CPLog.error("Unknown fragment type");
|
||||
}
|
||||
}
|
||||
// always output individual .j files
|
||||
else
|
||||
{
|
||||
outputFiles[path] = file.contents;
|
||||
}
|
||||
}
|
||||
else
|
||||
CPLog.warn("No bundle for " + path)
|
||||
}
|
||||
|
||||
// phase 4: copy everything and write out the new files
|
||||
|
||||
var sourceDirectoryFile = new Packages.java.io.File(sourceDirectory),
|
||||
outputDirectoryFile = new Packages.java.io.File(outputDirectory);
|
||||
|
||||
copyDirectory(sourceDirectoryFile, outputDirectoryFile);
|
||||
|
||||
for (var path in outputFiles)
|
||||
{
|
||||
var file = new java.io.File(outputDirectoryFile, path);
|
||||
|
||||
var parent = file.getParentFile();
|
||||
if (!parent.exists())
|
||||
{
|
||||
CPLog.warn(parent + " doesn't exist, creating directories.");
|
||||
parent.mkdirs();
|
||||
}
|
||||
|
||||
CPLog.info("Writing out " + file);
|
||||
|
||||
var writer = new java.io.BufferedWriter(new java.io.FileWriter(file));
|
||||
|
||||
if (typeof outputFiles[path] == "string")
|
||||
writer.write(outputFiles[path]);
|
||||
else
|
||||
writer.write(outputFiles[path].join(""));
|
||||
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper Utilities
|
||||
|
||||
// TODO: moved elsewhere?
|
||||
|
||||
function copyDirectory(src, dst)
|
||||
{
|
||||
CPLog.trace("Copying directory " + src);
|
||||
|
||||
dst.mkdirs();
|
||||
|
||||
var files = src.listFiles();
|
||||
for (var i = 0; i < files.length; i++)
|
||||
{
|
||||
if (files[i].isFile())
|
||||
copyFile(files[i], new Packages.java.io.File(dst, files[i].getName()));
|
||||
else if (files[i].isDirectory())
|
||||
copyDirectory(files[i], new Packages.java.io.File(dst, files[i].getName()));
|
||||
}
|
||||
}
|
||||
|
||||
function copyFile(src, dst)
|
||||
{
|
||||
CPLog.trace("Copying file " + src);
|
||||
|
||||
var input = (new Packages.java.io.FileInputStream(src)).getChannel(),
|
||||
output = (new Packages.java.io.FileOutputStream(dst)).getChannel();
|
||||
|
||||
input.transferTo(0, input.size(), output);
|
||||
|
||||
input.close();
|
||||
output.close();
|
||||
}
|
||||
|
||||
function dirname(path)
|
||||
{
|
||||
return path.substring(0, path.lastIndexOf("/"));
|
||||
}
|
||||
|
||||
function basename(path)
|
||||
{
|
||||
return path.substring(path.lastIndexOf("/") + 1);
|
||||
}
|
||||
|
||||
function pathRelativeTo(target, relativeTo)
|
||||
{
|
||||
var components = [],
|
||||
targetParts = target.split("/"),
|
||||
relativeParts = relativeTo.split("/");
|
||||
|
||||
var i = 0;
|
||||
while (i < targetParts.length)
|
||||
{
|
||||
if (targetParts[i] != relativeParts[i])
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
|
||||
for (var j = i; j < relativeParts.length; j++)
|
||||
components.push("..");
|
||||
|
||||
for (var j = i; j < targetParts.length; j++)
|
||||
components.push(targetParts[j]);
|
||||
|
||||
return components.join("/");
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user