| Index: lib/typoFixer.js | 
| =================================================================== | 
| new file mode 100644 | 
| --- /dev/null | 
| +++ b/lib/typoFixer.js | 
| @@ -0,0 +1,198 @@ | 
| +/* This Source Code Form is subject to the terms of the Mozilla Public | 
| + * License, v. 2.0. If a copy of the MPL was not distributed with this file, | 
| + * You can obtain one at http://mozilla.org/MPL/2.0/. */ | 
| + | 
| +Cu.import("resource://gre/modules/Services.jsm"); | 
| +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); | 
| + | 
| +let {Prefs} = require("prefs"); | 
| +let {WindowObserver} = require("windowObserver"); | 
| +let {getSchemeCorrection, isKnownScheme, getDomainCorrection, getDomainReferral, onWhitelistEntryAdded} = require("typoRules"); | 
| +let {processTypedDomain, processDomainCorrection, processFalsePositive} = require("typoCollector"); | 
| +let appIntegration = require("typoAppIntegration"); | 
| +let netError = require("typoNetError"); | 
| + | 
| +// Attach our handlers to all browser windows | 
| +new WindowObserver( | 
| +{ | 
| + applyToWindow: function(window) | 
| + { | 
| + if (!appIntegration.isKnownWindow(window)) | 
| + return; | 
| + | 
| + netError.applyToWindow(window); | 
| + appIntegration.applyToWindow(window, correctURL); | 
| + }, | 
| + | 
| + removeFromWindow: function(window) | 
| + { | 
| + if (!appIntegration.isKnownWindow(window)) | 
| + return; | 
| + | 
| + netError.removeFromWindow(window); | 
| + appIntegration.removeFromWindow(window); | 
| + } | 
| +}); | 
| + | 
| +function parseURL(url) | 
| +{ | 
| + if (/^\s*((?:\w+:)?\/*(?:[^\/#]*@)?)([^\/:#]*)/.test(url)) | 
| + return [RegExp.$1, RegExp.$2.toLowerCase(), RegExp.rightContext]; | 
| + else | 
| + return [url, null, null]; | 
| +} | 
| + | 
| +function isIPAddress(domain) | 
| +{ | 
| + try | 
| + { | 
| + Services.eTLD.getBaseDomainFromHost(domain); | 
| + return false; | 
| + } | 
| + catch (e) | 
| + { | 
| + return (e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS); | 
| + } | 
| +} | 
| + | 
| +function correctURL(window, value) | 
| +{ | 
| + let hasCorrection = false; | 
| + | 
| + value = value.trim(); | 
| + if (value.length == 0) | 
| + return null; | 
| + | 
| + // Replace backslashes | 
| + value = value.replace(/\\/g, "/"); | 
| + | 
| + // Does the URL scheme need correcting? | 
| + if (/^([^\/]+)(\/.*)/.test(value)) | 
| + { | 
| + let scheme = RegExp.$1; | 
| + let suffix = RegExp.$2; | 
| + let correction = getSchemeCorrection(scheme) | 
| + if (correction != scheme) | 
| + { | 
| + value = correction + suffix; | 
| + hasCorrection = true; | 
| + } | 
| + } | 
| + | 
| + // Ignore URL schemes that we don't know | 
| + if (/^([\w\-]+:)/.test(value) && !isKnownScheme(RegExp.$1)) | 
| + return null; | 
| + | 
| + // Ignore search keywords and such | 
| + if ("getShortcutOrURI" in window && window.getShortcutOrURI(value) != value) | 
| + return null; | 
| + | 
| + // Spaces before the first slash or period is probably a quick search | 
| + if (/^[^\/\.\s]+\s/.test(value)) | 
| + return null; | 
| + | 
| + let [prefix, domain, suffix] = parseURL(value); | 
| + if (!domain) | 
| + return null; | 
| + | 
| + let oldDomain = domain; | 
| + if (!isIPAddress(domain)) | 
| + { | 
| + processTypedDomain(domain); | 
| + | 
| + let newDomain = getDomainCorrection(domain); | 
| + if (newDomain != domain) | 
| + { | 
| + processDomainCorrection(domain, newDomain); | 
| + domain = newDomain; | 
| + hasCorrection = true; | 
| + | 
| + let referral = getDomainReferral(domain.replace(/^www\./, "")); | 
| + if (referral) | 
| + { | 
| + // We need to add a query string parameter when sending users to this domain | 
| + let anchorIndex = suffix.indexOf("#"); | 
| + let anchor = ""; | 
| + if (anchorIndex >= 0) | 
| + { | 
| + anchor = suffix.substr(anchorIndex); | 
| + suffix = suffix.substr(0, anchorIndex); | 
| + } | 
| + | 
| + let queryIndex = suffix.indexOf("?"); | 
| + if (queryIndex >= 0) | 
| + { | 
| + if (!/&$/.test(suffix)) | 
| + suffix += "&"; | 
| + suffix += referral; | 
| + } | 
| + else | 
| + { | 
| + if (suffix.indexOf("/") < 0) | 
| + suffix += "/"; | 
| + suffix += "?" + referral; | 
| + } | 
| + | 
| + suffix += anchor; | 
| + } | 
| + } | 
| + } | 
| + | 
| + if (!hasCorrection) | 
| + return null; | 
| + | 
| + if (!appIntegration.isTypoCorrectionEnabled(window, prefix, domain, suffix)) | 
| + return null; | 
| + | 
| + // Show infobar to inform and ask about correction | 
| + let [message, yes, no] = getInfobarTexts(); | 
| + message = message.replace(/\?1\?/g, prefix+domain); | 
| + let buttons = [ | 
| + { | 
| + label: yes, | 
| + accessKey: null, | 
| + callback: function() | 
| + { | 
| + // Yes: Do nothing | 
| + } | 
| + }, | 
| + { | 
| + label: no, | 
| + accessKey: null, | 
| + callback: function() | 
| + { | 
| + // No: Add to list of corrections (ignore) | 
| + let entry = oldDomain.replace(/^www\./, ""); | 
| + Prefs.whitelist[entry] = true; | 
| + onWhitelistEntryAdded(entry); | 
| + Prefs.whitelist = JSON.parse(JSON.stringify(Prefs.whitelist)); | 
| + | 
| + appIntegration.loadURI(window, value); | 
| + processFalsePositive(domain, oldDomain); | 
| + } | 
| + } | 
| + ]; | 
| + // We need to have persistence being set to 1 due to redirect which happens afterwards | 
| + appIntegration.openInfobar(window, require("info").addonName + "-infobar-askafter", message, buttons, 1); | 
| + | 
| + require("typoSurvey").incrementCorrectionsCounter(); | 
| + | 
| + return prefix + domain + suffix; | 
| +} | 
| + | 
| +let stringBundle = null; | 
| + | 
| +function getInfobarTexts() | 
| +{ | 
| + // Randomize URI to work around bug 719376 | 
| + if (!stringBundle) | 
| + stringBundle = Services.strings.createBundle("chrome://" + require("info").addonName + "/locale/typo.properties?" + Math.random()); | 
| + let result = [ | 
| + stringBundle.GetStringFromName("urlfixer.isItCorrect"), | 
| + stringBundle.GetStringFromName("urlfixer.yes"), | 
| + stringBundle.GetStringFromName("urlfixer.no") | 
| + ]; | 
| + | 
| + getInfobarTexts = function() result; | 
| + return getInfobarTexts(); | 
| +} |