Index: lib/adblockplus_compat.js |
=================================================================== |
new file mode 100644 |
--- /dev/null |
+++ b/lib/adblockplus_compat.js |
@@ -0,0 +1,500 @@ |
+/* |
+ * 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/. |
+ */ |
+ |
+// TODO: Parts of this file are identical to ABP/Chrome, these should be split |
+// away into a separate file. |
+ |
+// |
+// Module framework stuff |
+// |
+ |
+function require(module) |
+{ |
+ return require.scopes[module]; |
+} |
+require.scopes = {__proto__: null}; |
+ |
+function importAll(module, globalObj) |
+{ |
+ var exports = require(module); |
+ for (var key in exports) |
+ globalObj[key] = exports[key]; |
+} |
+ |
+onShutdown = { |
+ done: false, |
+ add: function() {}, |
+ remove: function() {} |
+}; |
+ |
+// |
+// XPCOM emulation |
+// |
+ |
+var Components = |
+{ |
+ interfaces: |
+ { |
+ nsIFile: {DIRECTORY_TYPE: 0}, |
+ nsIFileURL: function() {}, |
+ nsIHttpChannel: function() {}, |
+ nsITimer: {TYPE_REPEATING_SLACK: 0}, |
+ nsIInterfaceRequestor: null, |
+ nsIChannelEventSink: null |
+ }, |
+ classes: |
+ { |
+ "@mozilla.org/timer;1": |
+ { |
+ createInstance: function() |
+ { |
+ return new FakeTimer(); |
+ } |
+ }, |
+ "@mozilla.org/xmlextras/xmlhttprequest;1": |
+ { |
+ createInstance: function() |
+ { |
+ return new XMLHttpRequest(); |
+ } |
+ } |
+ }, |
+ results: {}, |
+ utils: { |
+ reportError: function(e) |
+ { |
+ opera.postError(e + "\n" + (typeof e == "object" ? e.stack : new Error().stack)); |
+ } |
+ }, |
+ manager: null, |
+ ID: function() |
+ { |
+ return null; |
+ } |
+}; |
+const Cc = Components.classes; |
+const Ci = Components.interfaces; |
+const Cr = Components.results; |
+const Cu = Components.utils; |
+ |
+var XPCOMUtils = |
+{ |
+ generateQI: function() {} |
+}; |
+ |
+// |
+// Info pseudo-module |
+// |
+ |
+require.scopes.info = |
+{ |
+ get addonID() |
+ { |
+ return widget.id; |
+ }, |
+ addonVersion: "2.0.3", // Hardcoded for now |
+ addonRoot: "", |
+ get addonName() |
+ { |
+ return widget.name; |
+ }, |
+ application: "opera" |
+}; |
+ |
+// |
+// IO module: no direct file system access, using FileSystem API |
+// |
+ |
+require.scopes.io = |
+{ |
+ IO: { |
Felix Dahlke
2012/10/10 12:11:22
Opening brace not on its own line?
Felix Dahlke
2012/10/11 07:54:28
Nevermind.
|
+ _getFilePath: function(file) |
+ { |
+ if (file instanceof FakeFile) |
+ return file.path; |
+ else if ("spec" in file) |
+ return file.spec; |
+ |
+ throw new Error("Unexpected file type"); |
+ }, |
+ |
+ lineBreak: "\n", |
+ |
+ resolveFilePath: function(path) |
+ { |
+ return new FakeFile(path); |
+ }, |
+ |
+ readFromFile: function(file, decode, listener, callback, timeLineID) |
+ { |
+ if ("spec" in file && /^defaults\b/.test(file.spec)) |
Felix Dahlke
2012/10/10 12:11:22
The first check is not necessary, .test(undefined)
Felix Dahlke
2012/10/11 07:54:28
Nevermind.
Wladimir Palant
2012/10/11 09:36:26
Still, I don't feel comfortable running regular ex
|
+ { |
+ // Code attempts to read the default patterns.ini, we don't have that. |
+ // Make sure to execute first-run actions instead. |
+ callback(null); |
+ executeFirstRunActions(); |
Felix Dahlke
2012/10/11 07:54:28
This function cannot be called from here.
|
+ return; |
+ } |
+ |
+ var path = this._getFilePath(file); |
+ if (!(path in window.localStorage)) |
+ { |
+ callback(new Error("File doesn't exist")) |
+ return; |
+ } |
+ |
+ var lines = window.localStorage[path].split(/[\r\n]+/); |
+ |
+ // Fake asynchronous execution |
+ setTimeout(function() |
+ { |
+ for (var i = 0; i < lines.length; i++) |
+ listener.process(lines[i]); |
+ listener.process(null); |
+ callback(null); |
+ }.bind(this), 0); |
+ }, |
+ |
+ writeToFile: function(file, encode, data, callback, timeLineID) |
+ { |
+ var path = this._getFilePath(file); |
+ window.localStorage[path] = data.join("\n") + "\n"; |
Felix Dahlke
2012/10/10 12:11:22
Why not use this.lineBreak?
|
+ window.localStorage[path + "/lastModified"] = Date.now(); |
+ |
+ // Fake asynchronous execution |
+ setTimeout(callback.bind(null, null), 0); |
+ }, |
+ |
+ copyFile: function(fromFile, toFile, callback) |
+ { |
+ // Simply combine read and write operations |
+ var data = []; |
+ this.readFromFile(fromFile, false, { |
Felix Dahlke
2012/10/10 12:11:22
Shouldn't the opening brace be on its own line?
|
+ process: function(line) |
+ { |
+ if (line !== null) |
+ data.push(line); |
+ } |
+ }, function(e) |
+ { |
+ if (e) |
+ callback(e); |
+ else |
+ this.writeToFile(toFile, false, data, callback); |
+ }.bind(this)); |
+ }, |
+ |
+ renameFile: function(fromFile, newName, callback) |
+ { |
+ var path = this._getFilePath(fromFile); |
+ if (!(path in window.localStorage)) |
+ { |
+ callback(new Error("File doesn't exist")) |
+ return; |
+ } |
+ |
+ window.localStorage[newName] = window.localStorage[path]; |
+ window.localStorage[newName + "/lastModified"] = window.localStorage[path + "/lastModified"] || 0; |
Felix Dahlke
2012/10/10 12:11:22
How about a function that sets both localStorage[x
|
+ delete window.localStorage[path]; |
+ delete window.localStorage[path + "/lastModified"]; |
+ callback(null); |
+ }, |
+ |
+ removeFile: function(file, callback) |
+ { |
+ var path = this._getFilePath(file); |
+ delete window.localStorage[path]; |
Felix Dahlke
2012/10/10 12:11:22
How about a function to delete both localStorage[x
|
+ delete window.localStorage[path + "/lastModified"]; |
+ callback(null); |
+ }, |
+ |
+ statFile: function(file, callback) |
+ { |
+ var path = this._getFilePath(file); |
+ callback(null, { |
+ exists: path in window.localStorage, |
+ isDirectory: false, |
+ isFile: true, |
+ lastModified: parseInt(window.localStorage[path + "/lastModified"], 10) || 0 |
+ }); |
+ } |
+ } |
+}; |
+ |
+// |
+// Fake nsIFile implementation for our I/O |
+// |
+ |
+function FakeFile(path) |
+{ |
+ this.path = path; |
+} |
+FakeFile.prototype = |
+{ |
+ get leafName() |
+ { |
+ return this.path; |
+ }, |
+ set leafName(value) |
+ { |
+ this.path = value; |
+ }, |
+ append: function(path) |
+ { |
+ this.path += path; |
+ }, |
+ clone: function() |
+ { |
+ return new FakeFile(this.path); |
+ }, |
+ get parent() |
+ { |
+ return {create: function() {}}; |
+ }, |
+ normalize: function() {} |
+}; |
+ |
+// |
+// Prefs module: the values are hardcoded for now. |
+// |
+ |
+require.scopes.prefs = { |
+ Prefs: { |
+ enabled: true, |
+ patternsfile: "patterns.ini", |
+ patternsbackups: 0, |
+ patternsbackupinterval: 24, |
+ data_directory: "", |
+ savestats: false, |
+ privateBrowsing: false, |
+ subscriptions_fallbackerrors: 5, |
+ subscriptions_fallbackurl: "https://adblockplus.org/getSubscription?version=%VERSION%&url=%SUBSCRIPTION%&downloadURL=%URL%&error=%ERROR%&channelStatus=%CHANNELSTATUS%&responseStatus=%RESPONSESTATUS%", |
+ subscriptions_autoupdate: true, |
+ subscriptions_exceptionsurl: "https://easylist-downloads.adblockplus.org/exceptionrules.txt", |
+ documentation_link: "https://adblockplus.org/redirect?link=%LINK%&lang=%LANG%", |
+ addListener: function() {} |
+ } |
+}; |
+ |
+// |
+// Utils module |
+// |
+ |
+require.scopes.utils = |
+{ |
+ Utils: { |
+ systemPrincipal: null, |
+ getString: function(id) |
+ { |
+ return id; |
+ }, |
+ runAsync: function(callback, thisPtr) |
+ { |
+ var params = Array.prototype.slice.call(arguments, 2); |
+ window.setTimeout(function() |
+ { |
+ callback.apply(thisPtr, params); |
+ }, 0); |
+ }, |
+ get appLocale() |
+ { |
+ // Note: navigator.language |
+ return window.navigator.browserLanguage; |
+ }, |
+ generateChecksum: function(lines) |
+ { |
+ // We cannot calculate MD5 checksums yet :-( |
+ return null; |
+ }, |
+ makeURI: function(url) |
+ { |
+ return Services.io.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(this.appLocale)) |
+ return list[i]; |
+ |
+ return null; |
+ } |
+ } |
+}; |
+ |
+// |
+// ElemHideHitRegistration dummy implementation |
+// |
+ |
+require.scopes.elemHideHitRegistration = |
+{ |
+ AboutHandler: {} |
+}; |
+ |
+// |
+// Services.jsm module emulation |
+// |
+ |
+var Services = |
+{ |
+ io: { |
+ 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, |
+ QueryInterface: function() |
+ { |
+ return this; |
+ } |
+ }; |
+ }, |
+ newFileURI: function(file) |
+ { |
+ var result = this.newURI("file:///" + file.path); |
+ result.file = file; |
+ return result; |
+ } |
+ }, |
+ obs: { |
+ addObserver: function() {}, |
+ removeObserver: function() {} |
+ }, |
+ vc: { |
+ compare: function(v1, v2) |
+ { |
+ function parsePart(s) |
+ { |
+ if (!s) |
+ return parsePart("0"); |
+ |
+ var part = { |
+ numA: 0, |
+ strB: "", |
+ numC: 0, |
+ extraD: "" |
+ }; |
+ |
+ if (s === "*") |
+ { |
+ part.numA = Number.MAX_VALUE; |
+ return part; |
+ } |
+ |
+ var matches = s.match(/(\d*)(\D*)(\d*)(.*)/); |
+ part.numA = parseInt(matches[1], 10) || part.numA; |
+ part.strB = matches[2] || part.strB; |
+ part.numC = parseInt(matches[3], 10) || part.numC; |
+ part.extraD = matches[4] || part.extraD; |
+ |
+ if (part.strB == "+") |
+ { |
+ part.numA++; |
+ part.strB = "pre"; |
+ } |
+ |
+ return part; |
+ } |
+ |
+ function comparePartElement(s1, s2) |
+ { |
+ if (s1 === "" && s2 !== "") |
+ return 1; |
+ if (s1 !== "" && s2 === "") |
+ return -1; |
+ return s1 === s2 ? 0 : (s1 > s2 ? 1 : -1); |
+ } |
+ |
+ function compareParts(p1, p2) |
+ { |
+ var result = 0; |
+ var elements = ["numA", "strB", "numC", "extraD"]; |
+ elements.some(function(element) |
+ { |
+ result = comparePartElement(p1[element], p2[element]); |
+ return result; |
+ }); |
+ return result; |
+ } |
+ |
+ var parts1 = v1.split("."); |
+ var parts2 = v2.split("."); |
+ for (var i = 0; i < Math.max(parts1.length, parts2.length); i++) |
+ { |
+ var result = compareParts(parsePart(parts1[i]), parsePart(parts2[i])); |
+ if (result) |
+ return result; |
+ } |
+ return 0; |
+ } |
+ } |
+} |
+ |
+// |
+// FileUtils.jsm module emulation |
+// |
+ |
+var FileUtils = |
+{ |
+ PERMS_DIRECTORY: 0 |
+}; |
+ |
+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; |
+ window.setTimeout(function() |
+ { |
+ try |
+ { |
+ me.callback(); |
+ } |
+ catch(e) |
+ { |
+ Cu.reportError(e); |
+ } |
+ me.scheduleTimeout(); |
+ }, this.delay); |
+ } |
+}; |
+ |
+// |
+// Add a channel property to XMLHttpRequest, Synchronizer needs it |
+// |
+ |
+XMLHttpRequest.prototype.channel = |
+{ |
+ status: -1, |
+ notificationCallbacks: {}, |
+ loadFlags: 0, |
+ INHIBIT_CACHING: 0, |
+ VALIDATE_ALWAYS: 0, |
+ QueryInterface: function() |
+ { |
+ return this; |
+ } |
+}; |