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"); |