--- a/devtools/client/storage/test/browser.ini
+++ b/devtools/client/storage/test/browser.ini
@@ -1,19 +1,23 @@
[DEFAULT]
tags = devtools
subsuite = devtools
support-files =
+ storage-cookies.html
storage-complex-values.html
storage-listings.html
storage-overflow.html
storage-search.html
storage-secured-iframe.html
storage-unsecured-iframe.html
storage-updates.html
head.js
[browser_storage_basic.js]
[browser_storage_dynamic_updates.js]
+[browser_storage_cookies_edit.js]
+[browser_storage_cookies_edit_keyboard.js]
+[browser_storage_cookies_tab_navigation.js]
[browser_storage_overflow.js]
[browser_storage_search.js]
[browser_storage_sidebar.js]
[browser_storage_values.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_cookies_edit.js
@@ -0,0 +1,24 @@
+/* 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/. */
+
+// Basic test to check the editing of cookies.
+
+"use strict";
+
+add_task(function*() {
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html");
+ yield gUI.table.once(TableWidget.EVENTS.FIELDS_EDITABLE);
+
+ showAllColumns(true);
+
+ yield editCell("test3", "name", "newTest3");
+ yield editCell("newTest3", "path", "/");
+ yield editCell("newTest3", "host", "test1.example.org");
+ yield editCell("newTest3", "expires", "Tue, 14 Feb 2040 17:41:14 GMT");
+ yield editCell("newTest3", "value", "newValue3");
+ yield editCell("newTest3", "isSecure", "true");
+ yield editCell("newTest3", "isHttpOnly", "true");
+
+ yield finishTests();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_cookies_edit_keyboard.js
@@ -0,0 +1,25 @@
+/* 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/. */
+
+// Basic test to check the editing of cookies with the keyboard.
+
+"use strict";
+
+add_task(function*() {
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html");
+ yield gUI.table.once(TableWidget.EVENTS.FIELDS_EDITABLE);
+
+ showAllColumns(true);
+
+ yield startCellEdit("test4", "name");
+ yield typeWithTerminator("test6", "VK_TAB");
+ yield typeWithTerminator("/", "VK_TAB");
+ yield typeWithTerminator(".example.org", "VK_TAB");
+ yield typeWithTerminator("Tue, 25 Dec 2040 12:00:00 GMT", "VK_TAB");
+ yield typeWithTerminator("test6value", "VK_TAB");
+ yield typeWithTerminator("false", "VK_TAB");
+ yield typeWithTerminator("false", "VK_TAB");
+
+ yield finishTests();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_cookies_tab_navigation.js
@@ -0,0 +1,26 @@
+/* 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/. */
+
+// Basic test to check cookie table tab navigation.
+
+"use strict";
+
+add_task(function*() {
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html");
+ yield gUI.table.once(TableWidget.EVENTS.FIELDS_EDITABLE);
+
+ showAllColumns(true);
+
+ yield startCellEdit("test1", "name");
+
+ PressKeyXTimes("VK_TAB", 18);
+ is(getCurrentEditorValue(), "value3",
+ "We have tabbed to the correct cell.");
+
+ PressKeyXTimes("VK_TAB", 18, {shiftKey: true});
+ is(getCurrentEditorValue(), "test1",
+ "We have shift-tabbed to the correct cell.");
+
+ yield finishTests();
+});
--- a/devtools/client/storage/test/head.js
+++ b/devtools/client/storage/test/head.js
@@ -6,16 +6,17 @@
/* eslint no-unused-vars: [2, {"vars": "local"}] */
var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
var { TargetFactory } = require("devtools/client/framework/target");
var promise = require("promise");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const {TableWidget} = require("devtools/client/shared/widgets/TableWidget");
const SPLIT_CONSOLE_PREF = "devtools.toolbox.splitconsoleEnabled";
const STORAGE_PREF = "devtools.storage.enabled";
const DUMPEMIT_PREF = "devtools.dump.emit";
const DEBUGGERLOG_PREF = "devtools.debugger.log";
// Allows Cache API to be working on usage `http` test page
const CACHES_ON_HTTP_PREF = "dom.caches.testing.enabled";
const PATH = "browser/devtools/client/storage/test/";
const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
@@ -244,22 +245,26 @@ function* finishTests() {
};
_getAllWindows(baseWindow);
return windows;
}
let windows = getAllWindows(content);
for (let win of windows) {
+ win.localStorage.clear();
+ win.sessionStorage.clear();
+
if (win.clear) {
yield win.clear();
}
}
});
+ Services.cookies.removeAll();
forceCollections();
finish();
}
// Sends a click event on the passed DOM node in an async manner
function* click(node) {
let def = promise.defer();
@@ -557,8 +562,244 @@ function once(target, eventName, useCapt
deferred.resolve.apply(deferred, aArgs);
}, useCapture);
break;
}
}
return deferred.promise;
}
+
+/**
+ * Get values for a row.
+ *
+ * @param {String} id
+ * The uniqueId of the given row.
+ * @param {Boolean} includeHidden
+ * Include hidden columns.
+ *
+ * @return {Object}
+ * An object of column names to values for the given row.
+ */
+function getRowValues(id, includeHidden = false) {
+ let cells = getRowCells(id, includeHidden);
+ let values = {};
+
+ for (let name in cells) {
+ let cell = cells[name];
+
+ values[name] = cell.value;
+ }
+
+ return values;
+}
+
+/**
+ * Get cells for a row.
+ *
+ * @param {String} id
+ * The uniqueId of the given row.
+ * @param {Boolean} includeHidden
+ * Include hidden columns.
+ *
+ * @return {Object}
+ * An object of column names to cells for the given row.
+ */
+function getRowCells(id, includeHidden = false) {
+ let doc = gPanelWindow.document;
+ let table = gUI.table;
+ let item = doc.querySelector(".table-widget-column#" + table.uniqueId +
+ " .table-widget-cell[value='" + id + "']");
+
+ if (!item) {
+ ok(false, "Row id '" + id + "' exists");
+ }
+
+ let index = table.columns.get(table.uniqueId).cellNodes.indexOf(item);
+ let cells = {};
+
+ for (let [name, column] of [...table.columns]) {
+ if (!includeHidden && column.column.parentNode.hidden) {
+ continue;
+ }
+ cells[name] = column.cellNodes[index];
+ }
+
+ return cells;
+}
+
+/**
+ * Get a cell value.
+ *
+ * @param {String} id
+ * The uniqueId of the row.
+ * @param {String} column
+ * The id of the column
+ *
+ * @yield {String}
+ * The cell value.
+ */
+function getCellValue(id, column) {
+ let row = getRowValues(id, true);
+
+ return row[column];
+}
+
+/**
+ * Edit a cell value. The cell is assumed to be in edit mode, see startCellEdit.
+ *
+ * @param {String} id
+ * The uniqueId of the row.
+ * @param {String} column
+ * The id of the column
+ * @param {String} newValue
+ * Replacement value.
+ * @param {Boolean} validate
+ * Validate result? Default true.
+ *
+ * @yield {String}
+ * The uniqueId of the changed row.
+ */
+function* editCell(id, column, newValue, validate = true) {
+ let row = getRowCells(id, true);
+ let editableFieldsEngine = gUI.table._editableFieldsEngine;
+
+ editableFieldsEngine.edit(row[column]);
+
+ return yield typeWithTerminator(newValue, "VK_RETURN", validate);
+}
+
+/**
+ * Begin edit mode for a cell.
+ *
+ * @param {String} id
+ * The uniqueId of the row.
+ * @param {String} column
+ * The id of the column
+ * @param {Boolean} selectText
+ * Select text? Default true.
+ */
+function* startCellEdit(id, column, selectText = true) {
+ let row = getRowCells(id, true);
+ let editableFieldsEngine = gUI.table._editableFieldsEngine;
+ let cell = row[column];
+
+ info("Selecting row " + id);
+ gUI.table.selectedRow = id;
+
+ info("Starting cell edit (" + id + ", " + column + ")");
+ editableFieldsEngine.edit(cell);
+
+ if (!selectText) {
+ let textbox = gUI.table._editableFieldsEngine.textbox;
+ textbox.selectionEnd = textbox.selectionStart;
+ }
+}
+
+/**
+ * Check a cell value.
+ *
+ * @param {String} id
+ * The uniqueId of the row.
+ * @param {String} column
+ * The id of the column
+ * @param {String} expected
+ * Expected value.
+ */
+function checkCell(id, column, expected) {
+ is(getCellValue(id, column), expected,
+ column + " column has the right value for " + id);
+}
+
+/**
+ * Show or hide a column.
+ *
+ * @param {String} id
+ * The uniqueId of the given column.
+ * @param {Boolean} state
+ * true = show, false = hide
+ */
+function showColumn(id, state) {
+ let columns = gUI.table.columns;
+ let column = columns.get(id);
+
+ if (state) {
+ column.wrapper.removeAttribute("hidden");
+ } else {
+ column.wrapper.setAttribute("hidden", true);
+ }
+}
+
+/**
+ * Show or hide all columns.
+ *
+ * @param {Boolean} state
+ * true = show, false = hide
+ */
+function showAllColumns(state) {
+ let columns = gUI.table.columns;
+
+ for (let [id] of columns) {
+ showColumn(id, state);
+ }
+}
+
+/**
+ * Type a string in the currently selected editor and then wait for the row to
+ * be updated.
+ *
+ * @param {String} str
+ * The string to type.
+ * @param {String} terminator
+ * The terminating key e.g. VK_RETURN or VK_TAB
+ * @param {Boolean} validate
+ * Validate result? Default true.
+ */
+function* typeWithTerminator(str, terminator, validate = true) {
+ let editableFieldsEngine = gUI.table._editableFieldsEngine;
+ let textbox = editableFieldsEngine.textbox;
+ let colName = textbox.closest(".table-widget-column").id;
+
+ let changeExpected = str !== textbox.value;
+
+ if (!changeExpected) {
+ return editableFieldsEngine.currentTarget.getAttribute("data-id");
+ }
+
+ info("Typing " + str);
+ EventUtils.sendString(str);
+
+ info("Pressing " + terminator);
+ EventUtils.synthesizeKey(terminator, {});
+
+ if (validate) {
+ info("Validating results... waiting for ROW_EDIT event.");
+ let uniqueId = yield gUI.table.once(TableWidget.EVENTS.ROW_EDIT);
+
+ checkCell(uniqueId, colName, str);
+ return uniqueId;
+ }
+
+ return yield gUI.table.once(TableWidget.EVENTS.ROW_EDIT);
+}
+
+function getCurrentEditorValue() {
+ let editableFieldsEngine = gUI.table._editableFieldsEngine;
+ let textbox = editableFieldsEngine.textbox;
+
+ return textbox.value;
+}
+
+/**
+ * Press a key x times.
+ *
+ * @param {String} key
+ * The key to press e.g. VK_RETURN or VK_TAB
+ * @param {Number} x
+ * The number of times to press the key.
+ * @param {Object} modifiers
+ * The event modifier e.g. {shiftKey: true}
+ */
+function PressKeyXTimes(key, x, modifiers = {}) {
+ for (let i = 0; i < x; i++) {
+ EventUtils.synthesizeKey(key, modifiers);
+ }
+}
--- a/devtools/client/storage/test/storage-complex-values.html
+++ b/devtools/client/storage/test/storage-complex-values.html
@@ -100,23 +100,16 @@ function deleteDB(dbName) {
});
}
window.setup = function*() {
yield idbGenerator();
};
window.clear = function*() {
- document.cookie = "c1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/browser";
- document.cookie = "cs2=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
-
- localStorage.clear();
- sessionStorage.clear();
-
yield deleteDB("idb1");
yield deleteDB("idb2");
- dump("removed cookies, localStorage, sessionStorage and indexedDB data " +
- "from " + document.location + "\n");
+ dump("removed indexedDB data from " + document.location + "\n");
};
</script>
</body>
</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/storage/test/storage-cookies.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ Bug 970517 - Storage inspector front end - tests
+ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Storage inspector cookie test</title>
+ </head>
+ <body>
+ <script type="application/javascript;version=1.7">
+ "use strict";
+ let partialHostname = location.hostname.match(/^[^.]+(\..*)$/)[1];
+ let expiresIn24Hours = new Date(Date.now() + 60 * 60 * 24 * 1000).toUTCString();
+ for (let i = 1; i <= 5; i++) {
+ let cookieString = "test" + i + "=value" + i +
+ ";expires=" + expiresIn24Hours + ";path=/browser";
+ if (i % 2) {
+ cookieString += ";domain=.example.org";
+ }
+ document.cookie = cookieString;
+ }
+ </script>
+ </body>
+</html>
--- a/devtools/client/storage/test/storage-listings.html
+++ b/devtools/client/storage/test/storage-listings.html
@@ -109,29 +109,18 @@ let cacheGenerator = function*() {
};
window.setup = function*() {
yield idbGenerator();
yield cacheGenerator();
};
window.clear = function*() {
- document.cookie = "c1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/browser";
- document.cookie =
- "c3=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; secure=true";
- document.cookie =
- "cs2=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=" +
- partialHostname;
-
- localStorage.clear();
- sessionStorage.clear();
-
yield deleteDB("idb1");
yield deleteDB("idb2");
yield caches.delete("plop");
- dump("removed cookies, localStorage, sessionStorage and indexedDB data " +
- "from " + document.location + "\n");
+ dump("removed indexedDB and cache data from " + document.location + "\n");
};
</script>
</body>
</html>
--- a/devtools/client/storage/test/storage-overflow.html
+++ b/devtools/client/storage/test/storage-overflow.html
@@ -5,18 +5,15 @@ Bug 1171903 - Storage Inspector endless
-->
<head>
<meta charset="utf-8">
<title>Storage inspector endless scrolling test</title>
</head>
<body>
<script type="text/javascript;version=1.8">
"use strict";
-window.clear = () => {
- localStorage.clear();
-};
for (let i = 0; i < 160; i++) {
localStorage.setItem(`item-${i}`, `value-${i}`);
}
</script>
</body>
</html>
--- a/devtools/client/storage/test/storage-search.html
+++ b/devtools/client/storage/test/storage-search.html
@@ -5,19 +5,16 @@ Bug 1224115 - Storage Inspector table fi
-->
<head>
<meta charset="utf-8">
<title>Storage inspector table filtering test</title>
</head>
<body>
<script type="text/javascript;version=1.8">
"use strict";
-window.clear = () => {
- localStorage.clear();
-};
localStorage.setItem("01234", "56789");
localStorage.setItem("ANIMAL", "hOrSe");
localStorage.setItem("FOO", "bArBaz");
localStorage.setItem("food", "energy bar");
localStorage.setItem("money", "##$$$**");
localStorage.setItem("sport", "football");
localStorage.setItem("year", "2016");
--- a/devtools/client/storage/test/storage-secured-iframe.html
+++ b/devtools/client/storage/test/storage-secured-iframe.html
@@ -75,22 +75,16 @@ function deleteDB(dbName) {
});
}
window.setup = function*() {
yield idbGenerator();
};
window.clear = function*() {
- document.cookie = "sc1=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
-
- localStorage.clear();
- sessionStorage.clear();
-
yield deleteDB("idb-s1");
yield deleteDB("idb-s2");
- dump("removed cookies, localStorage, sessionStorage and indexedDB data " +
- "from " + document.location + "\n");
+ dump("removed indexedDB data from " + document.location + "\n");
};
</script>
</body>
</html>
--- a/devtools/client/storage/test/storage-unsecured-iframe.html
+++ b/devtools/client/storage/test/storage-unsecured-iframe.html
@@ -9,22 +9,11 @@ Iframe for testing multiple host detetio
<body>
<script>
"use strict";
document.cookie = "uc1=foobar; domain=.example.org; path=/; secure=true";
localStorage.setItem("iframe-u-ls1", "foobar");
sessionStorage.setItem("iframe-u-ss1", "foobar1");
sessionStorage.setItem("iframe-u-ss2", "foobar2");
console.log("added cookies and stuff from unsecured iframe");
-
-window.clear = function*() {
- document.cookie = "uc1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; " +
- "domain=.example.org; secure=true";
-
- localStorage.clear();
- sessionStorage.clear();
-
- dump("removed cookies, localStorage and sessionStorage from " +
- document.location + "\n");
-};
</script>
</body>
</html>
--- a/devtools/client/storage/test/storage-updates.html
+++ b/devtools/client/storage/test/storage-updates.html
@@ -27,28 +27,25 @@ window.addCookie = function(name, value,
document.cookie = cookieString;
};
window.removeCookie = function(name, path) {
document.cookie =
name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=" + path;
};
+/**
+ * We keep this method here even though these items are automatically cleared
+ * after the test is complete. this is so that the store-objects-cleared event
+ * can be tested.
+ */
window.clear = function*() {
- let cookies = document.cookie;
- for (let cookie of cookies.split(";")) {
- removeCookie(cookie.split("=")[0]);
- removeCookie(cookie.split("=")[0], "/browser");
- }
-
- localStorage.clear();
sessionStorage.clear();
- dump("removed cookies, localStorage and sessionStorage from " +
- document.location + "\n");
+ dump("removed sessionStorage from " + document.location + "\n");
};
window.onload = function() {
addCookie("c1", "1.2.3.4.5.6.7", "/browser");
addCookie("c2", "foobar", "/browser");
localStorage.setItem("ls1", "testing");
localStorage.setItem("ls2", "testing");
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -23,16 +23,20 @@ var gTrackedMessageManager = new Map();
// Maximum number of cookies/local storage key-value-pairs that can be sent
// over the wire to the client in one request.
const MAX_STORE_OBJECT_COUNT = 50;
// Delay for the batch job that sends the accumulated update packets to the
// client (ms).
const BATCH_DELAY = 200;
+// MAX_COOKIE_EXPIRY should be 2^63-1, but JavaScript can't handle that
+// precision.
+const MAX_COOKIE_EXPIRY = Math.pow(2, 62);
+
// A RegExp for characters that cannot appear in a file/directory name. This is
// used to sanitize the host name for indexed db to lookup whether the file is
// present in <profileDir>/storage/default/ location
var illegalFileNameCharacters = [
"[",
// Control characters \001 to \036
"\\x00-\\x24",
// Special characters
@@ -561,19 +565,19 @@ StorageActors.createActor({
}
}
},
/**
* Notification observer for "cookie-change".
*
* @param subject
- * {nsiCookie|[nsiCookie]} A single nsiCookie object or a list of it
- * depending on the action. Array is only in case of "batch-deleted"
- * action.
+ * {Cookie|[Array]} A JSON parsed object containing either a single
+ * cookie representation or an array. Array is only in case of
+ * a "batch-deleted" action.
* @param {string} topic
* The topic of the notification.
* @param {string} action
* Additional data associated with the notification. Its the type of
* cookie change in the "cookie-change" topic.
*/
onCookieChanged: function(subject, topic, action) {
if (topic !== "cookie-changed" ||
@@ -627,23 +631,62 @@ StorageActors.createActor({
case "reload":
this.storageActor.update("reloaded", "cookies", hosts);
break;
}
return null;
},
+ /**
+ * This method marks the table as editable.
+ *
+ * @return {Array}
+ * An array of column header ids.
+ */
+ getEditableFields: method(Task.async(function*() {
+ return [
+ "name",
+ "path",
+ "host",
+ "expires",
+ "value",
+ "isSecure",
+ "isHttpOnly"
+ ];
+ }), {
+ request: {},
+ response: {
+ value: RetVal("json")
+ }
+ }),
+
+ /**
+ * Pass the editItem command from the content to the chrome process.
+ *
+ * @param {Object} data
+ * See editCookie() for format details.
+ */
+ editItem: method(Task.async(function*(data) {
+ this.editCookie(data);
+ }), {
+ request: {
+ data: Arg(0, "json"),
+ },
+ response: {}
+ }),
+
maybeSetupChildProcess: function() {
cookieHelpers.onCookieChanged = this.onCookieChanged.bind(this);
if (!DebuggerServer.isInChildProcess) {
this.getCookiesFromHost = cookieHelpers.getCookiesFromHost;
this.addCookieObservers = cookieHelpers.addCookieObservers;
this.removeCookieObservers = cookieHelpers.removeCookieObservers;
+ this.editCookie = cookieHelpers.editCookie;
return;
}
const { sendSyncMessage, addMessageListener } =
this.conn.parentMessageManager;
this.conn.setupInParent({
module: "devtools/server/actors/storage",
@@ -651,16 +694,18 @@ StorageActors.createActor({
});
this.getCookiesFromHost =
callParentProcess.bind(null, "getCookiesFromHost");
this.addCookieObservers =
callParentProcess.bind(null, "addCookieObservers");
this.removeCookieObservers =
callParentProcess.bind(null, "removeCookieObservers");
+ this.editCookie =
+ callParentProcess.bind(null, "editCookie");
addMessageListener("storage:storage-cookie-request-child",
cookieHelpers.handleParentRequest);
function callParentProcess(methodName, ...args) {
let reply = sendSyncMessage("storage:storage-cookie-request-parent", {
method: methodName,
args: args
@@ -690,35 +735,159 @@ var cookieHelpers = {
host = "";
}
let cookies = Services.cookies.getCookiesFromHost(host);
let store = [];
while (cookies.hasMoreElements()) {
let cookie = cookies.getNext().QueryInterface(Ci.nsICookie2);
+
store.push(cookie);
}
return store;
},
+ /**
+ * Apply the results of a cookie edit.
+ *
+ * @param {Object} data
+ * An object in the following format:
+ * {
+ * field: "value",
+ * key: "name",
+ * oldValue: "%7BHello%7D",
+ * newValue: "%7BHelloo%7D",
+ * items: {
+ * name: "optimizelyBuckets",
+ * path: "/",
+ * host: ".mozilla.org",
+ * expires: "Mon, 02 Jun 2025 12:37:37 GMT",
+ * creationTime: "Tue, 18 Nov 2014 16:21:18 GMT",
+ * lastAccessed: "Wed, 17 Feb 2016 10:06:23 GMT",
+ * value: "%7BHelloo%7D",
+ * isDomain: "true",
+ * isSecure: "false",
+ * isHttpOnly: "false"
+ * }
+ * }
+ */
+ editCookie: function(data) {
+ let {field, oldValue, newValue} = data;
+ let origName = field === "name" ? oldValue : data.items.name;
+ let origHost = field === "host" ? oldValue : data.items.host;
+ let origPath = field === "path" ? oldValue : data.items.path;
+ let cookie = null;
+
+ let enumerator = Services.cookies.getCookiesFromHost(origHost);
+ while (enumerator.hasMoreElements()) {
+ let nsiCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ if (nsiCookie.name === origName && nsiCookie.host === origHost) {
+ cookie = {
+ host: nsiCookie.host,
+ path: nsiCookie.path,
+ name: nsiCookie.name,
+ value: nsiCookie.value,
+ isSecure: nsiCookie.isSecure,
+ isHttpOnly: nsiCookie.isHttpOnly,
+ isSession: nsiCookie.isSession,
+ expires: nsiCookie.expires,
+ originAttributes: nsiCookie.originAttributes
+ };
+ break;
+ }
+ }
+
+ if (!cookie) {
+ return;
+ }
+
+ // If the date is expired set it for 1 minute in the future.
+ let now = new Date();
+ if (!cookie.isSession && (cookie.expires * 1000) <= now) {
+ let tenSecondsFromNow = (now.getTime() + 10 * 1000) / 1000;
+
+ cookie.expires = tenSecondsFromNow;
+ }
+
+ switch (field) {
+ case "isSecure":
+ case "isHttpOnly":
+ case "isSession":
+ newValue = newValue === "true";
+ break;
+
+ case "expires":
+ newValue = Date.parse(newValue) / 1000;
+
+ if (isNaN(newValue)) {
+ newValue = MAX_COOKIE_EXPIRY;
+ }
+ break;
+
+ case "host":
+ case "name":
+ case "path":
+ // Remove the edited cookie.
+ Services.cookies.remove(origHost, origName, origPath,
+ cookie.originAttributes, false);
+ break;
+ }
+
+ // Apply changes.
+ cookie[field] = newValue;
+
+ // cookie.isSession is not always set correctly on session cookies so we
+ // need to trust cookie.expires instead.
+ cookie.isSession = !cookie.expires;
+
+ // Add the edited cookie.
+ Services.cookies.add(
+ cookie.host,
+ cookie.path,
+ cookie.name,
+ cookie.value,
+ cookie.isSecure,
+ cookie.isHttpOnly,
+ cookie.isSession,
+ cookie.isSession ? MAX_COOKIE_EXPIRY : cookie.expires
+ );
+ },
+
addCookieObservers: function() {
Services.obs.addObserver(cookieHelpers, "cookie-changed", false);
return null;
},
removeCookieObservers: function() {
Services.obs.removeObserver(cookieHelpers, "cookie-changed", false);
return null;
},
observe: function(subject, topic, data) {
+ if (!subject) {
+ return;
+ }
+
switch (topic) {
case "cookie-changed":
+ if (data === "batch-deleted") {
+ let cookiesNoInterface = subject.QueryInterface(Ci.nsIArray);
+ let cookies = [];
+
+ for (let i = 0; i < cookiesNoInterface.length; i++) {
+ let cookie = cookiesNoInterface.queryElementAt(i, Ci.nsICookie2);
+ cookies.push(cookie);
+ }
+ cookieHelpers.onCookieChanged(cookies, topic, data);
+
+ return;
+ }
+
let cookie = subject.QueryInterface(Ci.nsICookie2);
cookieHelpers.onCookieChanged(cookie, topic, data);
break;
}
},
handleParentRequest: function(msg) {
switch (msg.json.method) {
@@ -735,16 +904,19 @@ var cookieHelpers = {
case "getCookiesFromHost":
let host = msg.data.args[0];
let cookies = cookieHelpers.getCookiesFromHost(host);
return JSON.stringify(cookies);
case "addCookieObservers":
return cookieHelpers.addCookieObservers();
case "removeCookieObservers":
return cookieHelpers.removeCookieObservers();
+ case "editCookie":
+ let rowdata = msg.data.args[0];
+ return cookieHelpers.editCookie(rowdata);
default:
console.error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD", msg.json.method);
throw new Error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD");
}
},
};
/**