aboutsummaryrefslogtreecommitdiff
path: root/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'chrome')
-rw-r--r--chrome/code/browserOverlay.xul.js104
-rw-r--r--chrome/code/component.js21
-rw-r--r--chrome/code/editRedirect.xul.js103
-rw-r--r--chrome/code/redirect.js218
-rw-r--r--chrome/code/redirector.js (renamed from chrome/content/code/redirector.prototype.js)713
-rw-r--r--chrome/code/redirectorprefs.js93
-rw-r--r--chrome/code/settings.xul.js276
-rw-r--r--chrome/code/unittest/run.html99
-rw-r--r--chrome/code/unittest/testcases.js129
-rw-r--r--chrome/ui/browserOverlay.xul34
-rw-r--r--chrome/ui/editRedirect.xul58
-rw-r--r--chrome/ui/help.html182
-rw-r--r--chrome/ui/settings.xul118
-rw-r--r--chrome/ui/skin/movedown.pngbin0 -> 294 bytes
-rw-r--r--chrome/ui/skin/movedowndisabled.pngbin0 -> 361 bytes
-rw-r--r--chrome/ui/skin/moveup.pngbin0 -> 360 bytes
-rw-r--r--chrome/ui/skin/moveupdisabled.pngbin0 -> 282 bytes
-rw-r--r--chrome/ui/skin/redirector.css15
-rw-r--r--chrome/ui/skin/redirector.pngbin0 -> 1462 bytes
-rw-r--r--chrome/ui/skin/statusactive.pngbin0 -> 360 bytes
-rw-r--r--chrome/ui/skin/statusinactive.pngbin0 -> 396 bytes
21 files changed, 1820 insertions, 343 deletions
diff --git a/chrome/code/browserOverlay.xul.js b/chrome/code/browserOverlay.xul.js
new file mode 100644
index 0000000..0d2f46e
--- /dev/null
+++ b/chrome/code/browserOverlay.xul.js
@@ -0,0 +1,104 @@
+
+Components.utils.import("chrome://redirector/content/code/redirector.js");
+
+var RedirectorOverlay = {
+
+ strings : null,
+ prefs : null,
+
+ onLoad : function(event) {
+ try {
+
+ // initialization code
+ document.getElementById('contentAreaContextMenu')
+ .addEventListener("popupshowing", function(e) { RedirectorOverlay.showContextMenu(e); }, false);
+
+ this.strings = document.getElementById("redirector-strings");
+ this.prefs = new RedirectorPrefs();
+ this.changedPrefs(this.prefs);
+ this.prefs.addListener(this);
+ } catch(e) {
+ if (this.strings) {
+ alert(this.strings.getString("initError") + "\n\n" + e);
+ } else {
+ alert(e);
+ }
+ }
+ },
+
+ onUnload : function(event) {
+ this.prefs.dispose();
+ Redirector.debug("Finished cleanup");
+ },
+
+ changedPrefs : function(prefs) {
+ var statusImg = document.getElementById('redirector-statusbar-img');
+
+ if (prefs.enabled) {
+ statusImg.src = 'chrome://redirector/skin/statusactive.png'
+ statusImg.setAttribute('tooltiptext', this.strings.getString('enabledTooltip'));
+ } else {
+ statusImg.src = 'chrome://redirector/skin/statusinactive.png'
+ statusImg.setAttribute('tooltiptext', this.strings.getString('disabledTooltip'));
+ }
+
+ document.getElementById('redirector-status').hidden = !prefs.showStatusBarIcon;
+ document.getElementById('redirector-context').hidden = !prefs.showContextMenu;
+ },
+
+ showContextMenu : function(event) {
+ if (gContextMenu.onLink) {
+ document.getElementById("redirector-context").label = this.strings.getString('addLinkUrl');
+ } else {
+ document.getElementById("redirector-context").label = this.strings.getString('addCurrentUrl');
+ }
+ },
+
+ onContextMenuCommand: function(event) {
+ var redirect = new Redirect(window.content.location.href, window.content.location.href);
+ if (gContextMenu.onLink) {
+ redirect.redirectUrl = gContextMenu.link.toString();
+ }
+
+ var args = { saved : false, 'redirect' : redirect };
+ window.openDialog("chrome://redirector/content/ui/editRedirect.xul", "redirect", "chrome,dialog,modal,centerscreen", args);
+ if (args.saved) {
+ Redirector.addRedirect(args.redirect);
+ }
+ },
+
+ onMenuItemCommand: function(event) {
+ this.openSettings();
+ },
+
+ toggleEnabled : function(event) {
+ this.prefs.enabled = !this.prefs.enabled;
+ },
+
+ openSettings : function() {
+ var windowName = "redirectorSettings";
+ var windowsMediator = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator);
+ var win = windowsMediator.getMostRecentWindow(windowName);
+ if (win) {
+ win.focus();
+ } else {
+ window.openDialog("chrome://redirector/content/ui/settings.xul",
+ windowName,
+ "chrome,dialog,resizable=yes,centerscreen", this);
+ }
+
+ },
+
+ statusBarClick : function(event) {
+ var LEFT = 0, RIGHT = 2;
+
+ if (event.button == LEFT) {
+ RedirectorOverlay.toggleEnabled();
+ } else if (event.button == RIGHT) {
+ this.openSettings();
+ }
+ }
+
+};
+window.addEventListener("load", function(event) { RedirectorOverlay.onLoad(event); }, false);
+window.addEventListener("unload", function(event) { RedirectorOverlay.onUnload(event); }, false);
diff --git a/chrome/code/component.js b/chrome/code/component.js
new file mode 100644
index 0000000..453d134
--- /dev/null
+++ b/chrome/code/component.js
@@ -0,0 +1,21 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Ci = Components.interfaces;
+Cr = Components.results;
+
+Components.utils.import("chrome://redirector/content/code/redirector.js");
+
+function RedirectorComponent() { }
+
+RedirectorComponent.prototype = {
+ classDescription: "My Hello World Javascript XPCOM Component",
+ classID: Components.ID("{b7a7a54f-0581-47ff-b086-d6920cb7a3f7}"),
+ contractID: "@einaregilsson.com/redirector;1",
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIContentPolicy) || iid.equals(Ci.nsIChannelEventSink)) {
+ return Redirector;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([RedirectorComponent]);
diff --git a/chrome/code/editRedirect.xul.js b/chrome/code/editRedirect.xul.js
new file mode 100644
index 0000000..0c8f34b
--- /dev/null
+++ b/chrome/code/editRedirect.xul.js
@@ -0,0 +1,103 @@
+//// $Id$
+
+var EditRedirect = {
+ txtExampleUrl : null,
+ txtIncludePattern : null,
+ txtRedirectUrl : null,
+ txtExcludePattern : null,
+ chkUnescapeMatches : null,
+ rdoRegex : null,
+ rdoWildcard : null,
+
+ onLoad : function() {
+ var args = window.arguments[0];
+ var redirect = args.redirect;
+ this.txtExampleUrl = document.getElementById('txtExampleUrl');
+ this.txtIncludePattern = document.getElementById('txtIncludePattern');
+ this.txtRedirectUrl= document.getElementById('txtRedirectUrl');
+ this.txtExcludePattern= document.getElementById('txtExcludePattern');
+ this.chkUnescapeMatches= document.getElementById('chkUnescapeMatches');
+ this.rdoWildcard= document.getElementById('rdoWildcard');
+ this.rdoRegex = document.getElementById('rdoRegex');
+
+ this.txtExampleUrl.value = redirect.exampleUrl;
+ this.txtIncludePattern.value = redirect.includePattern;
+ this.txtExcludePattern.value = redirect.excludePattern;
+ this.txtRedirectUrl.value = redirect.redirectUrl;
+ this.chkUnescapeMatches.setAttribute('checked', redirect.unescapeMatches);
+ this.rdoRegex.setAttribute('selected', redirect.isRegex());
+ this.rdoWildcard.setAttribute('selected', redirect.isWildcard());
+
+ this.txtIncludePattern.focus();
+ this.strings = document.getElementById("redirector-strings");
+ },
+
+ onAccept : function() {
+ var args = window.arguments[0];
+ var msg, title;
+ args.saved = true;
+ this.saveValues(args.redirect);
+
+ var oldDisabled = args.redirect.disabled;
+ args.redirect.disabled = false;
+ if (!/^\s*$/.test(args.redirect.exampleUrl)) {
+ var result = args.redirect.getMatch(args.redirect.exampleUrl);
+ if (!result.isMatch) {
+ title = this.strings.getString('warningExampleUrlDoesntMatchPatternTitle');
+ msg = this.strings.getString('warningExampleUrlDoesntMatchPattern');
+ var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
+ var rv = ps.confirmEx(window, title, msg, ps.STD_YES_NO_BUTTONS, ps.BUTTON_TITLE_YES, ps.BUTTON_TITLE_NO, null, null, {});
+ return rv == 0;
+ } else {
+ var resultUrl = result.redirectTo;
+ if (!resultUrl.match(/https?:/)) {
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
+ var uri = ioService.newURI(args.redirect.exampleUrl, null, null);
+ resultUrl = uri.resolve(resultUrl);
+ }
+
+ var secondResult = args.redirect.getMatch(resultUrl);
+ if (secondResult.isMatch) {
+ title = this.strings.getString('errorExampleUrlMatchesRecursiveTitle');
+ msg = this.strings.getFormattedString('errorExampleUrlMatchesRecursive', [args.redirect.exampleUrl, resultUrl]);
+ this.msgBox(title, msg);
+ return false;
+ }
+ }
+ }
+ return true;
+ },
+
+ msgBox : function(title, text) {
+ Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService)
+ .alert(window, title, text);
+ },
+
+ saveValues : function(redirect) {
+ redirect.exampleUrl = this.txtExampleUrl.value;
+ redirect.includePattern = this.txtIncludePattern.value;
+ redirect.excludePattern = this.txtExcludePattern.value;
+ redirect.redirectUrl = this.txtRedirectUrl.value;
+ redirect.patternType = this.rdoRegex.getAttribute('selected') == 'true' ? Redirect.REGEX : Redirect.WILDCARD;
+ var val = this.chkUnescapeMatches.getAttribute('checked');
+ redirect.unescapeMatches = val === 'true' || val === true;
+ //Disabled cannot be set here
+ },
+
+ testPattern : function() {
+ try {
+ var redirect = new Redirect();
+ this.saveValues(redirect);
+ var extName = this.strings.getString('extensionName');
+ var result = redirect.test();
+ if (result.isMatch) {
+ this.msgBox(extName, this.strings.getFormattedString('testPatternSuccess', [redirect.includePattern, redirect.exampleUrl, result.redirectTo]));
+ } else if (result.isExcludeMatch) {
+ this.msgBox(extName, this.strings.getFormattedString('testPatternExclude', [redirect.exampleUrl, redirect.excludePattern]));
+ } else {
+ this.msgBox(extName, this.strings.getFormattedString('testPatternFailure', [redirect.includePattern, redirect.exampleUrl]));
+ }
+ } catch(e) {alert(e);}
+ }
+}; \ No newline at end of file
diff --git a/chrome/code/redirect.js b/chrome/code/redirect.js
new file mode 100644
index 0000000..f9b7f00
--- /dev/null
+++ b/chrome/code/redirect.js
@@ -0,0 +1,218 @@
+
+var EXPORTED_SYMBOLS = ['Redirect'];
+
+
+function Redirect(exampleUrl, includePattern, redirectUrl, patternType, excludePattern, unescapeMatches, disabled) {
+ this._init(exampleUrl, includePattern, redirectUrl, patternType, excludePattern, unescapeMatches, disabled);
+}
+
+//Static
+Redirect.WILDCARD = 'W';
+Redirect.REGEX = 'R';
+
+Redirect.prototype = {
+
+ //attributes
+ exampleUrl : null,
+
+ get includePattern() { return this._includePattern; },
+ set includePattern(value) {
+ this._includePattern = value;
+ this._rxInclude = this._compile(value);
+ },
+
+ get excludePattern() { return this._excludePattern; },
+ set excludePattern(value) {
+ this._excludePattern = value;
+ this._rxExclude = this._compile(value);
+ },
+
+ redirectUrl : null,
+
+ get patternType() { return this._patternType; },
+ set patternType(value) {
+ this._patternType = value;
+ this.compile();
+ },
+
+ unescapeMatches : false,
+
+ disabled : false,
+
+ //Functions
+ clone : function() {
+ return new Redirect(this.exampleUrl, this.includePattern,
+ this.redirectUrl, this.patternType,
+ this.excludePattern, this.unescapeMatches,
+ this.disabled);
+ },
+
+ compile : function() {
+ this._rxInclude = this._compile(this._includePattern);
+ this._rxExclude = this._compile(this._excludePattern);
+ },
+
+ copyValues : function(other) {
+ this.exampleUrl = other.exampleUrl;
+ this.includePattern = other.includePattern;
+ this.excludePattern = other.excludePattern;
+ this.redirectUrl = other.redirectUrl;
+ this.patternType = other.patternType;
+ this.unescapeMatches = other.unescapeMatches;
+ this.disabled = other.disabled;
+ },
+
+ deserialize : function(str) {
+ if (!str || !str.split) {
+ throw Error("Invalid serialized redirect: " + str);
+ }
+ var parts = str.split(',,,');
+ if (parts.length < 5) {
+ throw Error("Invalid serialized redirect, too few fields: " + str);
+ }
+ this._init.apply(this, parts);
+ },
+
+ equals : function(redirect) {
+ return this.exampleUrl == redirect.exampleUrl
+ && this.includePattern == redirect.includePattern
+ && this.excludePattern == redirect.excludePattern
+ && this.redirectUrl == redirect.redirectUrl
+ && this.patternType == redirect.patternType
+ && this.unescapeMatches == redirect.unescapeMatches
+ ;
+ },
+
+ getMatch: function(url) {
+ var result = {
+ isMatch : false,
+ isExcludeMatch : false,
+ isDisabledMatch : false,
+ redirectTo : '',
+ toString : function() { return "{ isMatch : " + this.isMatch +
+ ", isExcludeMatch : " + this.isExcludeMatch +
+ ", isDisabledMatch : " + this.isDisabledMatch +
+ ", redirectTo : \"" + this.redirectTo + "\"" +
+ "}"; }
+ };
+ var redirectTo = null;
+
+ redirectTo = this._includeMatch(url);
+ if (redirectTo !== null) {
+ if (this.disabled) {
+ result.isDisabledMatch = true;
+ } else if (this._excludeMatch(url)) {
+ result.isExcludeMatch = true;
+ } else {
+ result.isMatch = true;
+ result.redirectTo = redirectTo;
+ }
+ }
+ return result;
+ },
+
+ isRegex: function() {
+ return this.patternType == Redirect.REGEX;
+ },
+
+ isWildcard : function() {
+ return this.patternType == Redirect.WILDCARD;
+ },
+
+ serialize : function() {
+ return [ this.exampleUrl
+ , this.includePattern
+ , this.redirectUrl
+ , this.patternType
+ , this.excludePattern
+ , this.unescapeMatches
+ , this.disabled ].join(',,,');
+ },
+
+ test : function() {
+ return this.getMatch(this.exampleUrl);
+ },
+
+
+ //Private functions below
+
+ _includePattern : null,
+ _excludePattern : null,
+ _patternType : null,
+ _rxInclude : null,
+ _rxExclude : null,
+
+ _preparePattern : function(pattern) {
+ if (this.patternType == Redirect.REGEX) {
+ return pattern;
+ } else { //Convert wildcard to regex pattern
+ var converted = '^';
+ for (var i = 0; i < pattern.length; i++) {
+ var ch = pattern.charAt(i);
+ if ('()[]{}?.^$\\+'.indexOf(ch) != -1) {
+ converted += '\\' + ch;
+ } else if (ch == '*') {
+ converted += '(.*?)';
+ } else {
+ converted += ch;
+ }
+ }
+ converted += '$';
+ return converted;
+ }
+ },
+
+ _compile : function(pattern) {
+ if (!pattern) {
+ return null;
+ }
+ return new RegExp(this._preparePattern(pattern),"gi");
+ },
+
+ _init : function(exampleUrl, includePattern, redirectUrl, patternType, excludePattern, unescapeMatches, disabled) {
+ this.exampleUrl = exampleUrl || '';
+ this.includePattern = includePattern || '';
+ this.excludePattern = excludePattern || '';
+ this.redirectUrl = redirectUrl || '';
+ this.patternType = patternType || Redirect.WILDCARD;
+ this.unescapeMatches = (unescapeMatches === 'true' || unescapeMatches === true);
+ this.disabled = (disabled === 'true' || disabled === true);
+ },
+
+ toString : function() {
+ return 'REDIRECT: {'
+ + '\n\tExample url : ' + this.exampleUrl
+ + '\n\tInclude pattern : ' + this.includePattern
+ + '\n\tExclude pattern : ' + this.excludePattern
+ + '\n\tRedirect url : ' + this.redirectUrl
+ + '\n\tPattern type : ' + this.patternType
+ + '\n\tUnescape matches : ' + this.unescapeMatches
+ + '\n\tDisabled : ' + this.disabled
+ + '\n}\n';
+ },
+
+ _includeMatch : function(url) {
+ if (!this._rxInclude) {
+ return null;
+ }
+ var matches = this._rxInclude.exec(url);
+ if (!matches) {
+ return null;
+ }
+ var resultUrl = this.redirectUrl;
+ for (var i = 1; i < matches.length; i++) {
+ resultUrl = resultUrl.replace(new RegExp('\\$' + i, 'gi'), this.unescapeMatches ? unescape(matches[i]) : matches[i]);
+ }
+ this._rxInclude.lastIndex = 0;
+ return resultUrl;
+ },
+
+ _excludeMatch : function(url) {
+ if (!this._rxExclude) {
+ return false;
+ }
+ var shouldExclude = !!this._rxExclude.exec(url);
+ this._rxExclude.lastIndex = 0;
+ return shouldExclude;
+ }
+}; \ No newline at end of file
diff --git a/chrome/content/code/redirector.prototype.js b/chrome/code/redirector.js
index be920d8..8744483 100644
--- a/chrome/content/code/redirector.prototype.js
+++ b/chrome/code/redirector.js
@@ -1,343 +1,370 @@
-//// $Id$
-
-Redirector.prototype = {
-
- //rdIRedirector implementation
- get enabled() {
- return this._prefs && this._prefs.enabled;
- },
-
- set enabled(value) {
- if (this._prefs) {
- this._prefs.enabled = value;
- }
- },
-
- get redirectCount() {
- return this._list.length;
- },
-
- addRedirect : function(redirect) {
- //This runaround is necessary because the redirect
- //that was created somewhere up in the GUI doesn't
- //have the Redirect function in scope.
- var rx = new Redirect();
- rx.copyValues(redirect);
- this._list.push(rx);
- this.save();
- },
-
- debug : function(msg) {
- if (this._prefs.debugEnabled) {
- this._cout.logStringMessage('REDIRECTOR: ' + msg);
- }
- },
-
- deleteRedirectAt : function(index) {
- this._list.splice(index, 1);
- this.save();
- },
-
- exportRedirects : function(file) {
- var fileStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
- const PR_WRONLY = 0x02;
- const PR_CREATE_FILE = 0x08;
- const PR_TRUNCATE = 0x20;
-
- fileStream.init(file, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0644, 0);
- var stream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream);
- stream.init(fileStream, "UTF-8", 16384, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
- stream.writeString(this._redirectsAsString('\n'));
- stream.close();
- },
-
- getRedirectAt : function(index) {
- return this._list[index];
- },
-
- //Get the redirect url for the given url. This will not check if we are enabled, and
- //not do any verification on the url, just assume that it is a good string url that is for http/s
- getRedirectUrl : function(url) {
- this.debug("Checking " + url);
-
- for each (var redirect in this._list) {
- var result = redirect.getMatch(url);
- if (result.isExcludeMatch) {
- this.debug(url + ' matched exclude pattern ' + redirect.excludePattern + ' so the redirect ' + redirect.includePattern + ' will not be used');
- } else if (result.isDisabledMatch) {
- this.debug(url + ' matched pattern ' + redirect.includePattern + ' but the redirect is disabled');
- } else if (result.isMatch) {
- redirectUrl = this._makeAbsoluteUrl(url, result.redirectTo);
-
- //check for loops...
- result = redirect.getMatch(redirectUrl);
- if (result.isMatch) {
- var title = this._getString('invalidRedirectTitle');
- var msg = this._getFormattedString('invalidRedirectText', [redirect.includePattern, url, redirectUrl]);
- this.debug(msg);
- redirect.disabled = true;
- this.save();
- this._msgBox(title, msg);
- } else {
- this.debug('Redirecting ' + url + ' to ' + redirectUrl);
- return redirectUrl;
- }
- }
- }
- return null;
- },
-
- importRedirects : function(file) {
- var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
- fileStream.init(file, 0x01, 0444, 0); //TODO: Find the actual constants for these magic numbers
-
- var stream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream);
- stream.init(fileStream, "UTF-8", 16384, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
- stream = stream.QueryInterface(Ci.nsIUnicharLineInputStream);
-
- var importCount = 0, existsCount = 0;
- var lines = [];
- var line = {value: null};
- stream.readLine(line);
- while (line.value) {
- var redirect = new Redirect();
- redirect.deserialize(line.value.replace('\n', ''));
- if (this._containsRedirect(redirect)) {
- existsCount++;
- } else {
- this._list.push(redirect);
- importCount++;
- }
- stream.readLine(line);
- }
- stream.close();
- this.save();
- return importCount | (existsCount << 16);
- },
-
- reload : function() {
- loader.loadSubScript('chrome://redirector/content/code/redirector.prototype.js');
- loader.loadSubScript('chrome://redirector/content/code/redirect.js');
- var oldEnabled = this.enabled;
- for (var key in Redirector.prototype) {
- if (key != 'redirectCount' && key != 'enabled') {
- this[key] = Redirector.prototype[key];
- }
- }
- this._init();
- this.enabled = oldEnabled;
- },
-
- save : function() {
- this._prefs.redirects = this._redirectsAsString(':::');
- },
-
- switchItems : function(index1, index2) {
- var item = this._list[index1];
- this._list[index1] = this._list[index2];
- this._list[index2] = item;
- this.save();
- },
-
- //End rdIRedirector
-
- // nsIContentPolicy implementation
- shouldLoad: function(contentType, contentLocation, requestOrigin, aContext, mimeTypeGuess, extra) {
- rdump('nsIContentPolicy::ShouldLoad ' + contentLocation.spec);
- try {
- //This is also done in getRedirectUrl, but we want to exit as quickly as possible for performance
- if (!this._prefs.enabled) {
- return Ci.nsIContentPolicy.ACCEPT;
- }
-
- if (contentType != Ci.nsIContentPolicy.TYPE_DOCUMENT) {
- return Ci.nsIContentPolicy.ACCEPT;
- }
-
- if (contentLocation.scheme != "http" && contentLocation.scheme != "https") {
- return Ci.nsIContentPolicy.ACCEPT;
- }
-
- if (!aContext || !aContext.loadURI) {
- return Ci.nsIContentPolicy.ACCEPT;
- }
-
- var redirectUrl = this.getRedirectUrl(contentLocation.spec);
-
- if (!redirectUrl) {
- return Ci.nsIContentPolicy.ACCEPT;
- }
-
- aContext.loadURI(redirectUrl, requestOrigin, null);
- return Ci.nsIContentPolicy.REJECT_REQUEST;
- } catch(e) {
- this.debug(e);
- }
-
- },
-
- shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra) {
- return Ci.nsIContentPolicy.ACCEPT;
- },
- //end nsIContentPolicy
-
- //nsIChannelEventSink implementation
-
- //For FF4.0. Got this from a thread about adblock plus, https://adblockplus.org/forum/viewtopic.php?t=5895
- asyncOnChannelRedirect: function(oldChannel, newChannel, flags, redirectCallback) {
- this.onChannelRedirect(oldChannel, newChannel, flags);
- redirectCallback.onRedirectVerifyCallback(0);
- },
-
- onChannelRedirect: function(oldChannel, newChannel, flags)
- {
- try {
- let newLocation = newChannel.URI.spec;
- rdump('nsIChannelEventSink::onChannelRedirect ' + newLocation);
-
- if (!(newChannel.loadFlags & Ci.nsIChannel.LOAD_DOCUMENT_URI)) {
- //We only redirect documents...
- return;
- }
-
- if (!this._prefs.enabled) {
- return;
- }
-
- if (!newLocation) {
- return;
- }
- let callbacks = [];
- if (newChannel.notificationCallbacks) {
- callbacks.push(newChannel.notificationCallbacks);
- }
- if (newChannel.loadGroup && newChannel.loadGroup.notificationCallbacks) {
- callbacks.push(newChannel.loadGroup.notificationCallbacks);
- }
- var win;
- var webNav;
- for each (let callback in callbacks)
- {
- try {
- win = callback.getInterface(Ci.nsILoadContext).associatedWindow;
- webNav = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
- break;
- } catch(e) {}
- }
- if (!webNav) {
- return;
- }
- var redirectUrl = this.getRedirectUrl(newLocation);
-
- if (redirectUrl) {
- webNav.loadURI(redirectUrl,null,null,null,null);
- throw Cr.NS_BASE_STREAM_WOULD_BLOCK; //Throw this because the real error we should throw shows up in console...
- }
-
- } catch (e if (e != Cr.NS_BASE_STREAM_WOULD_BLOCK)) {
- // We shouldn't throw exceptions here - this will prevent the redirect.
- dump("Redirector: Unexpected error in onChannelRedirect: " + e + "\n");
- }
- },
- //end nsIChannelEventSink
-
- //Private members and methods
-
- _prefs : null,
- _list : null,
- _strings : null,
- _cout : Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService),
-
- _init : function() {
- if (this._prefs) {
- this._prefs.dispose();
- }
- this._cout.logStringMessage('REDIRECTOR CREATED');
- this._prefs = new RedirectorPrefs();
- //Check if we need to update existing redirects
- var data = this._prefs.redirects;
- var version = this._prefs.version;
- this._loadStrings();
- var currentVersion = '2.5';
- //Here update checks are handled
- if (version == 'undefined') { //Either a fresh install of Redirector, or first time install of v2.0
- if (data) { //There is some data in redirects, we are upgrading from a previous version, need to upgrade data
- var tempList = JSON.parse(data);
- var arr;
- var newArr = []
- for each (arr in tempList) {
- if (arr.length == 5) {
- arr.push(''); //For those that don't have an exclude pattern. Backwards compatibility is a bitch!
- }
- arr.splice(3,1); //Remove the "only if link exists" data
- newArr.push(arr.join(',,,'));
- }
- this._prefs.redirects = newArr.join(':::');
- }
- this._prefs.version = currentVersion;
- } else if (version == '2.0' || version == '2.0.1' || version == '2.0.2') {
- this._prefs.version = currentVersion;
- }
- //Update finished
-
- //Now get from the new format
- data = this._prefs.redirects;
- var arr;
- this._list = [];
- if (data != '') {
- for each (redirectString in data.split(':::')) {
- var redirect = new Redirect();
- redirect.deserialize(redirectString);
- this._list.push(redirect);
- }
- }
- },
-
- _loadStrings : function() {
- var src = 'chrome://redirector/locale/redirector.properties';
- var localeService = Cc["@mozilla.org/intl/nslocaleservice;1"].getService(Ci.nsILocaleService);
- var appLocale = localeService.getApplicationLocale();
- var stringBundleService = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
- this._strings = stringBundleService.createBundle(src, appLocale);
- },
-
- _redirectsAsString : function(seperator) {
- return [r.serialize() for each (r in this._list)].join(seperator);
- },
-
-
- _containsRedirect : function(redirect) {
- for each (var existing in this._list) {
- if (existing.equals(redirect)) {
- return true;
- }
- }
- return false;
- },
-
- _getString : function(name) {
- return this._strings.GetStringFromName(name);
- },
-
- _getFormattedString : function(name, params) {
- return this._strings.formatStringFromName(name, params, params.length);
- },
-
- _msgBox : function(title, text) {
- Cc["@mozilla.org/embedcomp/prompt-service;1"]
- .getService(Ci.nsIPromptService)
- .alert(null, title, text);
- },
-
- _makeAbsoluteUrl : function(currentUrl, relativeUrl) {
-
- if (relativeUrl.match(/https?:/)) {
- return relativeUrl;
- }
-
- var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
- var uri = ioService.newURI(currentUrl, null, null);
-
- return uri.resolve(relativeUrl);
- }
-};
+var EXPORTED_SYMBOLS = ['Redirector', 'rdump'];
+Ci = Components.interfaces;
+Cc = Components.classes;
+Ci = Components.interfaces;
+Cr = Components.results;
+
+Components.utils.import("chrome://redirector/content/code/redirect.js");
+Components.utils.import("chrome://redirector/content/code/redirectorprefs.js");
+
+function rdump(msg) {
+ dump(msg + '\n');
+}
+
+Redirector = {
+
+ get enabled() {
+ return this._prefs && this._prefs.enabled;
+ },
+
+ set enabled(value) {
+ if (this._prefs) {
+ this._prefs.enabled = value;
+ }
+ },
+
+ get redirectCount() {
+ return this._list.length;
+ },
+
+ toString : function() {
+ return "Redirector";
+ },
+
+ addRedirect : function(redirect) {
+ //This runaround is necessary because the redirect
+ //that was created somewhere up in the GUI doesn't
+ //have the Redirect function in scope.
+ var rx = new Redirect();
+ rx.copyValues(redirect);
+ this._list.push(rx);
+ this.save();
+ },
+
+ debug : function(msg) {
+ if (this._prefs.debugEnabled) {
+ this._cout.logStringMessage('REDIRECTOR: ' + msg);
+ }
+ },
+
+ deleteRedirectAt : function(index) {
+ this._list.splice(index, 1);
+ this.save();
+ },
+
+ exportRedirects : function(file) {
+ var fileStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+ const PR_WRONLY = 0x02;
+ const PR_CREATE_FILE = 0x08;
+ const PR_TRUNCATE = 0x20;
+
+ fileStream.init(file, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0644, 0);
+ var stream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream);
+ stream.init(fileStream, "UTF-8", 16384, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
+ stream.writeString(this._redirectsAsString('\n'));
+ stream.close();
+ },
+
+ getRedirectAt : function(index) {
+ return this._list[index];
+ },
+
+ //Get the redirect url for the given url. This will not check if we are enabled, and
+ //not do any verification on the url, just assume that it is a good string url that is for http/s
+ getRedirectUrl : function(url) {
+ this.debug("Checking " + url);
+
+ for each (var redirect in this._list) {
+ var result = redirect.getMatch(url);
+ if (result.isExcludeMatch) {
+ this.debug(url + ' matched exclude pattern ' + redirect.excludePattern + ' so the redirect ' + redirect.includePattern + ' will not be used');
+ } else if (result.isDisabledMatch) {
+ this.debug(url + ' matched pattern ' + redirect.includePattern + ' but the redirect is disabled');
+ } else if (result.isMatch) {
+ redirectUrl = this._makeAbsoluteUrl(url, result.redirectTo);
+
+ //check for loops...
+ result = redirect.getMatch(redirectUrl);
+ if (result.isMatch) {
+ var title = this._getString('invalidRedirectTitle');
+ var msg = this._getFormattedString('invalidRedirectText', [redirect.includePattern, url, redirectUrl]);
+ this.debug(msg);
+ redirect.disabled = true;
+ this.save();
+ this._msgBox(title, msg);
+ } else {
+ this.debug('Redirecting ' + url + ' to ' + redirectUrl);
+ return redirectUrl;
+ }
+ }
+ }
+ return null;
+ },
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIContentPolicy) || iid.equals(Ci.nsIChannelEventSink)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ importRedirects : function(file) {
+ var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
+ fileStream.init(file, 0x01, 0444, 0); //TODO: Find the actual constants for these magic numbers
+
+ var stream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream);
+ stream.init(fileStream, "UTF-8", 16384, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
+ stream = stream.QueryInterface(Ci.nsIUnicharLineInputStream);
+
+ var importCount = 0, existsCount = 0;
+ var lines = [];
+ var line = {value: null};
+ stream.readLine(line);
+ while (line.value) {
+ var redirect = new Redirect();
+ redirect.deserialize(line.value.replace('\n', ''));
+ if (this._containsRedirect(redirect)) {
+ existsCount++;
+ } else {
+ this._list.push(redirect);
+ importCount++;
+ }
+ stream.readLine(line);
+ }
+ stream.close();
+ this.save();
+ return importCount | (existsCount << 16);
+ },
+
+ reload : function() {
+ loader.loadSubScript('chrome://redirector/content/code/redirector.prototype.js');
+ loader.loadSubScript('chrome://redirector/content/code/redirect.js');
+ var oldEnabled = this.enabled;
+ for (var key in Redirector.prototype) {
+ if (key != 'redirectCount' && key != 'enabled') {
+ this[key] = Redirector.prototype[key];
+ }
+ }
+ this._init();
+ this.enabled = oldEnabled;
+ },
+
+ save : function() {
+ this._prefs.redirects = this._redirectsAsString(':::');
+ },
+
+ switchItems : function(index1, index2) {
+ var item = this._list[index1];
+ this._list[index1] = this._list[index2];
+ this._list[index2] = item;
+ this.save();
+ },
+
+
+ // nsIContentPolicy implementation
+ shouldLoad: function(contentType, contentLocation, requestOrigin, aContext, mimeTypeGuess, extra) {
+ if (contentLocation.scheme != "http" && contentLocation.scheme != "https") {
+ return Ci.nsIContentPolicy.ACCEPT;
+ } //Immediately, otherwise we will log all sorts of crap
+
+ rdump('nsIContentPolicy::ShouldLoad ' + contentLocation.spec);
+ try {
+ //This is also done in getRedirectUrl, but we want to exit as quickly as possible for performance
+ if (!this._prefs.enabled) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+
+ if (contentType != Ci.nsIContentPolicy.TYPE_DOCUMENT) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+
+ if (contentLocation.scheme != "http" && contentLocation.scheme != "https") {
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+
+ if (!aContext || !aContext.loadURI) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+
+ var redirectUrl = this.getRedirectUrl(contentLocation.spec);
+
+ if (!redirectUrl) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+
+ aContext.loadURI(redirectUrl, requestOrigin, null);
+ return Ci.nsIContentPolicy.REJECT_REQUEST;
+ } catch(e) {
+ this.debug(e);
+ }
+
+ },
+
+ shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+ //end nsIContentPolicy
+
+ //nsIChannelEventSink implementation
+
+ //For FF4.0. Got this from a thread about adblock plus, https://adblockplus.org/forum/viewtopic.php?t=5895
+ asyncOnChannelRedirect: function(oldChannel, newChannel, flags, redirectCallback) {
+ this.onChannelRedirect(oldChannel, newChannel, flags);
+ redirectCallback.onRedirectVerifyCallback(0);
+ },
+
+ onChannelRedirect: function(oldChannel, newChannel, flags)
+ {
+ try {
+ let newLocation = newChannel.URI.spec;
+ rrdump('nsIChannelEventSink::onChannelRedirect ' + newLocation);
+
+ if (!(newChannel.loadFlags & Ci.nsIChannel.LOAD_DOCUMENT_URI)) {
+ //We only redirect documents...
+ return;
+ }
+
+ if (!this._prefs.enabled) {
+ return;
+ }
+
+ if (!newLocation) {
+ return;
+ }
+ let callbacks = [];
+ if (newChannel.notificationCallbacks) {
+ callbacks.push(newChannel.notificationCallbacks);
+ }
+ if (newChannel.loadGroup && newChannel.loadGroup.notificationCallbacks) {
+ callbacks.push(newChannel.loadGroup.notificationCallbacks);
+ }
+ var win;
+ var webNav;
+ for each (let callback in callbacks)
+ {
+ try {
+ win = callback.getInterface(Ci.nsILoadContext).associatedWindow;
+ webNav = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
+ break;
+ } catch(e) {}
+ }
+ if (!webNav) {
+ return;
+ }
+ var redirectUrl = this.getRedirectUrl(newLocation);
+
+ if (redirectUrl) {
+ webNav.loadURI(redirectUrl,null,null,null,null);
+ throw Cr.NS_BASE_STREAM_WOULD_BLOCK; //Throw this because the real error we should throw shows up in console...
+ }
+
+ } catch (e if (e != Cr.NS_BASE_STREAM_WOULD_BLOCK)) {
+ // We shouldn't throw exceptions here - this will prevent the redirect.
+ rdump("Redirector: Unexpected error in onChannelRedirect: " + e + "\n");
+ }
+ },
+ //end nsIChannelEventSink
+
+ //Private members and methods
+
+ _prefs : null,
+ _list : null,
+ _strings : null,
+ _cout : Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService),
+
+ _init : function() {
+ if (this._prefs) {
+ this._prefs.dispose();
+ }
+ this._cout.logStringMessage('REDIRECTOR CREATED');
+ this._prefs = new RedirectorPrefs();
+ //Check if we need to update existing redirects
+ var data = this._prefs.redirects;
+ var version = this._prefs.version;
+ this._loadStrings();
+ var currentVersion = '2.5';
+ //Here update checks are handled
+ if (version == 'undefined') { //Either a fresh install of Redirector, or first time install of v2.0
+ if (data) { //There is some data in redirects, we are upgrading from a previous version, need to upgrade data
+ var tempList = JSON.parse(data);
+ var arr;
+ var newArr = []
+ for each (arr in tempList) {
+ if (arr.length == 5) {
+ arr.push(''); //For those that don't have an exclude pattern. Backwards compatibility is a bitch!
+ }
+ arr.splice(3,1); //Remove the "only if link exists" data
+ newArr.push(arr.join(',,,'));
+ }
+ this._prefs.redirects = newArr.join(':::');
+ }
+ this._prefs.version = currentVersion;
+ } else if (version == '2.0' || version == '2.0.1' || version == '2.0.2') {
+ this._prefs.version = currentVersion;
+ }
+ //Update finished
+
+ //Now get from the new format
+ data = this._prefs.redirects;
+ var arr;
+ this._list = [];
+ if (data != '') {
+ for each (redirectString in data.split(':::')) {
+ var redirect = new Redirect();
+ redirect.deserialize(redirectString);
+ this._list.push(redirect);
+ }
+ }
+ },
+
+ _loadStrings : function() {
+ var src = 'chrome://redirector/locale/redirector.properties';
+ var localeService = Cc["@mozilla.org/intl/nslocaleservice;1"].getService(Ci.nsILocaleService);
+ var appLocale = localeService.getApplicationLocale();
+ var stringBundleService = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
+ this._strings = stringBundleService.createBundle(src, appLocale);
+ },
+
+ _redirectsAsString : function(seperator) {
+ return [r.serialize() for each (r in this._list)].join(seperator);
+ },
+
+
+ _containsRedirect : function(redirect) {
+ for each (var existing in this._list) {
+ if (existing.equals(redirect)) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ _getString : function(name) {
+ return this._strings.GetStringFromName(name);
+ },
+
+ _getFormattedString : function(name, params) {
+ return this._strings.formatStringFromName(name, params, params.length);
+ },
+
+ _msgBox : function(title, text) {
+ Cc["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Ci.nsIPromptService)
+ .alert(null, title, text);
+ },
+
+ _makeAbsoluteUrl : function(currentUrl, relativeUrl) {
+
+ if (relativeUrl.match(/https?:/)) {
+ return relativeUrl;
+ }
+
+ var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ var uri = ioService.newURI(currentUrl, null, null);
+
+ return uri.resolve(relativeUrl);
+ }
+};
+
+Redirector._init(); \ No newline at end of file
diff --git a/chrome/code/redirectorprefs.js b/chrome/code/redirectorprefs.js
new file mode 100644
index 0000000..101a3f0
--- /dev/null
+++ b/chrome/code/redirectorprefs.js
@@ -0,0 +1,93 @@
+var EXPORTED_SYMBOLS = ['RedirectorPrefs'];
+
+function RedirectorPrefs() {
+ this.init();
+}
+
+RedirectorPrefs.prototype = {
+
+ //Preferences:
+ _version : null,
+ _enabled : null,
+ _showStatusBarIcon : null,
+ _showContextMenu : null,
+ _debugEnabled : null,
+ _defaultDir : null,
+ _redirects : null,
+
+ _prefBranch : null,
+
+ _listeners : null,
+
+ //Preferences props
+
+ get version() { return this._version; },
+ set version(value) { this._prefBranch.setCharPref('version', value); },
+
+ get enabled() { return this._enabled; },
+ set enabled(value) { this._prefBranch.setBoolPref('enabled', value); },
+
+ get showStatusBarIcon() { return this._showStatusBarIcon; },
+ set showStatusBarIcon(value) { this._prefBranch.setBoolPref('showStatusBarIcon', value); },
+
+ get showContextMenu() { return this._showContextMenu; },
+ set showContextMenu(value) { this._prefBranch.setBoolPref('showContextMenu', value); },
+
+ get debugEnabled() { return this._debugEnabled; },
+ set debugEnabled(value) { this._prefBranch.setBoolPref('debugEnabled', value); },
+
+ get defaultDir() { return this._defaultDir; },
+ set defaultDir(value) { this._prefBranch.setCharPref('defaultDir', value); },
+
+ get redirects() { return this._redirects; },
+ set redirects(value) { this._prefBranch.setCharPref('redirects', value); },
+
+ init : function() {
+ this._prefBranch = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("extensions.redirector.");
+ this.reload();
+ this._listeners = [];
+ this.service.addObserver('extensions.redirector', this, false);
+ },
+
+ dispose : function() {
+ this._listeners = null;
+ this.service.removeObserver('extensions.redirector', this);
+ },
+
+ reload : function() {
+ this._version = this._prefBranch.getCharPref('version');
+ this._enabled = this._prefBranch.getBoolPref('enabled');
+ this._showStatusBarIcon = this._prefBranch.getBoolPref('showStatusBarIcon');
+ this._showContextMenu = this._prefBranch.getBoolPref('showContextMenu');
+ this._debugEnabled = this._prefBranch.getBoolPref('debugEnabled');
+ this._defaultDir = this._prefBranch.getCharPref('defaultDir');
+ this._redirects = this._prefBranch.getCharPref('redirects');
+ },
+
+ get service() {
+ return Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranchInternal);
+ },
+
+ observe : function(subject, topic, data) {
+ if (topic != 'nsPref:changed') {
+ return;
+ }
+ this.reload();
+ for each (var listener in this._listeners) {
+ listener && listener.changedPrefs && listener.changedPrefs(this);
+ }
+ },
+
+ addListener : function(listener) {
+ this._listeners.push(listener);
+ },
+
+ removeListener : function(listener) {
+ for (var i = 0; i < this._listeners.length; i++) {
+ if (this._listeners[i] == listener) {
+ this._listeners.splice(i,1);
+ return;
+ }
+ }
+ },
+} \ No newline at end of file
diff --git a/chrome/code/settings.xul.js b/chrome/code/settings.xul.js
new file mode 100644
index 0000000..c7f780a
--- /dev/null
+++ b/chrome/code/settings.xul.js
@@ -0,0 +1,276 @@
+Components.utils.import("chrome://redirector/content/code/redirector.js");
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const nsLocalFile = Components.Constructor("@mozilla.org/file/local;1", "nsILocalFile", "initWithPath");
+
+var Settings = {
+
+ lstRedirects: null,
+ btnDelete : null,
+ btnEdit : null,
+ btnUp : null,
+ btnDown : null,
+ btnExport : null,
+ btnImport : null,
+ chkEnableRedirector : null,
+ chkShowStatusBarIcon : null,
+ chkShowContextMenu : null,
+ chkEnableDebugOutput : null,
+ prefs : null,
+
+ onLoad : function() {
+ try {
+ //Get references to controls
+ this.lstRedirects = document.getElementById('lstRedirects');
+ this.btnDelete = document.getElementById('btnDelete');
+ this.btnEdit = document.getElementById('btnEdit');
+ this.btnUp = document.getElementById('btnUp');
+ this.btnDown = document.getElementById('btnDown');
+ this.btnExport = document.getElementById('btnExport');
+ this.btnImport = document.getElementById('btnImport');
+ this.chkEnableRedirector = document.getElementById('chkEnableRedirector');
+ this.chkShowStatusBarIcon = document.getElementById('chkShowStatusBarIcon');
+ this.chkShowContextMenu = document.getElementById('chkShowContextMenu');
+ this.chkEnableDebugOutput = document.getElementById('chkEnableDebugOutput');
+
+ this.prefs = new RedirectorPrefs();
+ //Preferences
+ this.changedPrefs(this.prefs);
+ this.prefs.addListener(this);
+
+ //Redirect list
+ this.lstRedirects.selType = 'single';
+ this.template = document.getElementsByTagName('richlistitem')[0];
+ this.lstRedirects.removeChild(this.template);
+ var list = [];
+ for (var i = 0; i < Redirector.redirectCount; i++) {
+ list.push(Redirector.getRedirectAt(i));
+ }
+ this.addItemsToListBox(list);
+ this.selectionChange();
+
+ this.strings = document.getElementById('redirector-strings');
+ this.strings.getPluralized = function(id, number) {
+ id += number == 1 ? 'Singular' : '';
+ return this.getFormattedString(id, [number]);
+ };
+ } catch(e) {
+ alert(e);
+ }
+ },
+
+ onUnload : function() {
+ this.prefs.dispose();
+ },
+
+ changedPrefs : function(prefs) {
+ this.chkEnableRedirector.setAttribute('checked', prefs.enabled);
+ this.chkShowStatusBarIcon.setAttribute('checked', prefs.showStatusBarIcon);
+ this.chkShowContextMenu.setAttribute('checked', prefs.showContextMenu);
+ this.chkEnableDebugOutput.setAttribute('checked', prefs.debugEnabled);
+ },
+
+ addItemsToListBox : function(items) {
+
+ var item, row, value, newItem;
+
+ for each (item in items) {
+ newItem = this.template.cloneNode(true);
+
+ newItem.getElementsByAttribute('name', 'dscrIncludePattern')[0].setAttribute('value', item.includePattern);
+ newItem.getElementsByAttribute('name', 'dscrExcludePattern')[0].setAttribute('value', item.excludePattern);
+ newItem.getElementsByAttribute('name', 'dscrRedirectTo')[0].setAttribute('value', item.redirectUrl);
+ var checkEnabled = newItem.getElementsByAttribute('name', 'chkEnabled')[0];
+ checkEnabled.setAttribute('checked', !item.disabled);
+ newItem.setAttribute('class', item.disabled ? 'disabledRedirect' : '');
+ newItem.item = item;
+ this.lstRedirects.appendChild(newItem);
+ newItem.setAttribute('selected', false)
+ }
+
+ //Enable, disable functionality
+ this.lstRedirects.addEventListener('click', function(ev) {
+ if (ev.originalTarget && ev.originalTarget.tagName == 'checkbox') {
+ var parent = ev.originalTarget.parentNode;
+ while (!parent.item) {
+ parent = parent.parentNode;
+ }
+ parent.item.disabled = !ev.originalTarget.hasAttribute('checked');
+ parent.setAttribute('class', parent.item.disabled ? 'disabledRedirect' : '');
+ Redirector.save();
+ }
+ },false);
+ },
+
+ moveUp : function(){
+ if (this.lstRedirects.selectedIndex <= 0) {
+ return;
+ }
+ this.switchItems(this.lstRedirects.selectedIndex-1);
+ },
+
+ moveDown : function() {
+ if (this.lstRedirects.selectedIndex == Redirector.redirectCount-1) {
+ return;
+ }
+ this.switchItems(this.lstRedirects.selectedIndex);
+ },
+
+ switchItems : function(firstIndex) {
+ Redirector.switchItems(firstIndex, firstIndex+1);
+ var firstItem = this.lstRedirects.children[firstIndex];
+ var secondItem = this.lstRedirects.children[firstIndex+1];
+ this.lstRedirects.removeChild(secondItem);
+ this.lstRedirects.insertBefore(secondItem, firstItem);
+ this.selectionChange();
+ },
+
+ setListItemValues : function(listItem, item){
+ listItem.getElementsByAttribute('name', 'dscrIncludePattern')[0].setAttribute('value', item.includePattern);
+ listItem.getElementsByAttribute('name', 'dscrExcludePattern')[0].setAttribute('value', item.excludePattern);
+ listItem.getElementsByAttribute('name', 'dscrRedirectTo')[0].setAttribute('value', item.redirectUrl);
+ },
+
+ preferenceChange : function(event) {
+ this.prefs[event.originalTarget.getAttribute('preference')] = event.originalTarget.hasAttribute('checked');
+ },
+
+ addRedirect : function() {
+ var args = { saved : false, redirect : new Redirect() };
+ window.openDialog("chrome://redirector/content/ui/editRedirect.xul", "redirect", "chrome,dialog,modal,centerscreen", args);
+ if (args.saved) {
+ Redirector.addRedirect(args.redirect);
+ //Get it from redirector since it has processed it and it's no longer the same
+ //object as the one we added.
+ this.addItemsToListBox([Redirector.getRedirectAt(Redirector.redirectCount-1)]);
+ this.selectionChange();
+ }
+ },
+
+ editRedirect : function() {
+
+ if (this.lstRedirects.selectedIndex == -1) {
+ return;
+ }
+ //.selectedItem is still there after it has been removed, that's why we have the .selectedIndex check above as well
+ var listItem = this.lstRedirects.selectedItem;
+ if (!listItem) {
+ return;
+ }
+ var redirect = listItem.item;
+ var args = { saved: false, "redirect":redirect.clone()};
+ window.openDialog("chrome://redirector/content/ui/editRedirect.xul", "redirect", "chrome,dialog,modal,centerscreen", args);
+
+ if (args.saved) {
+ redirect.copyValues(args.redirect);
+ this.setListItemValues(listItem, redirect);
+ Redirector.save();
+ this.selectionChange();
+ }
+ },
+
+ deleteRedirect : function() {
+ var index = this.lstRedirects.selectedIndex;
+
+ if (index == -1) {
+ return;
+ }
+
+ var text = this.strings.getString("deleteConfirmationText");
+ var title = this.strings.getString("deleteConfirmationTitle");
+ var reallyDelete = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService).confirm(null, title, text);
+ if (!reallyDelete) {
+ return;
+ }
+
+ try {
+ this.lstRedirects.removeChild(this.lstRedirects.children[index]);
+ Redirector.deleteRedirectAt(index);
+ this.selectionChange();
+ } catch(e) {
+ alert(e);
+ }
+ },
+
+ listKeypress : function(event) {
+ if (event.keyCode == 13) { //Enter button
+ this.editRedirect();
+ } else if (event.keyCode == 46) { //Del button
+ this.deleteRedirect();
+ }
+ },
+
+ selectionChange : function() {
+ if (!this.lstRedirects) {
+ return;
+ }
+ var index = this.lstRedirects.selectedIndex;
+
+ this.btnEdit.disabled = (index == -1);
+ this.btnDelete.disabled = (index == -1);
+ this.btnUp.disabled = (index <= 0);
+ this.btnDown.disabled = (index == -1 || index >= Redirector.redirectCount-1);
+ this.btnExport.disabled = (Redirector.redirectCount== 0);
+ },
+
+ getFile : function(captionKey, mode) {
+ //Mostly borrowed from Adblock Plus
+ var picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ picker.init(window, this.strings.getString(captionKey), mode);
+ picker.defaultExtension = ".rdx";
+ var dir = this.prefs.defaultDir;
+ if (dir) {
+ picker.displayDirectory = new nsLocalFile(dir);
+ }
+ picker.appendFilter(this.strings.getString('redirectorFiles'), '*.rdx');
+
+ if (picker.show() == picker.returnCancel) {
+ return null;
+ }
+ this.prefs.defaultDir = picker.displayDirectory.path;
+ return picker.file;
+ },
+
+ export : function() {
+ var file = this.getFile('exportCaption', Ci.nsIFilePicker.modeSave);
+ if (file) {
+ Redirector.exportRedirects(file);
+ }
+ },
+
+ import : function() {
+ var file = this.getFile('importCaption', Ci.nsIFilePicker.modeOpen);
+ var result;
+ if (!file) {
+ return;
+ }
+ result = Redirector.importRedirects(file);
+ var msg, imported, existed;
+ imported = result & 0xFFFF;
+ existed = result >> 16;
+
+ if (imported > 0) {
+ msg = this.strings.getPluralized('importedMessage', imported);
+ if (existed > 0) {
+ msg += ', ' + this.strings.getPluralized('existedMessage',existed);
+ } else {
+ msg += '.';
+ }
+ } else if (imported == 0 && existed > 0) {
+ msg = this.strings.getPluralized('allExistedMessage', existed);
+ } else { //Both 0
+ msg = this.strings.getString('importedNone');
+ }
+
+ var title = this.strings.getString("importResult");
+ Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService).alert(null, title, msg);
+
+ if (imported > 0) {
+ var newlist = [];
+ for (var i = Redirector.redirectCount-imported; i < Redirector.redirectCount; i++) {
+ newlist.push(Redirector.getRedirectAt(i));
+ }
+ this.addItemsToListBox(newlist);
+ }
+ }
+};
diff --git a/chrome/code/unittest/run.html b/chrome/code/unittest/run.html
new file mode 100644
index 0000000..cc178aa
--- /dev/null
+++ b/chrome/code/unittest/run.html
@@ -0,0 +1,99 @@
+<!-- $Id$ -->
+<html>
+ <head>
+ <title>Redirector Unit Tests</title>
+ <style type="text/css">
+ body { font-family: Verdana, Arial; color:black; background-color:white; font-size:0.8em; width:800px; margin:auto; text-align:center;}
+ a { color:blue; }
+ h1 { text-align:center; margin:10px 0px; }
+ table { margin:10px auto; border:solid 1px black; width:700px; border-collapse:collapse;}
+ td { border:solid 1px black; padding:3px; }
+ td.result { width:20px; height:20px; padding:0;}
+ td.result div { width:70%; height:70%; margin:auto; }
+ button { margin:20px auto; }
+ </style>
+ <script type="text/javascript">
+
+ //Global variables
+ var subscriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader);
+ var redirector = Components.classes["@einaregilsson.com/redirector;1"].getService(Components.interfaces.rdIRedirector);
+
+ function setupTest(name, testcase) {
+ var table = document.createElement('table');
+ var row = document.createElement('tr');
+ var cell = document.createElement('th');
+ var testdata;
+ cell.setAttribute('colspan', 2);
+ row.appendChild(cell);
+ table.appendChild(row);
+ cell.innerHTML = name;
+ document.getElementById('tests').appendChild(table);
+ for (var i = 0; i < testcase.tests.length; i++) {
+ var testdata = testcase.tests[i];
+ row = document.createElement('tr');
+ cell = document.createElement('td');
+ cell.setAttribute('class', 'result');
+ var dot = document.createElement('div');
+ dot.setAttribute('id', name + '_' + i);
+ cell.appendChild(dot);
+
+ row.appendChild(cell);
+ cell = document.createElement('td');
+ cell.innerHTML = testcase.describe(testdata);
+ row.appendChild(cell);
+ table.appendChild(row);
+ }
+ }
+
+ function setup() {
+ //quick and dirty cleanup
+ document.getElementById('tests').innerHTML = '';
+ subscriptLoader.loadSubScript('chrome://redirector/content/code/redirect.js');
+ subscriptLoader.loadSubScript('chrome://redirector/content/unittest/testcases.js');
+ redirector.reload();
+
+ var sorted = [];
+ for (var name in tests) {
+ sorted.push(name);
+ }
+
+ sorted.sort();
+ for each(var name in sorted) {
+ setupTest(name, tests[name]);
+ }
+ }
+
+ function runTests() {
+ for (var testcaseName in tests) {
+ var testcase = tests[testcaseName];
+ for (var i = 0; i < testcase.tests.length; i++) {
+ try {
+ var dot = document.getElementById(testcaseName + '_' + i);
+ var result = testcase.run(testcase.tests[i]);
+ if (result && result.passed) {
+ dot.style.backgroundColor = '#17f816';
+ } else {
+ dot.style.backgroundColor = '#ff0000';
+ if (result && result.message) {
+ dot.parentNode.nextSibling.innerHTML += '<br/><span style="color:red;">' + result.message + '</span>';
+ }
+ }
+ } catch(e) {
+ dot.style.backgroundColor = '#ff0000';
+ dot.parentNode.nextSibling.innerHTML += '<br/><span style="color:red;">' + e + '</span>';
+ ;
+ }
+ }
+ }
+ }
+
+ </script>
+ </head>
+ <body onload="setup();">
+ <h1>Redirector Unit Tests</h1>
+ <button onclick="runTests();">Run tests</button>
+ <button onclick="setup();">Reload tests</button>
+ <div id="tests">
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/chrome/code/unittest/testcases.js b/chrome/code/unittest/testcases.js
new file mode 100644
index 0000000..0c0c10c
--- /dev/null
+++ b/chrome/code/unittest/testcases.js
@@ -0,0 +1,129 @@
+//// $Id$
+var nsIContentPolicy = Components.interfaces.nsIContentPolicy;
+
+var tests = {
+ "Wildcard matches" : {
+ run : function(data,log) {
+ var pattern = data[0],
+ url = data[1],
+ expected = data[2];
+ var parts = expected.split(',');
+ var redirectUrl = '';
+ if (!(parts.length == 1 && parts[0] == '')) {
+ for (var i in parts) {
+ redirectUrl += '$' + (parseFloat(i)+1) + ',';
+ }
+ redirectUrl = redirectUrl.substr(0, redirectUrl.length-1);
+ }
+ var redirect = new Redirect(null, pattern, redirectUrl, Redirect.WILDCARD);
+ var result = redirect.getMatch(url);
+ return { passed: result.isMatch && (result.redirectTo == expected), message : "Expected '" + expected + "', actual was '" + result.redirectTo + "'"};
+ },
+
+ describe : function(data) { return data[0] + ' == ' + data[1] + ', matches=' + data[2]; },
+ tests : [
+ ['http://foo*', 'http://foobar.is', 'bar.is'],
+ ['http://foo*', 'http://foo', ''],
+ ['*://foo.is', 'http://foo.is', 'http'],
+ ['*http://foo.is', 'http://foo.is', ''],
+ ['http*foo*', 'http://foobar.is', '://,bar.is'],
+ ['http*foo*', 'http://foo', '://,'],
+ ['*://f*.is', 'http://foo.is', 'http,oo'],
+ ['*http://f*.is', 'http://foo.is', ',oo'],
+ ['*foo*', 'http://foo', 'http://,'],
+ ['*foo*', 'foobar.is', ',bar.is'],
+ ['*foo*', 'http://foobar.is', 'http://,bar.is'],
+ ['http://foo.is', 'http://foo.is', ''],
+ ['*', 'http://foo.is', 'http://foo.is'],
+ ['*://*oo*bar*', 'http://foo.is/bar/baz', 'http,f,.is/,/baz'],
+ ['*://**oo*bar*', 'http://foo.is/bar/baz', 'http,,f,.is/,/baz'],
+ ]
+ },
+
+ "Regex matches" : {
+ run : function(data) {
+ var pattern = data[0],
+ url = data[1],
+ expected = data[2];
+ var parts = expected.split(',');
+ var redirectUrl = '';
+ if (!(parts.length == 1 && parts[0] == '')) {
+ for (var i in parts) {
+ redirectUrl += '$' + (parseFloat(i)+1) + ',';
+ }
+ redirectUrl = redirectUrl.substr(0, redirectUrl.length-1);
+ }
+ var redirect = new Redirect(null, pattern, redirectUrl, Redirect.REGEX, null, null);
+ var result = redirect.getMatch(url);
+ return { passed: result.isMatch && result.redirectTo == expected, message : "Expected '" + expected + "', actual was '" + result.redirectTo + "'"};
+ },
+
+ describe : function(data) { return data[0] + ' == ' + data[1] + ', matches=' + data[2]; },
+ tests : [
+ ['http://foo(.*)', 'http://foobar.is', 'bar.is'],
+ ['http://foo(.*)', 'http://foo', ''],
+ ['(.*)://foo.is', 'http://foo.is', 'http'],
+ ['(.*)http://foo\\.is', 'http://foo.is', ''],
+ ['http(.*)foo(.*)', 'http://foobar.is', '://,bar.is'],
+ ['http(.*)foo(.*)', 'http://foo', '://,'],
+ ['(.*)://f(.*)\\.is', 'http://foo.is', 'http,oo'],
+ ['(.*)http://f(.*)\\.is', 'http://foo.is', ',oo'],
+ ['(.*)foo(.*)', 'http://foo', 'http://,'],
+ ['(.*)foo(.*)', 'foobar.is', ',bar.is'],
+ ['(.*)foo(.*)', 'http://foobar.is', 'http://,bar.is'],
+ ['http://foo\.is', 'http://foo.is', ''],
+ ['(.*)', 'http://foo.is', 'http://foo.is'],
+ ['(.*)://(.*)oo(.*)bar(.*)', 'http://foo.is/bar/baz', 'http,f,.is/,/baz'],
+ ['(.*)://(.*?)(.*)oo(.*)bar(.*)', 'http://foo.is/bar/baz', 'http,,f,.is/,/baz'],
+ ]
+ },
+
+ "nsIContentPolicy implementation" : {
+ run : function(data) {
+ var runTest = function() {
+ var args = {
+ contentType : nsIContentPolicy.TYPE_DOCUMENT,
+ contentLocation : "http://foo.is",
+ requestOrigin : null,
+ aContext : { loadURI : function(){}},
+ mimeTypeGuess : null,
+ extra : null
+ };
+ for (var key in data[1]) {
+ args[key] = data[1][key];
+ }
+
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
+ args.contentLocation = ioService.newURI(args.contentLocation, null, null);
+ var contentPolicy = redirector.QueryInterface(nsIContentPolicy);
+ var result = contentPolicy.shouldLoad(args.contentType, args.contentLocation, args.requestOrigin, args.aContext, args.mimeTypeGuess, args.extra);
+ return { passed: result == nsIContentPolicy.ACCEPT, message : "Expected nsIContentPolicy.ACCEPT, actual was " + result };
+ }
+
+ if (typeof data[2] == "function") {
+ return data[2](runTest);
+ } else {
+ return runTest();
+ }
+ },
+
+ describe : function(data) { return data[0]; },
+ tests : [
+ ["Accepts if not TYPE_DOCUMENT", { contentType : nsIContentPolicy.TYPE_STYLESHEET}],
+ ["Accepts if not http or https", { contentLocation : "resource://foo/bar"}],
+ ["Accepts if no aContext", { aContext : null}],
+ ["Accepts if aContext has no loadURI function", { aContext : { foo : function(){}}}],
+ ["Accepts if Redirector is not enabled", {}, function(doFunc) {
+ try {
+ redirector.enabled = false;
+ return doFunc();
+ redirector.enabled = true;
+
+ } catch(e) {
+ redirector.enabled = true;
+ throw e;
+ }
+ }]
+ ]
+ }
+};
diff --git a/chrome/ui/browserOverlay.xul b/chrome/ui/browserOverlay.xul
new file mode 100644
index 0000000..32bf1d5
--- /dev/null
+++ b/chrome/ui/browserOverlay.xul
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- $Id$ -->
+<!DOCTYPE overlay SYSTEM "chrome://redirector/locale/browserOverlay.xul.dtd">
+<overlay id="redirector-overlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="../code/redirect.js"/>
+ <script src="../code/redirectorprefs.js"/>
+ <script src="../code/browserOverlay.xul.js"/>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="redirector-strings" src="chrome://redirector/locale/redirector.properties"/>
+ </stringbundleset>
+
+ <menupopup id="menu_ToolsPopup">
+ <menuitem id="redirector-menuitem" label="&RedirectorMenuItem.label;"
+ accesskey="&RedirectorMenuItem.accesskey;"
+ oncommand="RedirectorOverlay.onMenuItemCommand(event);"/>
+ </menupopup>
+ <popup id="contentAreaContextMenu">
+ <menuitem id="redirector-context" label="&RedirectorContext.label;"
+ accesskey="&RedirectorContext.accesskey;"
+ insertafter="context-stop"
+ oncommand="RedirectorOverlay.onContextMenuCommand(event)"/>
+ </popup>
+ <statusbar id="status-bar">
+ <statusbarpanel id="redirector-status">
+ <image id="redirector-statusbar-img" src="chrome://redirector/skin/statusactive.png"
+ tooltiptext="Redirector is enabled;"
+ style="width:16px; height:16px;"
+ onclick="RedirectorOverlay.statusBarClick(event);" />
+ </statusbarpanel>
+ </statusbar>
+
+</overlay> \ No newline at end of file
diff --git a/chrome/ui/editRedirect.xul b/chrome/ui/editRedirect.xul
new file mode 100644
index 0000000..6d7b133
--- /dev/null
+++ b/chrome/ui/editRedirect.xul
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- $Id$ -->
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://redirector/skin/redirector.css" type="text/css"?>
+<!DOCTYPE dialog SYSTEM "chrome://redirector/locale/editRedirect.xul.dtd">
+<dialog title="&redirectWindow.title;"
+ orient="vertical"
+ autostretch="always"
+ onload="EditRedirect.onLoad();"
+ buttons="accept,cancel"
+ ondialogaccept="return EditRedirect.onAccept();"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/x-javascript" src="../code/redirect.js"/>
+ <script type="application/x-javascript" src="../code/editRedirect.xul.js"/>
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="redirector-strings" src="chrome://redirector/locale/redirector.properties"/>
+ </stringbundleset>
+
+ <grid>
+ <rows class="editRedirects">
+ <row align="center">
+ <text value="&txtExampleUrl.label;" />
+ <textbox id="txtExampleUrl" />
+ </row>
+ <row align="center">
+ <text value="&txtIncludePattern.label;" />
+ <textbox id="txtIncludePattern" taborder="1"/>
+ <button id="btnTestPattern" label="&btnTestPattern.label;" onclick="EditRedirect.testPattern();" taborder="2"/>
+ </row>
+ <row align="center">
+ <text value="&txtExcludePattern.label;" />
+ <textbox id="txtExcludePattern" taborder="3"/>
+ </row>
+ <row align="center">
+ <text value="&txtRedirectUrl.label;" />
+ <textbox id="txtRedirectUrl" taborder="4"/>
+ </row>
+ <row align="center">
+ <text value="&rdoPatternTypes.label;"/>
+ <radiogroup>
+ <hbox>
+ <radio id="rdoWildcard" label="&rdoWildcard.label;" accesskey="&rdoWildcard.accessKey;" selected="true" taborder="5"/>
+ <radio id="rdoRegex" label="&rdoRegex.label;" accesskey="&rdoRegex.accessKey;" taborder="6"/>
+ </hbox>
+ </radiogroup>
+ </row>
+ <row align="center">
+ <text value="&chkUnescapeMatches.label;" />
+ <hbox>
+ <checkbox id="chkUnescapeMatches" label="" taborder="7"/>
+ <spacer flex="1" />
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+</dialog>
diff --git a/chrome/ui/help.html b/chrome/ui/help.html
new file mode 100644
index 0000000..4534d96
--- /dev/null
+++ b/chrome/ui/help.html
@@ -0,0 +1,182 @@
+<!-- $Id$ -->
+<html>
+ <head>
+ <title>Redirector Help</title>
+ <style type="text/css">
+ body { font-family: Verdana, Arial; color:black; background-color:white; font-size:0.9em;}
+ a { color:blue; }
+ </style>
+ </head>
+ <body>
+ <h1>Redirector Help</h1>
+ <h3>Table of contents</h3>
+ <ul>
+ <li><a href="#whatisredirector">What is Redirector?</a></li>
+ <li><a href="#basicusage">Basic usage</a>
+ <ul>
+ <li><a href="#exampleurl">Example url</a></li>
+ <li><a href="#includepattern">Include pattern</a></li>
+ <li><a href="#excludepattern">Exclude pattern</a></li>
+ <li><a href="#redirectto">Redirect to</a></li>
+ <li><a href="#patterntype">Pattern type</a></li>
+ <li><a href="#unescapematches">Unescape matches</a></li>
+ </ul>
+ </li>
+ <li><a href="#wildcards">Wildcards</a></li>
+ <li><a href="#regularexpressions">Regular expressions</a></li>
+ <li><a href="#examples">Examples</a>
+ <ol>
+ <li><a href="#ex1">Static redirect</a></li>
+ <li><a href="#ex2">Redirect using query string parameter and wildcards</a></li>
+ <li><a href="#ex3">Redirect using query string parameter and regular expressions</a></li>
+ <li><a href="#ex4">Redirect to a different folder using wildcards</a></li>
+ <li><a href="#ex5">Redirect http to https using wildcards</a></li>
+ </ol>
+
+ </li>
+ </ul>
+
+
+ <a name="whatisredirector"></a>
+ <h4>What is Redirector?</h4>
+
+ <p>Redirector is an extension for Firefox that allows you to automatically redirect from
+ one webpage to another. For example, every time you visit http://abc.com you will automatically
+ load http://def.com instead. This can be useful for instance to always redirect articles to printer friendly
+ versions, redirect http:// to https:// for sites that support both, bypass advertising pages that appear before
+ being able to view certain pages and more.</p>
+
+ <a name="basicusage"></a>
+ <h4>Basic usage</h4>
+ <p>To add a new redirect you can go to the <em>Tools</em> menuitem and select <em>Redirector</em>. That will
+ open the <em>Redirector settings</em> window which shows all your redirects. The window can also be opened
+ by right clicking on the <strong>R</strong> icon in your statusbar.
+ There you can press the <em>Add...</em> button and then you can enter the details for the new redirect. A redirect
+ consists of a few things:
+ <ul>
+ <li><a name="exampleurl"></a><strong>Example url:</strong> This is an example of an url you want to redirect. It is not really used for anything,
+ it's just there to show what types of urls you're targetting. You can leave this out, but then you can't use the <em>Test pattern</em> button.</li>
+
+ <li><a name="includepattern"></a><strong>Include pattern:</strong> This is pattern for the urls you want to redirect. In the simplest case, where you just want
+ to redirect one specific url to another then this will just be the exact url you want to redirect. For instance, if you just want http://aaa.com to
+ redirect to http://bbb.com then <em>Include pattern</em> will just be http://aaa.com. For more complex patterns that match many
+ urls you can use either <a href="#wildcards">wildcards</a> or <a href="#regularexpressions">regular expressions</a>.</li>
+
+ <li><a name="excludepattern"></a><strong>Exclude pattern:</strong> Urls that match this pattern will never be redirected. This is not necessary to
+ fill out, but can be useful when you want to redirect all urls that contain some text except if they contain some other text.
+ Redirects like that can often be done with a complex regular expression, but using an exclude pattern makes it much simpler. The exclude
+ patterns can use wildcard characters or regular expressions like the include patterns.</li>
+
+ <li><a name="redirectto"></a><strong>Redirect to:</strong> This is the url that you will be redirected to when you open any page where the url matches the
+ include pattern. You can use the special signs $1, $2, $3 etc. in the url, they will be replaced by the results of captures with regular
+ expressions or stars with wildcards. For instance, if you have the include pattern <em>http://google.com/*</em>, redirect to <em>http://froogle.com/$1</em>
+ and you open the page http://google.com/foobar, then you will be redireced to http://froogle.com/foobar, since 'foobar' was what the star replaced. $1 is for the
+ first star in the pattern, $2 for the second and so on. For regular expression $1 is for the first parantheses, $2 for the second etc.</li>
+
+ <li><a name="patterntype"></a><strong>Pattern type:</strong> This specifies how Redirector should interpret the patterns, either as
+ <a href="#wildcards">wildcards</a> or <a href="#regularexpressions#">regular expressions</a>.</li>
+
+ <li><a name="unescapematches"></a><strong>Unescape matches:</strong> A common usage of Redirector is to catch urls like
+ <em>http://foo.com/redirect.php?url=http%3A%2F%2Fbar%2Ecom%2Fpath</em> and try to catch the url parameter and redirect to it. A pattern
+ like <em>http://foo.com/redirect.php?url=*</em> might be used for that purpose. However, if the url parameter is <em>escaped</em> (also known
+ as <em>urlencoded</em>) then that won't work. In the url above we see that it starts with <em>http%3A%2F%2F</em> instead of <em>http://</em>, and Firefox
+ won't accept this as a new url to redirect to. So, in cases like these you can check the <em>Unescape matches</em> option and then all
+ matches will be unescaped (turned from e.g. <em>http%3A%2F%2Fbar%2Ecom</em> to <em>http://bar.com</em>) before being inserted into the target url.
+ </li>
+
+ </ul>
+ </p>
+
+ <a name="wildcards"></a>
+ <h4>Wildcards</h4>
+
+ <p>Wildcards are the simplest way to specify include and exclude patterns. When you create a wildcard pattern there
+ is just one special character, the asterisk *. An asterisk in your pattern will match zero or more characters and you can
+ have more than one star in your pattern. Some examples:
+ <ul>
+ <li><em>http://example.com/*</em> matches http://example.com/, http://example.com/foo, http://example.com/bar and all other urls that start with http://example.com/.</li>
+ <li><em>http://*.example.com</em> matches all subdomains of example.com, like http://www.example.com, http://mail.example.com.</li>
+ <li><em>http*://example.com</em> matches both http://example.com and https://example.com.</li>
+ <li><em>http://example.com/index.asp*</em> matches http://example.com/index.asp, http://example.com/index.asp?a=b&c=d.</li>
+ </ul>
+ $1, $2, $3 in the redirect urls will match the text that the stars matched. Examples:
+ <ul>
+ <li><em>http://example.com/*</em> matches http://example.com/foobar, $1 is foobar.</li>
+ <li><em>http://*.example.com/*</em> matches http://www.example.com/foobar, $1 is www, $2 is foobar.</li>
+ </ul>
+ </p>
+
+ <a name="regularexpressions"></a>
+ <h4>Regular expressions</h4>
+
+ <p>Regular expressions allow for more complicated patterns but they are a lot harder to learn than wildcards. I'm not gonna
+ create a regex tutorial here but normal javascript regex syntax works, look at <a href="http://regular-expressions.info" target="_blank">http://regular-expressions.info</a> for
+ an introduction to regular expressions. $1,$2 etc. can be used in the redirect url and will be replaced with contents of captures in
+ the regular expressions. Captures are specified with parantheses. Example: http://example.com/index.asp\?id=(\d+) will match the url
+ http://example.com/index.asp?id=12345 and $1 will be replaced by 12345. (A common mistake in regex patterns is to forget to escape
+ the ? sign in the querystring of the url. ? is a special character in regular expressions so if you want to match an url with a querystring
+ you should escape it as \?).</p>
+
+ <a name="examples"></a>
+ <h4>Examples</h4>
+
+ <ol>
+ <li>
+ <strong><a name="ex1"></a>Static redirect</strong><br/>
+ Redirects from http://example.com/foo to http://example.com/bar
+ <p>
+ <strong>Include pattern:</strong> http://example.com/foo<br/>
+ <strong>Exclude pattern:</strong><br/>
+ <strong>Redirect to:</strong> http://example.com/bar<br/>
+ <strong>Pattern type:</strong> Wildcard<br />
+ </p>
+
+ </li>
+ <li>
+ <strong><a name="ex2"></a>Redirect using query string parameter and wildcards</strong><br/>
+ Redirects from http://example.com/index.php?id=12345&a=b to http://example.com/printerfriendly.php?id=12345&a=b
+ where 12345 could be any number.
+ <p>
+ <strong>Include pattern:</strong> http://example.com/index.php?id=*&a=b<br/>
+ <strong>Exclude pattern:</strong><br/>
+ <strong>Redirect to:</strong> http://example.com/printerfriendly.com?id=$1&a=b<br/>
+ <strong>Pattern type:</strong> Wildcard<br />
+ </p>
+ </li>
+ <li>
+ <strong><a name="ex3"></a>Redirect using query string parameter and regular expressions</strong><br/>
+ Redirects from http://example.com/index.php?id=12345&a=b to http://example.com/printerfriendly.php?id=12345&a=b
+ where 12345 could be any number.
+ <p>
+ <strong>Include pattern:</strong> http://example.com/index.php\?id=(\d+)&a=b<br/>
+ <strong>Exclude pattern:</strong><br/>
+ <strong>Redirect to:</strong> http://example.com/printerfriendly.com?id=$1&a=b<br/>
+ <strong>Pattern type:</strong> Regular Expression<br />
+ </p>
+ </li>
+ <li>
+ <strong><a name="ex4"></a>Redirect to a different folder using wildcards</strong><br/>
+ Redirects from http://example.com/category/fish/index.php to http://example.com/category/cats/index.php
+ where fish could be any word. The exclude pattern makes sure that there is only one
+ folder there, so for instance http://example.com/category/fish/cat/mouse/index.php would not match.
+ <p>
+ <strong>Include pattern:</strong> http://example.com/category/*/index.php<br/>
+ <strong>Exclude pattern:</strong> http://example.com/category/*/*/index.php<br/>
+ <strong>Redirect to:</strong> http://example.com/category/cats/index.php<br/>
+ <strong>Pattern type:</strong> Wildcard<br />
+ </p>
+ </li>
+ <li>
+ <strong><a name="ex5"></a>Redirect http to https using wildcards</strong><br/>
+ Redirects from http://mail.google.com/randomcharacters to https://mail.google.com/randomcharacters
+ where randomcharacters could be anything.
+ <p>
+ <strong>Include pattern:</strong> http://mail.google.com*<br/>
+ <strong>Exclude pattern:</strong><br/>
+ <strong>Redirect to:</strong> https://mail.google.com$1<br/>
+ <strong>Pattern type:</strong> Wildcard<br />
+ </p>
+ </li>
+ </ol>
+ </body>
+</html> \ No newline at end of file
diff --git a/chrome/ui/settings.xul b/chrome/ui/settings.xul
new file mode 100644
index 0000000..ccb9569
--- /dev/null
+++ b/chrome/ui/settings.xul
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- $Id$ -->
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://redirector/skin/redirector.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://redirector/locale/settings.xul.dtd">
+<window title="&window.title;"
+ orient="vertical"
+ onload="Settings.onLoad();"
+ onunload="Settings.onUnload();"
+ buttons="accept"
+ width="650px"
+ height="500px"
+ id="redirectorSettings"
+ windowtype="redirectorSettings"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/x-javascript" src="../code/redirectorprefs.js"/>
+ <script type="application/x-javascript" src="../code/redirect.js"/>
+ <script type="application/x-javascript" src="../code/settings.xul.js"/>
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="redirector-strings" src="chrome://redirector/locale/redirector.properties"/>
+ </stringbundleset>
+ <tabbox flex="1" >
+ <tabs>
+ <tab label="&tabRedirects.label;" accesskey="&tabRedirects.accesskey;" />
+ <tab label="&tabPreferences.label;" accesskey="&tabPreferences.accesskey;" />
+ <tab label="&tabImportExport.label;" accesskey="&tabImportExport.accesskey;" />
+ <tab label="&tabHelp.label;" accesskey="&tabHelp.accesskey;" />
+ </tabs>
+ <tabpanels flex="1">
+ <tabpanel flex="1">
+ <vbox flex="1">
+ <richlistbox seltype="single" id="lstRedirects" flex="1" onkeypress="Settings.listKeypress(event);" ondblclick="Settings.editRedirect();" onselect="Settings.selectionChange();">
+ <richlistitem class="SettingsItem" selected="false">
+ <grid>
+ <cols />
+ <rows class="redirectRows">
+ <row>
+ <label value="&colIncludePattern.label;:" />
+ <description name="dscrIncludePattern" />
+ </row>
+ <row>
+ <label value="&colExcludePattern.label;:" />
+ <description name="dscrExcludePattern" />
+ </row>
+ <row>
+ <label value="&colRedirectTo.label;:" />
+ <description name="dscrRedirectTo" />
+ </row>
+ <row>
+ <label value="&colEnabled.label;:" />
+ <hbox><checkbox checked="false" name="chkEnabled" label="" /> <spacer flex="1"/></hbox>
+ </row>
+ </rows>
+ </grid>
+ </richlistitem>
+ </richlistbox>
+ <hbox>
+ <button id="btnAdd" oncommand="Settings.addRedirect();" accesskey="&btnAdd.accesskey;" label="&btnAdd.label;" tooltiptext="&btnAdd.tooltip;" disabled="false" />
+ <button id="btnEdit" oncommand="Settings.editRedirect();" accesskey="&btnEdit.accesskey;" label="&btnEdit.label;" tooltiptext="&btnEdit.tooltip;" disabled="true" />
+ <button id="btnDelete" oncommand="Settings.deleteRedirect();" accesskey="&btnDelete.accesskey;" label="&btnDelete.label;" tooltiptext="&btnDelete.tooltip;" disabled="true" />
+ <button id="btnUp" oncommand="Settings.moveUp();" tooltiptext="&btnUp.tooltip;" disabled="true" />
+ <button id="btnDown" oncommand="Settings.moveDown();" tooltiptext="&btnDown.tooltip;" disabled="true" />
+ </hbox>
+ </vbox>
+ </tabpanel>
+ <tabpanel>
+ <vbox flex="1">
+ <groupbox>
+ <caption label="&grpGeneralPreferences.label;" />
+ <hbox>
+ <checkbox id="chkEnableRedirector" label="&chkEnableRedirector.label;" oncommand="Settings.preferenceChange(event);" accesskey="&chkEnableRedirector.accesskey;" preference="enabled" />
+ <spacer flex="1" />
+ </hbox>
+ <hbox>
+ <checkbox id="chkShowStatusBarIcon" label="&chkShowStatusBarIcon.label;" oncommand="Settings.preferenceChange(event);" accesskey="&chkShowStatusBarIcon.accesskey;" preference="showStatusBarIcon" />
+ <spacer flex="1" />
+ </hbox>
+ <hbox>
+ <checkbox id="chkShowContextMenu" label="&chkShowContextMenu.label;" oncommand="Settings.preferenceChange(event);" accesskey="&chkShowContextMenu.accesskey;" preference="showContextMenu" />
+ <spacer flex="1" />
+ </hbox>
+ </groupbox>
+ <groupbox>
+ <caption label="&grpDebuggingPreferences.label;" />
+ <hbox>
+ <checkbox id="chkEnableDebugOutput" label="&chkEnableDebugOutput.label;" oncommand="Settings.preferenceChange(event);" accesskey="&chkEnableDebugOutput.accesskey;" preference="debugEnabled" />
+ <spacer flex="1" />
+ </hbox>
+ </groupbox>
+ <spacer flex="1" />
+ </vbox>
+ </tabpanel>
+ <tabpanel>
+ <groupbox flex="1" id="grpImportExport">
+ <vbox>
+ <hbox align="middle">
+ <button id="btnImport" accesskey="&btnImport.accesskey;" oncommand="Settings.import();" label="&btnImport.label;"/>
+ <label id="lblImport" value="&lblImport.label;" />
+ <spacer flex="1" />
+ </hbox>
+ <hbox>
+ <button id="btnExport" accesskey="&btnExport.accesskey;" oncommand="Settings.export();" label="&btnExport.label;"/>
+ <label id="lblExport" value="&lblExport.label;" />
+ <spacer flex="1" />
+ </hbox>
+ <spacer flex="1" />
+ </vbox>
+ </groupbox>
+ </tabpanel>
+ <tabpanel>
+ <browser type="content" src="chrome://redirector/content/ui/help.html" flex="1" />
+ </tabpanel>
+ </tabpanels>
+ </tabbox>
+</window>
diff --git a/chrome/ui/skin/movedown.png b/chrome/ui/skin/movedown.png
new file mode 100644
index 0000000..d32b79c
--- /dev/null
+++ b/chrome/ui/skin/movedown.png
Binary files differ
diff --git a/chrome/ui/skin/movedowndisabled.png b/chrome/ui/skin/movedowndisabled.png
new file mode 100644
index 0000000..afd8fc6
--- /dev/null
+++ b/chrome/ui/skin/movedowndisabled.png
Binary files differ
diff --git a/chrome/ui/skin/moveup.png b/chrome/ui/skin/moveup.png
new file mode 100644
index 0000000..3025378
--- /dev/null
+++ b/chrome/ui/skin/moveup.png
Binary files differ
diff --git a/chrome/ui/skin/moveupdisabled.png b/chrome/ui/skin/moveupdisabled.png
new file mode 100644
index 0000000..e526b29
--- /dev/null
+++ b/chrome/ui/skin/moveupdisabled.png
Binary files differ
diff --git a/chrome/ui/skin/redirector.css b/chrome/ui/skin/redirector.css
new file mode 100644
index 0000000..ff9762e
--- /dev/null
+++ b/chrome/ui/skin/redirector.css
@@ -0,0 +1,15 @@
+/* $Id$ */
+
+.disabledRedirect { color:grey; }
+#lstRedirects richlistitem { border-bottom:dotted 1px grey; padding:3px; }
+.redirectRows > row > label { font-weight:bold;}
+.editRedirects > row > textbox { width: 350px; }
+#redirectorSettings > tabbox { margin:4px; }
+#btnUp { list-style-image: url('chrome://redirector/skin/moveup.png'); }
+#btnDown { list-style-image: url('chrome://redirector/skin/movedown.png'); }
+#btnUp[disabled=true] { list-style-image: url('chrome://redirector/skin/moveupdisabled.png'); }
+#btnDown[disabled=true] { list-style-image: url('chrome://redirector/skin/movedowndisabled.png'); }
+
+#btnUp, #btnDown { width:25px; min-width:25px; }
+#lblExport, #lblImport { padding-top:5px; }
+#grpImportExport { padding-top:10px; padding-left:5px;} \ No newline at end of file
diff --git a/chrome/ui/skin/redirector.png b/chrome/ui/skin/redirector.png
new file mode 100644
index 0000000..f8de12c
--- /dev/null
+++ b/chrome/ui/skin/redirector.png
Binary files differ
diff --git a/chrome/ui/skin/statusactive.png b/chrome/ui/skin/statusactive.png
new file mode 100644
index 0000000..06ce766
--- /dev/null
+++ b/chrome/ui/skin/statusactive.png
Binary files differ
diff --git a/chrome/ui/skin/statusinactive.png b/chrome/ui/skin/statusinactive.png
new file mode 100644
index 0000000..8b83562
--- /dev/null
+++ b/chrome/ui/skin/statusinactive.png
Binary files differ