Index: chrome/content/ui/utils.js
===================================================================
--- a/chrome/content/ui/utils.js
+++ b/chrome/content/ui/utils.js
@@ -34,17 +34,17 @@ function require(/**String*/ module)
}
var {Policy} = require("contentPolicy");
var {Filter, InvalidFilter, CommentFilter, ActiveFilter, RegExpFilter,
BlockingFilter, WhitelistFilter, ElemHideBase, ElemHideFilter,
ElemHideException, ElemHideEmulationFilter} = require("filterClasses");
var {FilterNotifier} = require("filterNotifier");
var {FilterStorage} = require("filterStorage");
-var {IO} = require("io");
+var {IO} = require("legacyIO");
var {defaultMatcher, Matcher, CombinedMatcher} = require("matcher");
var {Prefs} = require("prefs");
var {RequestNotifier} = require("requestNotifier");
var {Subscription, SpecialSubscription, RegularSubscription,
ExternalSubscription, DownloadableSubscription} = require("subscriptionClasses");
var {Synchronizer} = require("synchronizer");
var {UI} = require("ui");
var {Utils} = require("utils");
Index: lib/io.js
===================================================================
--- a/lib/io.js
+++ b/lib/io.js
@@ -10,330 +10,165 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Adblock Plus. If not, see .
*/
-/**
- * @fileOverview Module containing file I/O helpers.
- */
+"use strict";
-let {Services} = Cu.import("resource://gre/modules/Services.jsm", null);
-let {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", null);
-let {OS} = Cu.import("resource://gre/modules/osfile.jsm", null);
-let {Task} = Cu.import("resource://gre/modules/Task.jsm", null);
-
-let {Prefs} = require("prefs");
+let {IO: LegacyIO} = require("legacyIO");
let {Utils} = require("utils");
-let firstRead = true;
-const BUFFER_SIZE = 0x80000; // 512kB
-
-let IO = exports.IO =
-{
- /**
- * Retrieves the platform-dependent line break string.
- */
- get lineBreak()
- {
- let lineBreak = (Services.appinfo.OS == "WINNT" ? "\r\n" : "\n");
- Object.defineProperty(this, "lineBreak", {value: lineBreak});
- return lineBreak;
- },
+let webextension = require("webextension");
+let messageID = 0;
+let messageCallbacks = new Map();
- /**
- * Tries to interpret a file path as an absolute path or a path relative to
- * user's profile. Returns a file or null on failure.
- */
- resolveFilePath: function(/**String*/ path) /**nsIFile*/
+webextension.then(port =>
+{
+ port.onMessage.addListener(message =>
{
- if (!path)
- return null;
+ let {id} = message;
+ let callbacks = messageCallbacks.get(id);
+ if (callbacks)
+ {
+ messageCallbacks.delete(id);
- try {
- // Assume an absolute path first
- return new FileUtils.File(path);
- } catch (e) {}
+ if (message.success)
+ callbacks.resolve(message.result);
+ else
+ callbacks.reject(message.result);
+ }
+ });
+});
- try {
- // Try relative path now
- return FileUtils.getFile("ProfD", path.split("/"));
- } catch (e) {}
+function callWebExt(method, ...args)
+{
+ return webextension.then(port =>
+ {
+ return new Promise((resolve, reject) =>
+ {
+ let id = ++messageID;
+ messageCallbacks.set(id, {resolve, reject});
+ port.postMessage({id, method, args});
+ });
+ });
+}
- return null;
- },
+function attachCallback(promise, callback, fallback)
+{
+ promise.then(result =>
+ {
+ callback(null, result);
+ }).catch(error =>
+ {
+ if (fallback && error == "NoSuchFile")
+ fallback();
+ else
+ callback(error);
+ });
+}
+
+exports.IO =
+{
+ resolveFilePath: LegacyIO.resolveFilePath,
/**
* Reads strings from a file asynchronously, calls listener.process() with
* each line read and with a null parameter once the read operation is done.
* The callback will be called when the operation is done.
*/
- readFromFile: function(/**nsIFile*/ file, /**Object*/ listener, /**Function*/ callback)
+ readFromFile(/**nsIFile*/ file, /**Object*/ listener, /**Function*/ callback)
{
- try
- {
- let processing = false;
- let buffer = "";
- let loaded = false;
- let error = null;
+ attachCallback(
+ callWebExt("readFromFile", file.leafName).then(contents =>
+ {
+ return new Promise((resolve, reject) =>
+ {
+ let lineIndex = 0;
- let onProgress = function*(data)
- {
- let index = (processing ? -1 : Math.max(data.lastIndexOf("\n"), data.lastIndexOf("\r")));
- if (index >= 0)
- {
- // Protect against reentrance in case the listener processes events.
- processing = true;
- try
+ function processBatch()
{
- let oldBuffer = buffer;
- buffer = data.substr(index + 1);
- data = data.substr(0, index + 1);
- let lines = data.split(/[\r\n]+/);
- lines.pop();
- lines[0] = oldBuffer + lines[0];
- for (let i = 0; i < lines.length; i++)
+ while (lineIndex < contents.length)
{
- let promise = listener.process(lines[i]);
- if (promise)
- yield promise;
- }
- }
- finally
- {
- processing = false;
- data = buffer;
- buffer = "";
- yield* onProgress(data);
-
- if (loaded)
- {
- loaded = false;
- onSuccess();
+ listener.process(contents[lineIndex++]);
+ if (lineIndex % 1000 == 0)
+ {
+ Utils.runAsync(processBatch);
+ return;
+ }
}
- if (error)
- {
- let param = error;
- error = null;
- onError(param);
- }
+ listener.process(null);
+ resolve();
}
- }
- else
- buffer += data;
- };
-
- let onSuccess = function()
- {
- if (processing)
- {
- // Still processing data, delay processing this event.
- loaded = true;
- return;
- }
-
- // We are ignoring return value of listener.process() here because
- // turning this callback into a generator would be complicated, and
- // delaying isn't really necessary for the last two calls.
- if (buffer !== "")
- listener.process(buffer);
- listener.process(null);
-
- callback(null);
- };
-
- let onError = function(e)
- {
- if (processing)
- {
- // Still processing data, delay processing this event.
- error = e;
- return;
- }
- callback(e);
- };
-
- let decoder = new TextDecoder();
- Task.spawn(function*()
- {
- if (firstRead && Services.vc.compare(Utils.platformVersion, "23.0a1") <= 0)
- {
- // See https://issues.adblockplus.org/ticket/530 - the first file
- // opened cannot be closed due to Gecko bug 858723. Make sure that
- // our patterns.ini file doesn't stay locked by opening a dummy file
- // first.
- try
- {
- let dummyPath = IO.resolveFilePath(Prefs.data_directory + "/dummy").path;
- let dummy = yield OS.File.open(dummyPath, {write: true, truncate: true});
- yield dummy.close();
- }
- catch (e)
- {
- // Dummy might be locked already, we don't care
- }
- }
- firstRead = false;
-
- let f = yield OS.File.open(file.path, {read: true});
- while (true)
- {
- let array = yield f.read(BUFFER_SIZE);
- if (!array.length)
- break;
-
- let data = decoder.decode(array, {stream: true});
- yield* onProgress(data);
- }
- yield f.close();
- }.bind(this)).then(onSuccess, onError);
- }
- catch (e)
- {
- callback(e);
- }
+ processBatch();
+ });
+ }),
+ callback,
+ () => LegacyIO.readFromFile(file, listener, callback)
+ );
},
/**
* Writes string data to a file in UTF-8 format asynchronously. The callback
* will be called when the write operation is done.
*/
- writeToFile: function(/**nsIFile*/ file, /**Iterator*/ data, /**Function*/ callback)
+ writeToFile(/**nsIFile*/ file, /**Iterator*/ data, /**Function*/ callback)
{
- try
- {
- let encoder = new TextEncoder();
-
- Task.spawn(function*()
- {
- // This mimics OS.File.writeAtomic() but writes in chunks.
- let tmpPath = file.path + ".tmp";
- let f = yield OS.File.open(tmpPath, {write: true, truncate: true});
-
- let buf = [];
- let bufLen = 0;
- let lineBreak = this.lineBreak;
-
- function writeChunk()
- {
- let array = encoder.encode(buf.join(lineBreak) + lineBreak);
- buf = [];
- bufLen = 0;
- return f.write(array);
- }
-
- for (let line of data)
- {
- buf.push(line);
- bufLen += line.length;
- if (bufLen >= BUFFER_SIZE)
- yield writeChunk();
- }
-
- if (bufLen)
- yield writeChunk();
-
- // OS.File.flush() isn't exposed prior to Gecko 27, see bug 912457.
- if (typeof f.flush == "function")
- yield f.flush();
- yield f.close();
- yield OS.File.move(tmpPath, file.path, {noCopy: true});
- }.bind(this)).then(callback.bind(null, null), callback);
- }
- catch (e)
- {
- callback(e);
- }
+ attachCallback(
+ callWebExt("writeToFile", file.leafName, Array.from(data)),
+ callback
+ );
},
/**
* Copies a file asynchronously. The callback will be called when the copy
* operation is done.
*/
- copyFile: function(/**nsIFile*/ fromFile, /**nsIFile*/ toFile, /**Function*/ callback)
+ copyFile(/**nsIFile*/ fromFile, /**nsIFile*/ toFile, /**Function*/ callback)
{
- try
- {
- let promise = OS.File.copy(fromFile.path, toFile.path);
- promise.then(callback.bind(null, null), callback);
- }
- catch (e)
- {
- callback(e);
- }
+ attachCallback(
+ callWebExt("copyFile", fromFile.leafName, toFile.leafName),
+ callback,
+ () => LegacyIO.copyFile(fromFile, toFile, callback)
+ );
},
/**
* Renames a file within the same directory, will call callback when done.
*/
- renameFile: function(/**nsIFile*/ fromFile, /**String*/ newName, /**Function*/ callback)
+ renameFile(/**nsIFile*/ fromFile, /**String*/ newName, /**Function*/ callback)
{
- try
- {
- let toFile = fromFile.clone();
- toFile.leafName = newName;
- let promise = OS.File.move(fromFile.path, toFile.path);
- promise.then(callback.bind(null, null), callback);
- }
- catch(e)
- {
- callback(e);
- }
+ attachCallback(
+ callWebExt("renameFile", fromFile.leafName, newName),
+ callback,
+ () => LegacyIO.renameFile(fromFile, newName, callback)
+ );
},
/**
* Removes a file, will call callback when done.
*/
- removeFile: function(/**nsIFile*/ file, /**Function*/ callback)
+ removeFile(/**nsIFile*/ file, /**Function*/ callback)
{
- try
- {
- let promise = OS.File.remove(file.path);
- promise.then(callback.bind(null, null), callback);
- }
- catch(e)
- {
- callback(e);
- }
+ attachCallback(
+ callWebExt("removeFile", file.leafName),
+ callback,
+ () => LegacyIO.removeFile(file, callback)
+ );
},
/**
* Gets file information such as whether the file exists.
*/
- statFile: function(/**nsIFile*/ file, /**Function*/ callback)
+ statFile(/**nsIFile*/ file, /**Function*/ callback)
{
- try
- {
- let promise = OS.File.stat(file.path);
- promise.then(function onSuccess(info)
- {
- callback(null, {
- exists: true,
- isDirectory: info.isDir,
- isFile: !info.isDir,
- lastModified: info.lastModificationDate.getTime()
- });
- }, function onError(e)
- {
- if (e.becauseNoSuchFile)
- {
- callback(null, {
- exists: false,
- isDirectory: false,
- isFile: false,
- lastModified: 0
- });
- }
- else
- callback(e);
- });
- }
- catch(e)
- {
- callback(e);
- }
+ attachCallback(
+ callWebExt("statFile", file.leafName),
+ callback,
+ () => LegacyIO.statFile(file, callback)
+ );
}
-}
+};
Index: lib/legacyIO.js
===================================================================
copy from lib/io.js
copy to lib/legacyIO.js
--- a/lib/io.js
+++ b/lib/legacyIO.js
@@ -89,21 +89,17 @@ let IO = exports.IO =
{
let oldBuffer = buffer;
buffer = data.substr(index + 1);
data = data.substr(0, index + 1);
let lines = data.split(/[\r\n]+/);
lines.pop();
lines[0] = oldBuffer + lines[0];
for (let i = 0; i < lines.length; i++)
- {
- let promise = listener.process(lines[i]);
- if (promise)
- yield promise;
- }
+ listener.process(lines[i]);
}
finally
{
processing = false;
data = buffer;
buffer = "";
yield* onProgress(data);
Index: lib/prefs.json
===================================================================
--- a/lib/prefs.json
+++ b/lib/prefs.json
@@ -30,15 +30,14 @@
"clearStatsOnHistoryPurge": true,
"report_submiturl": "https://reports.adblockplus.org/submitReport?version=1&guid=%GUID%&lang=%LANG%",
"recentReports": [],
"hideContributeButton": false,
"blockableItemsSize": {"width": 200, "height": 200},
"notificationurl": "https://notification.adblockplus.org/notification.json",
"notificationdata": {},
"subscriptions_antiadblockurl": "https://easylist-downloads.adblockplus.org/antiadblockfilters.txt",
- "please_kill_startup_performance": false,
"suppress_first_run_page": false,
"notifications_showui": false,
"notifications_ignoredcategories": []
},
"preconfigurable": ["suppress_first_run_page"]
}
Index: lib/utils.js
===================================================================
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -321,28 +321,20 @@ let Utils = exports.Utils =
}
}
}
}
return selectedItem;
},
/**
- * Pauses code execution and allows events to be processed. Warning:
- * other extension code might execute, the extension might even shut down.
+ * DEPRECATED, do not use!
*/
yield: function()
{
- let {Prefs} = require("prefs");
- if (Prefs.please_kill_startup_performance)
- {
- this.yield = function() {};
- return;
- }
- return new Promise((resolve, reject) => Utils.runAsync(resolve));
},
/**
* Saves sidebar state before detaching/reattaching
*/
setParams: function(params)
{
sidebarParams = params;
Index: webextension/.eslintrc.json
===================================================================
new file mode 100644
--- /dev/null
+++ b/webextension/.eslintrc.json
@@ -0,0 +1,7 @@
+{
+ "extends": "eslint-config-eyeo",
+ "root": true,
+ "env": {
+ "webextensions": true
+ }
+}
Index: webextension/background.js
===================================================================
new file mode 100644
--- /dev/null
+++ b/webextension/background.js
@@ -0,0 +1,41 @@
+/*
+ * This file is part of Adblock Plus ,
+ * Copyright (C) 2006-2017 eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus. If not, see .
+ */
+
+"use strict";
+
+/* global IO */
+
+let port = browser.runtime.connect();
+
+port.onMessage.addListener(message =>
+{
+ IO[message.method](...message.args).then(result =>
+ {
+ port.postMessage({
+ id: message.id,
+ success: true,
+ result
+ });
+ }).catch(error =>
+ {
+ port.postMessage({
+ id: message.id,
+ success: false,
+ result: String(error)
+ });
+ });
+});
Index: webextension/io.js
===================================================================
new file mode 100644
--- /dev/null
+++ b/webextension/io.js
@@ -0,0 +1,109 @@
+/*
+ * This file is part of Adblock Plus ,
+ * Copyright (C) 2006-2017 eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus. If not, see .
+ */
+
+"use strict";
+
+(function(exports)
+{
+ const keyPrefix = "file:";
+
+ function fileToKey(fileName)
+ {
+ return keyPrefix + fileName;
+ }
+
+ function loadFile(file)
+ {
+ let key = fileToKey(file);
+
+ return browser.storage.local.get(key).then(items =>
+ {
+ if (items.hasOwnProperty(key))
+ return items[key];
+
+ throw "NoSuchFile";
+ });
+ }
+
+ function saveFile(file, data)
+ {
+ return browser.storage.local.set({
+ [fileToKey(file)]: {
+ content: Array.from(data),
+ lastModified: Date.now()
+ }
+ });
+ }
+
+ function removeFile(file)
+ {
+ return browser.storage.local.remove(fileToKey(file));
+ }
+
+ exports.IO =
+ {
+ readFromFile(file)
+ {
+ return loadFile(file).then(entry =>
+ {
+ return entry.content;
+ });
+ },
+
+ writeToFile(file, data)
+ {
+ return saveFile(file, data);
+ },
+
+ copyFile(fromFile, toFile)
+ {
+ return loadFile(fromFile).then(entry =>
+ {
+ return saveFile(toFile, entry.content);
+ });
+ },
+
+ renameFile(fromFile, newName)
+ {
+ return loadFile(fromFile).then(entry =>
+ {
+ return browser.storage.local.set({
+ [fileToKey(newName)]: entry
+ });
+ }).then(() =>
+ {
+ return removeFile(fromFile);
+ });
+ },
+
+ removeFile(file)
+ {
+ return removeFile(file);
+ },
+
+ statFile(file)
+ {
+ return loadFile(file).then(entry =>
+ {
+ return {
+ exists: true,
+ lastModified: entry.lastModified
+ };
+ });
+ }
+ };
+})(this);
Index: webextension/manifest.json
===================================================================
new file mode 100644
--- /dev/null
+++ b/webextension/manifest.json
@@ -0,0 +1,9 @@
+{
+ "manifest_version": 2,
+ "name": "Web Extensions I/O backend",
+ "version": "1.0",
+ "permissions": ["storage"],
+ "background": {
+ "scripts": ["background.js", "io.js"]
+ }
+}