1 /* ***** BEGIN LICENSE BLOCK *****
  2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3  *
  4  * The contents of this file are subject to the Mozilla Public License Version
  5  * 1.1 (the "License"); you may not use this file except in compliance with
  6  * the License. You may obtain a copy of the License at
  7  * http://www.mozilla.org/MPL/
  8  *
  9  * Software distributed under the License is distributed on an "AS IS" basis,
 10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 11  * for the specific language governing rights and limitations under the
 12  * License.
 13  *
 14  * The Initial Developer of the Original Code is Fireinput Inc.
 15  *
 16  * Portions created by the Initial Developer are Copyright (C) 2007
 17  * the Initial Developer. All Rights Reserved.
 18  *
 19  * Contributor(s):
 20  *     Olly Ja <ollyja@gmail.com>
 21  *
 22  * Alternatively, the contents of this file may be used under the terms of
 23  * either the GNU General Public License Version 2 or later (the "GPL"), or
 24  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 25  * in which case the provisions of the GPL or the LGPL are applicable instead
 26  * of those above. If you wish to allow use of your version of this file only
 27  * under the terms of either the GPL or the LGPL, and not to allow others to
 28  * use your version of this file under the terms of the MPL, indicate your
 29  * decision by deleting the provisions above and replace them with the notice
 30  * and other provisions required by the GPL or the LGPL. If you do not delete
 31  * the provisions above, a recipient may use your version of this file under
 32  * the terms of any one of the MPL, the GPL or the LGPL.
 33  *
 34  * ***** END LICENSE BLOCK ***** 
 35  */
 36 
 37 var FireinputXPC = 
 38 {
 39     CC: function(cName)
 40     {
 41        return Components.classes[cName];
 42     },
 43 
 44     CI: function(ifaceName)
 45     {
 46        return Components.interfaces[ifaceName];
 47     },
 48 
 49     getService: function(cName, ifaceName)
 50     {
 51        return this.CC(cName).getService(this.CI(ifaceName));
 52     },
 53 
 54     createInstance: function(cName, ifaceName)
 55     {
 56        return this.CC(cName).createInstance(this.CI(ifaceName));
 57     },
 58 
 59     QueryInterface: function(obj, iface)
 60     {
 61        return obj.QueryInterface(iface);
 62     },
 63  
 64     getIOService: function()
 65     {
 66        return this.getService("@mozilla.org/network/io-service;1", "nsIIOService");
 67     } 
 68  
 69     
 70 }; 
 71 
 72 
 73 var FireinputEnv = 
 74 {
 75     getEnv: function(name)
 76     {
 77        var gEnv = FireinputXPC.getService("@mozilla.org/process/environment;1", "nsIEnvironment");
 78        return gEnv.get(name);
 79     },
 80 
 81     getOS: function()
 82     {
 83        var gOS = FireinputXPC.getService("@mozilla.org/network/protocol;1?name=http", "nsIHttpProtocolHandler");
 84        var oscpu = gOS.oscpu; 
 85        if(/linux/i.test(oscpu))
 86           return "Linux"; 
 87        if(/Mac/i.test(oscpu))
 88           return "Mac"; 
 89        if(/Windows/i.test(oscpu))
 90           return "Windows"; 
 91 
 92        return "Linux";
 93     },
 94     
 95     isLinux: function()
 96     {
 97        return this.getOS() == 'Linux'; 
 98     },
 99 
100     isWindows: function()
101     {
102        return this.getOS() == 'Windows'; 
103     },
104 
105     isMac: function()
106     {
107        return this.getOS() == 'Mac'; 
108     }
109 
110 }; 
111 
112 
113 var FireinputPref = 
114 {
115     
116     getPref: function(name, type)
117     {
118        var gPref = FireinputXPC.getService("@mozilla.org/preferences-service;1", "nsIPrefBranch"); 
119        var prefName = prefDomain + "." + name;
120 
121        if(type == 'undefined')
122           type = gPref.getPrefType(prefName);
123 
124        if (type == "INT" || type == gPref.PREF_INT)
125           return gPref.getIntPref(prefName);
126        else if (type == "BOOL" || type == gPref.PREF_BOOL)
127           return gPref.getBoolPref(prefName);
128        else if(type == "STRING" || type == gPref.PREF_STRING)
129           return gPref.getCharPref(prefName); 
130        else
131           return null; 
132     },
133 
134     setPref: function(name, type,  value)
135     {
136        var gPref = FireinputXPC.getService("@mozilla.org/preferences-service;1", "nsIPrefBranch"); 
137        var prefName = prefDomain + "." + name;
138 
139        if(type == 'undefined')
140           type = gPref.getPrefType(prefName);
141 
142        if (type == "INT" || type == gPref.PREF_INT)
143           gPref.setIntPref(prefName, value);
144        else if (type == "BOOL" || type == gPref.PREF_BOOL)
145           gPref.setBoolPref(prefName, value);
146        else 
147           gPref.setCharPref(prefName, value);
148     },
149 
150     addObserver: function(aTopic, aOwnsWeak)
151     {
152        var gPref = FireinputXPC.getService("@mozilla.org/preferences-service;1", "nsIPrefBranch"); 
153        var pbi =  gPref.QueryInterface(Components.interfaces.nsIPrefBranch2);
154        pbi.addObserver(prefDomain, aTopic, aOwnsWeak);
155     }
156 
157 }; 
158 
159 var FireinputUnicode = 
160 {
161 
162     getUnicodeString: function(text, charset)
163     {
164        var gUnicode = FireinputXPC.getService("@mozilla.org/intl/scriptableunicodeconverter", "nsIScriptableUnicodeConverter"); 
165        gUnicode.charset = charset ? charset : "UTF-8";
166        try
167        {
168           return gUnicode.ConvertToUnicode(text);
169        }
170        catch(ex) 
171        {
172          return text; 
173        }
174     },
175     
176     convertFromUnicode: function(text, charset)
177     {
178        try 
179        {
180           var gUnicode = FireinputXPC.getService("@mozilla.org/intl/scriptableunicodeconverter", "nsIScriptableUnicodeConverter"); 
181           gUnicode.charset = charset ? charset : "UTF-8";
182           text = gUnicode.ConvertFromUnicode(text);
183           return text + gUnicode.Finish();
184        }
185        catch(ex) 
186        {
187          return text; 
188        }
189     }
190 };
191 
192 
193 var FireinputUtils = 
194 {
195     debug: 0, 
196 
197     $STR: function(name)
198     {
199        return document.getElementById("strings_fireinput").getString(name);
200     },
201 
202     $STRF: function(name, args)
203     {
204        return document.getElementById("strings_fireinput").getFormattedString(name, args);
205     },
206  
207     loadURI: function(url)
208     {
209       if(gBrowser.currentURI.spec == 'about:blank' || gBrowser.currentURI.spec == '')
210           gBrowser.loadURI(url); 
211        else
212           gBrowser.selectedTab = gBrowser.addTab(url); 
213     }, 
214 
215     setFocus: function(target)
216     {
217        try {
218           target.focus();
219        }
220        catch(e)
221        {
222        }
223     }, 
224 
225     insertCharAtCaret: function (element, text, sourceType, insertMode) 
226     {
227        if(!element)
228           return; 
229 
230        // text field. We can only insert bb code here  	
231        if(element.target.setSelectionRange) 
232        {
233           // setFocus before set any values to avoid being reset by target onfocus event -  normally happens to textfield or textarea  
234           this.setFocus(element.target); 
235 
236 	  var start = element.selectionStart; 
237 	  var end   = element.selectionEnd; 
238           var target = element.target; 
239 
240           // hack: the onfocus event will select all. We try to avoid this. 
241           if(element.focused == 0 && start != end && end == target.value.length)
242           {
243              start = end; 
244           }
245 
246           var scrollTop = target.scrollTop;
247           var scrollLeft = target.scrollLeft; 
248           var isBottom = (scrollTop + target.clientHeight) == target.scrollHeight;
249           if(typeof(sourceType) != 'undefined' && sourceType == IMAGE_SOURCE_TYPE)
250           {
251              if(insertMode == IMAGE_INSERT_BBCODE_URL) 
252       	        text = "[img]" + text + "[/img]"; 
253           }
254 
255 	  if(start != null) 
256 	  {
257 	     target.value = target.value.substr(0, start) 
258 			   + text + target.value.substr(end, target.value.length);
259 	     this.setCaretTo(target, start + text.length);
260 	  }
261           else 
262    	  {
263 	     target.value += text;
264 	     this.setCaretTo(target, target.value.length);
265 	  }
266           
267           if(typeof(scrollLeft) != "undefined")
268              target.scrollLeft = scrollLeft;
269           if(typeof(scrollTop) != "undefined")
270           {
271              if(!isBottom)
272                target.scrollTop = scrollTop;
273              else  
274                target.scrollTop = target.scrollHeight;
275           }
276 
277           // some target might have oninput event. Since the value is changed by script programmatically, 
278           // this event won't fire 
279           // Why do this way: the oninput won't show up as item of target. It must go to DOM tree to find 
280           // the attribute. Unfortunately if it's added by addEventListener, it won't be executed 
281           // The following code has security issue 
282           /* 
283           if(target.id != '')
284           {
285              var win = target.ownerDocument.defaultView; 
286              while (win.parent != win)
287                 win = window.parent;
288 
289              if(win.document)
290              {
291                  var node = win.document.getElementById(target.id); 
292                  if(node.hasAttribute("oninput"))
293                  {
294                      var f = node.getAttribute("oninput"); 
295                      try {
296                         eval(f); 
297                      } catch(e) { }
298                  }
299              }
300           }
301           */
302        }
303        else if(element.documentTarget)
304        {
305           var win = element.target; 
306           var doc = win.content.document; 
307           var selection =  win.getSelection();
308 
309           if(!selection.focusNode)
310              return; 
311 
312           //FireinputLog.debug(this, "selection.focusNode.nodeType: " + selection.focusNode.nodeType); 
313           //FireinputLog.debug(this, "selection.focusNode.name: " + selection.focusNode.nodeName); 
314           //FireinputLog.debug(this, "selection.focusNode.value: " + selection.focusNode.nodeValue); 
315           //FireinputLog.debug(this, "selection.focusOffset: " + selection.focusOffset); 
316 
317           // get the first range of the selection
318           // delete content if the collpase is not true 
319           var range = selection.getRangeAt(0);
320           if(!range.collapsed)
321               range.deleteContents();
322 
323           if (selection.focusNode.nodeType == Node.TEXT_NODE)
324           {
325 
326              if(typeof(sourceType) != 'undefined' && sourceType == IMAGE_SOURCE_TYPE)
327              {
328                 var imgNode = doc.createElement("img");
329                 imgNode.setAttribute("src", text); 
330 
331                 var startContainer = range.startContainer;
332                 var startOffset = range.startOffset;
333                 var beforeNode = startContainer.splitText(startOffset);
334                 var parentNode = beforeNode.parentNode;
335                 parentNode.insertBefore(imgNode, beforeNode);
336                 selection.collapse(beforeNode, 0); 
337              }
338              else 
339              {
340                 var endOffset = selection.focusOffset;
341                 var nodeValue = selection.focusNode.nodeValue; 
342 
343                 nodeValue = nodeValue.substr(0,endOffset) + text + 
344                           nodeValue.substr(endOffset, nodeValue.length); 
345                 selection.focusNode.nodeValue = nodeValue; 
346                 selection.collapse(selection.focusNode, endOffset+text.length); 
347              }
348 
349           }
350           else
351           {
352              this.insertAtSelection(selection, text, sourceType);
353           }
354        }
355        else 
356        {
357           element.target.value += text;
358        }
359     },
360 
361     setCaretTo: function (obj, pos) 
362     {
363        if(obj.selectionStart) 
364        {
365 	   // should turn on focus ? 
366 	   // obj.focus();
367 	  obj.setSelectionRange(pos, pos);
368        }
369     },
370 
371     getCaretPos: function(obj)
372     {
373        return obj.selectionEnd; 
374     },
375 
376     insertAtSelection: function(selection, text, sourceType)
377     {
378        var range = selection.getRangeAt(0);
379        var ancestorContainer = range.commonAncestorContainer;
380        var doc = ancestorContainer.ownerDocument;
381 
382        var startContainer = range.startContainer;
383        var startOffset = range.startOffset;
384 
385        try {
386           if (ancestorContainer == doc.body)
387           ancestorContainer = doc.documentElement;
388        } catch (e) { }
389     
390 
391        // each path is a "child sequence" (a.k.a. "tumbler") that
392        // descends from the ancestor down to the boundary point
393        var startPath = this.getPath(ancestorContainer, startContainer);
394 
395        startContainer = ancestorContainer;
396 
397        var canInsert = ancestorContainer.hasChildNodes();
398        if (canInsert)
399        {
400           var i;
401           for (i = startPath ? startPath.length-1 : -1; i >= 0; i--) 
402           {
403              startContainer = startContainer.childNodes.item(startPath[i]);
404           }
405 
406           var tmpNode = "";
407           if(typeof(sourceType) != 'undefined' && sourceType == IMAGE_SOURCE_TYPE)
408           {
409              tmpNode = doc.createElement("img");
410              tmpNode.setAttribute("src", text);
411           }
412           else 
413              tmpNode = doc.createTextNode(text);
414 
415           startContainer.insertBefore(tmpNode, startContainer.childNodes.item(startOffset));
416           selection.collapse(startContainer, startOffset+1);
417       }
418     }, 
419 
420     getPath: function (ancestor, node)
421     {
422        var n = node;
423        var p = n.parentNode;
424        if (n == ancestor || !p)
425           return null;
426 
427        var path = new Array();
428        if (!path)
429           return null;
430        do 
431        {
432           for (var i = 0; i < p.childNodes.length; i++) 
433           {
434              if (p.childNodes.item(i) == n) 
435              {
436                 path[path.length] = i; 
437                 break;
438              }
439           }
440           n = p;
441           p = n.parentNode;
442        } while (n != ancestor && p);
443 
444        return path;
445     },
446 
447     findPosX: function(obj)
448     {
449        var curleft = obj.offsetLeft;
450        if(obj.offsetParent)
451        { 
452           var osParent = obj.offsetParent; 
453           while(osParent)
454           {
455              curleft += osParent.offsetLeft;
456              osParent = osParent.offsetParent;
457           }
458        }        
459        else if(obj.x)
460           curleft += obj.x;
461        return curleft;
462     },
463 
464     findPosY: function(obj)
465     {
466        var curtop = obj.offsetTop;
467        if(obj.offsetParent)
468        {
469           var osParent = obj.offsetParent; 
470           while(osParent)
471           {
472              curtop += osParent.offsetTop;
473              osParent = osParent.offsetParent;
474           }
475        }
476        else if(obj.y)
477           curtop += obj.y;
478        return curtop;
479     },
480 
481     getDocumentScrollTop: function(document)
482     {
483        if(!document) 
484           return 0; 
485        return document.documentElement.scrollTop || 
486        (document.body ? document.body.scrollTop : 0); 
487     }, 
488 
489     getDocumentScrollLeft: function(document)
490     {
491        if(!document) 
492           return 0; 
493 
494        return document.documentElement.scrollLeft || 
495        (document.body ? document.body.scrollLeft : 0); 
496     }, 
497 
498     getExtensionPath : function() 
499     {
500        try 
501        {
502           var chromeRegistry = FireinputXPC.getService("@mozilla.org/chrome/chrome-registry;1", "nsIChromeRegistry");
503           var uri = FireinputXPC.createInstance("@mozilla.org/network/standard-url;1", "nsIURI");
504 					
505           uri.spec = "chrome://fireinput/content/";
506 			
507           var path = chromeRegistry.convertChromeURL(uri);
508           if (typeof(path) == "object") path = path.spec
509 
510           if (path.length >= 4 && path.substr(0, 4) == "jar:") 
511           {
512              path = path.substring(4); // remove "jar:" at the beginning
513           }
514 
515           return path; 
516        }
517        catch (e) 
518        {
519        }
520 
521        return undefined; 
522     },
523 
524     getAppRootPath: function()
525     {
526        var path = this.getExtensionPath();
527 
528        path =  path.substring(0, path.lastIndexOf("/extensions/fireinput@software"));
529        return path;
530     },
531 
532     trimString: function(str)
533     {
534        return str.replace(/^\s+/g, '').replace(/\s+$/g, '');
535     },
536 
537     getLocaleString: function (stringKey)
538     {
539        var stringBundle = FireinputXPC.getService("@mozilla.org/intl/stringbundle;1", "nsIStringBundleService");
540        var propertyBundle  = "chrome://fireinput/locale/fireinput.properties";
541        var bundle = stringBundle.createBundle(propertyBundle);
542        var str = ""; 
543        try 
544        {
545           str = bundle.GetStringFromName(stringKey);
546        }
547        catch (e) 
548        {
549        }
550  
551        return str; 
552     },
553 
554     getIMENameString: function(value)
555     {
556        var defaultLanguage = fireinputPrefGetDefault("interfaceLanguage"); 
557        switch(value)
558        {
559           case SMART_PINYIN:
560           default:
561              return this.getLocaleString('fireinput.pinyin.quan.label' + defaultLanguage);
562           break;
563           case ZIGUANG_SHUANGPIN:
564              return this.getLocaleString('fireinput.pinyin.shuang.ziguang.label' + defaultLanguage);
565           break;
566           case MS_SHUANGPIN:
567              return this.getLocaleString('fireinput.pinyin.shuang.ms.label' + defaultLanguage);
568           break;
569           case CHINESESTAR_SHUANGPIN:
570              return this.getLocaleString('fireinput.pinyin.shuang.chinesestar.label' + defaultLanguage);
571           break;
572           case SMARTABC_SHUANGPIN:
573              return this.getLocaleString('fireinput.pinyin.shuang.smartabc.label' + defaultLanguage);
574           break;
575           case WUBI_86:
576              return this.getLocaleString('fireinput.wubi86.label' + defaultLanguage);
577           break;
578           case WUBI_98:
579              return this.getLocaleString('fireinput.wubi98.label' + defaultLanguage);
580           break;
581           case CANGJIE_5:
582              return this.getLocaleString('fireinput.cangjie5.label' + defaultLanguage);
583           break;
584        }
585 
586        return "";
587     },
588 
589     isValidForDirectInput: function(str)
590     {
591        if(!str) 
592           return false; 
593        // check whether it's email 
594        if(str.indexOf(".") > 0 && str.indexOf("@") > 0)
595           return true; 
596    
597        // check whether it's url 
598        var reg = new RegExp("^[A-Za-z]+://[A-Za-z0-9-]+\.[A-Za-z0-9]+"); 
599        if(reg.test(str))
600           return true; 
601 
602        return false; 
603     }, 
604 
605     notify: function(aSubject, aTopic, aData)
606     {
607       var os = FireinputXPC.getService("@mozilla.org/observer-service;1", "nsIObserverService");
608       os.notifyObservers(aSubject, aTopic, aData); 
609       return true;
610     }
611 }; 
612 
613 var FireinputHash = function() 
614 {
615     this.items = new Array();
616     this.length = 0;
617 }; 
618 
619 FireinputHash.prototype = 
620 {
621     foreach: function(callback) 
622     {
623        for (var k in this.items) 
624        {
625           if(callback) callback(k, this.items[k]);
626        }
627     },
628 
629     removeItem: function(in_key) 
630     {
631        var tmp_value;
632        if (typeof(this.items[in_key]) != 'undefined') 
633        {
634           this.length--;
635           var tmp_value = this.items[in_key];
636           delete this.items[in_key];
637        }
638 
639        return tmp_value;
640     },
641 
642     getItem: function(in_key) 
643     {
644        if (typeof(this.items[in_key]) == 'undefined')
645           return null; 
646        else 
647           return this.items[in_key];
648     },
649 
650     setItem: function(in_key, in_value) 
651     {
652        if (typeof(in_value) != 'undefined') 
653        {
654           if (typeof(this.items[in_key]) == 'undefined') 
655           {
656              this.length++;
657           }
658 
659           this.items[in_key] = in_value;
660        }
661 
662        return in_value;
663     },
664 
665     hasItem: function(in_key) {
666        return typeof(this.items[in_key]) != 'undefined';
667     }
668 };
669 
670 var Ajax = function() {};
671 Ajax.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
672 
673 Ajax.prototype = 
674 {
675     transport: null, 
676 
677     setOptions: function(options) 
678     {
679        this.options = 
680        {
681           method:       'post',
682           asynchronous: true,
683           contextType:  'application/x-www-form-urlencoded',
684           parameters:   ''
685        }
686 
687        extend(this.options, options || {})
688     },
689 
690     responseIsSuccess: function() 
691     {
692        return this.transport.status == undefined
693         || this.transport.status == 0
694         || (this.transport.status >= 200 && this.transport.status < 300);
695     },
696   
697     responseIsFailure: function() 
698     {
699        return !this.responseIsSuccess();
700     }, 
701 
702     request: function(url) 
703     {
704        this.transport = new XMLHttpRequest(); 
705 
706        var parameters = this.options.parameters || '';
707        if (parameters.length > 0) parameters += '&_=';
708 
709        try 
710        {
711           this.url = url;
712           if (this.options.method == 'get' && parameters.length > 0)
713              this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
714 
715           this.transport.open(this.options.method, this.url,
716              this.options.asynchronous);
717           if (this.options.asynchronous) 
718           {
719              this.transport.onreadystatechange = bind(this.onStateChange, this); 
720              setTimeout(bind(function() {this.respondToReadyState(1)}, this), 10);
721           }
722 
723           this.setRequestHeaders();
724 
725           var body = this.options.postBody ? this.options.postBody : parameters;
726           this.transport.send(this.options.method == 'post' ? body : null);
727 
728        } 
729        catch (e) 
730        {
731           this.dispatchException(e);
732        }
733     },
734 
735     setRequestHeaders: function() 
736     {
737        var requestHeaders =
738           ['X-Requested-With', 'XMLHttpRequest',
739            'X-Prototype-Version', '1.0',
740            'Accept', 'text/javascript, text/html, application/xml, text/xml, */*'];
741 
742        if (this.options.method == 'post') 
743        {
744           requestHeaders.push('Content-type', this.options.contentType);
745 
746           if (this.transport.overrideMimeType)
747              requestHeaders.push('Connection', 'close');
748        }
749 
750        if (this.options.requestHeaders)
751           requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
752 
753        for (var i = 0; i < requestHeaders.length; i += 2)
754           this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
755     },
756 
757     onStateChange: function() 
758     {
759        var readyState = this.transport.readyState;
760        if (readyState != 1)
761           this.respondToReadyState(this.transport.readyState);
762     },
763 
764     header: function(name) 
765     {
766        try {
767           return this.transport.getResponseHeader(name);
768        }
769        catch (e) {}
770 
771        return null; 
772     },
773 
774     evalJSON: function() 
775     {
776        try {
777           return eval('(' + this.header('X-JSON') + ')');
778        } catch (e) {}
779 
780        return null; 
781     },
782 
783     evalResponse: function() 
784     {
785        try {
786           return eval(this.transport.responseText);
787        }
788        catch (e) 
789        {
790           this.dispatchException(e);
791        }
792 
793        return null; 
794     },
795 
796 
797     respondToReadyState: function(readyState) 
798     {
799        var event = Ajax.Events[readyState];
800        var transport = this.transport, json = this.evalJSON();
801 
802        if (event == 'Complete') 
803        {
804           try {
805              (this.options['on' + this.transport.status]
806              || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
807              )(transport, json);
808           } 
809           catch (e) 
810           {
811              this.dispatchException(e);
812           }
813           if ((this.header('Content-type') || '').match(/^text\/javascript/i))
814              this.evalResponse();
815        }
816 
817        try {
818           if(typeof(this.options['on' + event]) != "undefined")
819              (this.options['on' + event])(transport, json);
820        }
821        catch (e) {
822           this.dispatchException(e);
823        }
824 
825     },
826     dispatchException: function(exception) 
827     {
828        if(this.options.onException)
829           (this.options.onException)(this, exception);
830     }
831 };
832 
833  


syntax highlighted by Code2HTML, v. 0.9.1