| Index: assets/js/start.js |
| =================================================================== |
| new file mode 100755 |
| --- /dev/null |
| +++ b/assets/js/start.js |
| @@ -0,0 +1,589 @@ |
| +/* |
| + * This Source Code is subject to the terms of the Mozilla Public License |
| + * version 2.0 (the "License"). You can obtain a copy of the License at |
| + * http://mozilla.org/MPL/2.0/. |
| + */ |
| + |
| +function removeTrailingDots(string) |
| +{ |
| + return string.replace(/\.+$/, ""); |
| +} |
| + |
| +/** |
| + * Checks whether a request is third party for the given document, uses |
| + * information from the public suffix list to determine the effective domain |
| + * name for the document. |
| + */ |
| +function isThirdParty(requestHost, documentHost) |
| +{ |
| + requestHost = removeTrailingDots(requestHost); |
| + documentHost = removeTrailingDots(documentHost); |
| + |
| + // Extract domain name - leave IP addresses unchanged, otherwise leave only base domain |
| + var documentDomain = getBaseDomain(documentHost); |
| + if (requestHost.length > documentDomain.length) |
| + return (requestHost.substr(requestHost.length - documentDomain.length - 1) != "." + documentDomain); |
| + else |
| + return (requestHost != documentDomain); |
| +} |
| + |
| +function reportError(exp) |
| +{ |
| + Android.print("Error: " + exp); |
| + if (typeof exp == "string") |
| + { |
| + Android.showToast(exp); |
| + } |
| + Android.print(exp.stack); |
| +} |
| + |
| +function MatcherPatch() |
| +{ |
| + // Very ugly - we need to rewrite _checkEntryMatch() function to make sure |
| + // it calls Filter.fromText() instead of assuming that the filter exists. |
| + var origFunction = Matcher.prototype._checkEntryMatch.toString(); |
| + var newFunction = origFunction.replace(/\bFilter\.knownFilters\[(.*?)\];/g, "Filter.fromText($1);"); |
| + eval("Matcher.prototype._checkEntryMatch = " + newFunction); |
| +} |
| + |
| +var window = this; |
| + |
| +var Components = |
| +{ |
| + interfaces: |
| + { |
| + nsIFile: {DIRECTORY_TYPE: 0}, |
| + nsIFileURL: function() {}, |
| + nsIFileInputStream: null, |
| + nsIFileOutputStream: null, |
| + nsIHttpChannel: function() {}, |
| + nsIConverterInputStream: {DEFAULT_REPLACEMENT_CHARACTER: null}, |
| + nsIConverterOutputStream: null, |
| + nsIUnicharLineInputStream: null, |
| + nsISafeOutputStream: null, |
| + nsITimer: {TYPE_REPEATING_SLACK: 0}, |
| + nsIInterfaceRequestor: null, |
| + nsIChannelEventSink: null |
| + }, |
| + classes: |
| + { |
| + "@mozilla.org/network/file-input-stream;1": |
| + { |
| + createInstance: function() |
| + { |
| + return new FakeInputStream(); |
| + } |
| + }, |
| + "@mozilla.org/network/file-output-stream;1": |
| + { |
| + createInstance: function() |
| + { |
| + return new FakeOutputStream(); |
| + } |
| + }, |
| + "@mozilla.org/dom/json;1": |
| + { |
| + createInstance: function() { |
| + return { |
| + decodeFromStream: function(stream, encoding) |
| + { |
| + var line = {}; |
| + var haveMore = true; |
| + var s = new String(); |
| + while (true) |
| + { |
| + if (haveMore) |
| + haveMore = stream.readLine(line); |
| + else |
| + break; |
| + s += line.value; |
| + } |
| + return JSON.parse(s); |
| + }, |
| + encodeToStream: function(stream, encoding, something, obj) |
| + { |
| + var s = JSON.stringify(obj); |
| + stream.writeString(s); |
| + } |
| + } |
| + } |
| + }, |
| + "@mozilla.org/timer;1": |
| + { |
| + createInstance: function() |
| + { |
| + return new FakeTimer(); |
| + } |
| + } |
| + }, |
| + results: {}, |
| + utils: { |
| + reportError: reportError |
| + }, |
| + manager: null, |
| + ID: function() |
| + { |
| + return null; |
| + }, |
| + Constructor: function() |
| + { |
| + // This method is only used to get XMLHttpRequest constructor |
| + return XMLHttpRequest; |
| + } |
| +}; |
| +const Cc = Components.classes; |
| +const Ci = Components.interfaces; |
| +const Cr = Components.results; |
| +const Cu = Components.utils; |
| + |
| +Cc["@mozilla.org/intl/converter-input-stream;1"] = Cc["@mozilla.org/network/file-input-stream;1"]; |
| +Cc["@mozilla.org/network/safe-file-output-stream;1"] = Cc["@mozilla.org/intl/converter-output-stream;1"] = Cc["@mozilla.org/network/file-output-stream;1"]; |
| + |
| +var Prefs = |
| +{ |
| + patternsbackups: 5, |
| + patternsbackupinterval: 24, |
| + data_directory: _datapath, |
| + savestats: false, |
| + privateBrowsing: false, |
| + get subscriptions_autoupdate() { return Android.canAutoupdate() }, |
| + subscriptions_fallbackerrors: 5, |
| + subscriptions_fallbackurl: "https://adblockplus.org/getSubscription?version=%VERSION%&url=%SUBSCRIPTION%&downloadURL=%URL%&error=%ERROR%&channelStatus=%CHANNELSTATUS%&responseStatus=%RESPONSESTATUS%", |
| + addListener: function() {} |
| +}; |
| + |
| +var Utils = |
| +{ |
| + systemPrincipal: null, |
| + getString: function(id) |
| + { |
| + return id; |
| + }, |
| + getLineBreak: function() |
| + { |
| + return "\n"; |
| + }, |
| + resolveFilePath: function(path) |
| + { |
| + return new FakeFile(path); |
| + }, |
| + ioService: |
| + { |
| + newURI: function(uri) |
| + { |
| + if (!uri.length || uri[0] == "~") |
| + throw new Error("Invalid URI"); |
| + |
| + /^([^:\/]*)/.test(uri); |
| + var scheme = RegExp.$1.toLowerCase(); |
| + |
| + return {scheme: scheme, spec: uri}; |
| + } |
| + }, |
| + observerService: |
| + { |
| + addObserver: function() {}, |
| + removeObserver: function() {} |
| + }, |
| + chromeRegistry: |
| + { |
| + convertChromeURL: function() {} |
| + }, |
| + runAsync: function(callback, thisPtr) |
| + { |
| + var params = Array.prototype.slice.call(arguments, 2); |
| + Android.setTimeout(function() |
| + { |
| + callback.apply(thisPtr, params); |
| + }, 0); |
| + }, |
| + addonVersion: _version, |
| + platformVersion: "10.0", |
| + get appLocale() |
| + { |
| + Android.getLocale(); |
| + }, |
| + generateChecksum: function(lines) |
| + { |
| + // We cannot calculate MD5 checksums yet :-( |
| + return null; |
| + }, |
| + makeURI: function(url) |
| + { |
| + return Utils.ioService.newURI(url); |
| + }, |
| + checkLocalePrefixMatch: function(prefixes) |
| + { |
| + if (!prefixes) |
| + return null; |
| + |
| + var list = prefixes.split(","); |
| + for (var i = 0; i < list.length; i++) |
| + if (new RegExp("^" + list[i] + "\\b").test(Utils.appLocale)) |
| + return list[i]; |
| + |
| + return null; |
| + }, |
| + versionComparator: |
| + { |
| + compare: function(v1, v2) |
| + { |
| + var parts1 = v1.split("."); |
| + var parts2 = v2.split("."); |
| + for (var i = 0; i < Math.max(parts1.length, parts2.length); i++) |
| + { |
| + // TODO: Handle non-integer version parts properly |
| + var part1 = parseInt(i < parts1.length ? parts1[i] : "0"); |
| + var part2 = parseInt(i < parts2.length ? parts2[i] : "0"); |
| + if (part1 != part2) |
| + return part1 - part2; |
| + } |
| + return 0; |
| + } |
| + } |
| +}; |
| + |
| +var XPCOMUtils = |
| +{ |
| + generateQI: function() {} |
| +}; |
| + |
| +function FakeFile(path) |
| +{ |
| + this.path = path; |
| +} |
| +FakeFile.prototype = |
| +{ |
| + get leafName() |
| + { |
| + return this.path; |
| + }, |
| + set leafName(value) |
| + { |
| + this.path = value; |
| + }, |
| + append: function(path) |
| + { |
| + this.path += _separator + path; |
| + }, |
| + clone: function() |
| + { |
| + return new FakeFile(this.path); |
| + }, |
| + exists: function() |
| + { |
| + return Android.fileExists(this.path); |
| + }, |
| + remove: function() |
| + { |
| + Android.fileRemove(this.path); |
| + }, |
| + moveTo: function(parent, newPath) |
| + { |
| + Android.fileRename(this.path, newPath); |
| + }, |
| + get lastModifiedTime() |
| + { |
| + return Android.fileLastModified(this.path); |
| + }, |
| + get parent() |
| + { |
| + return {create: function() {}}; |
| + }, |
| + normalize: function() {} |
| +}; |
| + |
| +function FakeInputStream() |
| +{ |
| +} |
| +FakeInputStream.prototype = |
| +{ |
| + lines: null, |
| + currentIndex: 0, |
| + |
| + init: function(file) |
| + { |
| + if (file instanceof FakeInputStream) |
| + this.lines = file.lines; |
| + else |
| + this.lines = Android.fileRead(file.path).split(/\n/); |
| + }, |
| + readLine: function(line) |
| + { |
| + if (this.currentIndex < this.lines.length) |
| + line.value = this.lines[this.currentIndex]; |
| + this.currentIndex++; |
| + return (this.currentIndex < this.lines.length); |
| + }, |
| + close: function() {}, |
| + QueryInterface: function() |
| + { |
| + return this; |
| + } |
| +}; |
| + |
| +function FakeOutputStream() |
| +{ |
| +} |
| +FakeOutputStream.prototype = |
| +{ |
| + file: null, |
| + buffer: null, |
| + |
| + init: function(file) |
| + { |
| + if (file instanceof FakeOutputStream) |
| + { |
| + this.file = file.file; |
| + this.buffer = file.buffer; |
| + } |
| + else |
| + { |
| + this.file = file; |
| + this.buffer = []; |
| + } |
| + }, |
| + writeString: function(string) |
| + { |
| + this.buffer.push(string); |
| + }, |
| + close: function() |
| + { |
| + Android.fileWrite(this.file.path, this.buffer.join("")); |
| + }, |
| + finish: function() |
| + { |
| + this.close(); |
| + }, |
| + flush: function() {}, |
| + QueryInterface: function() |
| + { |
| + return this; |
| + } |
| +}; |
| + |
| +function FakeTimer() |
| +{ |
| +} |
| +FakeTimer.prototype = |
| +{ |
| + delay: 0, |
| + callback: null, |
| + initWithCallback: function(callback, delay) |
| + { |
| + this.callback = callback; |
| + this.delay = delay; |
| + this.scheduleTimeout(); |
| + }, |
| + scheduleTimeout: function() |
| + { |
| + var me = this; |
| + Android.setTimeout(function() |
| + { |
| + try |
| + { |
| + me.callback(); |
| + } |
| + catch(e) |
| + { |
| + reportError(e); |
| + } |
| + me.scheduleTimeout(); |
| + }, this.delay); |
| + } |
| +}; |
| + |
| +function ElemHidePatch() |
| +{ |
| + /** |
| + * Returns a list of selectors to be applied on a particular domain. With |
| + * specificOnly parameter set to true only the rules listing specific domains |
| + * will be considered. |
| + */ |
| + ElemHide.getSelectorsForDomain = function(/**String*/ domain, /**Boolean*/ specificOnly) |
| + { |
| + var result = []; |
| + for (var key in filterByKey) |
| + { |
| + var filter = Filter.knownFilters[filterByKey[key]]; |
| + if (specificOnly && (!filter.domains || filter.domains[""])) |
| + continue; |
| + |
| + if (filter.isActiveOnDomain(domain)) |
| + result.push(filter.selector); |
| + } |
| + if (result.length) |
| + return "<style type=\"text/css\">" + result.join() + " { display: none !important }</style>"; |
| + else |
| + return null; |
| + }; |
| + |
| + ElemHide.init = function() {}; |
| +} |
| + |
| +/** |
| + * Removes all subscriptions from storage. |
| + */ |
| +function clearSubscriptions() |
| +{ |
| + while (FilterStorage.subscriptions.length) |
| + FilterStorage.removeSubscription(FilterStorage.subscriptions[0]); |
| +} |
| + |
| +/** |
| + * Adds selected subscription to storage. |
| + */ |
| +function addSubscription(jsonSub) |
| +{ |
| + var newSub = JSON.parse(jsonSub); |
| + |
| + var subscription = Subscription.fromURL(newSub["url"]); |
| + if (subscription) |
| + { |
| + subscription.disabled = false; |
| + subscription.title = newSub["title"]; |
| + subscription.homepage = newSub["homepage"]; |
| + if (subscription instanceof DownloadableSubscription && !subscription.lastDownload) |
| + { |
| + Synchronizer.execute(subscription); |
| + } |
| + FilterStorage.addSubscription(subscription); |
| + FilterStorage.saveToDisk(); |
| + } |
| +} |
| + |
| +/** |
| + * Forces subscriptions refresh. |
| + */ |
| +function refreshSubscriptions() |
| +{ |
| + for (var i = 0; i < FilterStorage.subscriptions.length; i++) |
| + { |
| + var subscription = FilterStorage.subscriptions[i]; |
| + if (subscription instanceof DownloadableSubscription) |
| + Synchronizer.execute(subscription, true, true); |
| + } |
| +} |
| + |
| +/** |
| + * Verifies that subscriptions are loaded and returns flag of subscription presence. |
| + */ |
| +function verifySubscriptions() |
| +{ |
| + var hasSubscriptions = false; |
| + for (var i = 0; i < FilterStorage.subscriptions.length; i++) |
| + { |
| + var subscription = FilterStorage.subscriptions[i]; |
| + if (subscription instanceof DownloadableSubscription) |
| + { |
| + hasSubscriptions = true; |
| + updateSubscriptionStatus(subscription); |
| + if (!subscription.lastDownload) |
| + { |
| + Synchronizer.execute(subscription); |
| + } |
| + } |
| + } |
| + return hasSubscriptions; |
| +} |
| + |
| +/** |
| + * Callback for subscription status updates. |
| + */ |
| +function updateSubscriptionStatus(subscription) |
| +{ |
| + var status = ""; |
| + var time = 0; |
| + if (Synchronizer.isExecuting(subscription.url)) |
| + status = "synchronize_in_progress"; |
| + else if (subscription.downloadStatus && subscription.downloadStatus != "synchronize_ok") |
| + status = subscription.downloadStatus; |
| + else if (subscription.lastDownload > 0) |
| + { |
| + time = subscription.lastDownload * 1000; |
| + status = "synchronize_last_at"; |
| + } |
| + else |
| + status = "synchronize_never"; |
| + |
| + Android.setStatus(status, time); |
| +} |
| + |
| +function onFilterChange(action, subscription, param1, param2) |
| +{ |
| + switch (action) |
| + { |
| + case "subscription.lastDownload": |
| + case "subscription.downloadStatus": |
| + updateSubscriptionStatus(subscription); |
| + break; |
| + } |
| +} |
| + |
| +function startInteractive() |
| +{ |
| + FilterNotifier.addListener(onFilterChange); |
| +} |
| + |
| +function stopInteractive() |
| +{ |
| + FilterNotifier.removeListener(onFilterChange); |
| +} |
| + |
| +function matchesAny(url, query, reqHost, refHost, accept) |
| +{ |
| + var contentType = null; |
| + var thirdParty = true; |
| + |
| + if (accept != "") |
| + { |
| + if (accept.indexOf("text/css") != -1) |
| + contentType = "STYLESHEET"; |
| + else if (accept.indexOf("image/*" != -1)) |
| + contentType = "IMAGE"; |
| + } |
| + |
| + if (contentType == null) |
| + { |
| + var lurl = url.toLowerCase(); |
| + if (/\.js$/.test(lurl)) |
| + contentType = "SCRIPT"; |
| + else if (/\.css$/.test(lurl)) |
| + contentType = "STYLESHEET"; |
| + else if (/\.(?:gif|png|jpe?g|bmp|ico)$/.test(lurl)) |
| + contentType = "IMAGE"; |
| + else if (/\.(?:ttf|woff)$/.test(lurl)) |
| + contentType = "FONT"; |
| + } |
| + if (contentType == null) |
| + contentType = "OTHER"; |
| + |
| + if (refHost != "") |
| + { |
| + thirdParty = isThirdParty(reqHost, refHost); |
| + } |
| + |
| + if (query != "") |
| + url = url + "?" + query; |
| + |
| + var filter = defaultMatcher.matchesAny(url, contentType, null, thirdParty); |
| + |
| + return (filter != null && !(filter instanceof WhitelistFilter)); |
| +} |
| + |
| +Android.load("XMLHttpRequest.jsm"); |
| +Android.load("FilterNotifier.jsm"); |
| +Android.load("FilterClasses.jsm"); |
| +Android.load("SubscriptionClasses.jsm"); |
| +Android.load("FilterStorage.jsm"); |
| +Android.load("FilterListener.jsm"); |
| +Android.load("Matcher.jsm"); |
| +Android.load("ElemHide.jsm"); |
| +Android.load("Synchronizer.jsm"); |
| + |
| +FilterListener.startup(); |
| +Synchronizer.startup(); |
| + |
| +Android.load("publicSuffixList.js"); |
| +Android.load("punycode.js"); |
| +Android.load("basedomain.js"); |