From 32230477c611d6baddb4f2f6caf5c55c8b8cc198 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Wed, 28 Feb 2018 08:41:01 +0100 Subject: DevTools - Inspector - tooltip get element width/height wrong when browser zoomed https://github.com/MoonchildProductions/moebius/pull/54 --- devtools/server/actors/highlighters/box-model.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'devtools/server/actors') diff --git a/devtools/server/actors/highlighters/box-model.js b/devtools/server/actors/highlighters/box-model.js index 35f201a04..ae4284424 100644 --- a/devtools/server/actors/highlighters/box-model.js +++ b/devtools/server/actors/highlighters/box-model.js @@ -15,7 +15,10 @@ const { isNodeValid, moveInfobar, } = require("./utils/markup"); -const { setIgnoreLayoutChanges } = require("devtools/shared/layout/utils"); +const { + setIgnoreLayoutChanges, + getCurrentZoom, + } = require("devtools/shared/layout/utils"); const inspector = require("devtools/server/actors/inspector"); const nodeConstants = require("devtools/shared/dom-node-constants"); @@ -670,10 +673,14 @@ BoxModelHighlighter.prototype = extend(AutoRefreshHighlighter.prototype, { pseudos += ":" + pseudo; } - let rect = this._getOuterQuad("border").bounds; - let dim = parseFloat(rect.width.toPrecision(6)) + + // We want to display the original `width` and `height`, instead of the ones affected + // by any zoom. Since the infobar can be displayed also for text nodes, we can't + // access the computed style for that, and this is why we recalculate them here. + let zoom = getCurrentZoom(this.win); + let { width, height } = this._getOuterQuad("border").bounds; + let dim = parseFloat((width / zoom).toPrecision(6)) + " \u00D7 " + - parseFloat(rect.height.toPrecision(6)); + parseFloat((height / zoom).toPrecision(6)); this.getElement("infobar-tagname").setTextContent(displayName); this.getElement("infobar-id").setTextContent(id); -- cgit v1.2.3 From 12ea5683594229407ae1852969b6417a59ae16e1 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Thu, 1 Mar 2018 13:13:33 +0100 Subject: DevTools - style editor - StyleSheetsActor should use the parent tabActor to retrieve the list of windows and react to new/removed windows https://github.com/MoonchildProductions/moebius/pull/124 --- devtools/server/actors/stylesheets.js | 120 +++++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 31 deletions(-) (limited to 'devtools/server/actors') diff --git a/devtools/server/actors/stylesheets.js b/devtools/server/actors/stylesheets.js index f20634e6c..7fcbca8c4 100644 --- a/devtools/server/actors/stylesheets.js +++ b/devtools/server/actors/stylesheets.js @@ -13,7 +13,6 @@ const events = require("sdk/event/core"); const protocol = require("devtools/shared/protocol"); const {LongStringActor} = require("devtools/server/actors/string"); const {fetch} = require("devtools/shared/DevToolsUtils"); -const {listenOnce} = require("devtools/shared/async-utils"); const {originalSourceSpec, mediaRuleSpec, styleSheetSpec, styleSheetsSpec} = require("devtools/shared/specs/stylesheets"); const {SourceMapConsumer} = require("source-map"); @@ -251,7 +250,7 @@ var StyleSheetActor = protocol.ActorClassWithSpec(styleSheetSpec, { }, destroy: function () { - if (this._transitionTimeout) { + if (this._transitionTimeout && this.window) { this.window.clearTimeout(this._transitionTimeout); removePseudoClassLock( this.document.documentElement, TRANSITION_PSEUDO_CLASS); @@ -801,6 +800,64 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { protocol.Actor.prototype.initialize.call(this, null); this.parentActor = tabActor; + + this._onNewStyleSheetActor = this._onNewStyleSheetActor.bind(this); + this._onSheetAdded = this._onSheetAdded.bind(this); + this._onWindowReady = this._onWindowReady.bind(this); + + events.on(this.parentActor, "stylesheet-added", this._onNewStyleSheetActor); + events.on(this.parentActor, "window-ready", this._onWindowReady); + + // We listen for StyleSheetApplicableStateChanged rather than + // StyleSheetAdded, because the latter will be sent before the + // rules are ready. Using the former (with a check to ensure that + // the sheet is enabled) ensures that the sheet is ready before we + // try to make an actor for it. + this.parentActor.chromeEventHandler + .addEventListener("StyleSheetApplicableStateChanged", this._onSheetAdded, true); + + // This is used when creating a new style sheet, so that we can + // pass the correct flag when emitting our stylesheet-added event. + // See addStyleSheet and _onNewStyleSheetActor for more details. + this._nextStyleSheetIsNew = false; + }, + + destroy: function () { + for (let win of this.parentActor.windows) { + // This flag only exists for devtools, so we are free to clear + // it when we're done. + win.document.styleSheetChangeEventsEnabled = false; + } + + events.off(this.parentActor, "stylesheet-added", this._onNewStyleSheetActor); + events.off(this.parentActor, "window-ready", this._onWindowReady); + + this.parentActor.chromeEventHandler.removeEventListener("StyleSheetAdded", + this._onSheetAdded, true); + + protocol.Actor.prototype.destroy.call(this); + }, + + /** + * Event handler that is called when a the tab actor emits window-ready. + * + * @param {Event} evt + * The triggering event. + */ + _onWindowReady: function (evt) { + this._addStyleSheets(evt.window); + }, + + /** + * Event handler that is called when a the tab actor emits stylesheet-added. + * + * @param {StyleSheetActor} actor + * The new style sheet actor. + */ + _onNewStyleSheetActor: function (actor) { + // Forward it to the client side. + events.emit(this, "stylesheet-added", actor, this._nextStyleSheetIsNew); + this._nextStyleSheetIsNew = false; }, /** @@ -808,23 +865,11 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { * all the style sheets in this document. */ getStyleSheets: Task.async(function* () { - // Iframe document can change during load (bug 1171919). Track their windows - // instead. - let windows = [this.window]; let actors = []; - for (let win of windows) { + for (let win of this.parentActor.windows) { let sheets = yield this._addStyleSheets(win); actors = actors.concat(sheets); - - // Recursively handle style sheets of the documents in iframes. - for (let iframe of win.document.querySelectorAll("iframe, browser, frame")) { - if (iframe.contentDocument && iframe.contentWindow) { - // Sometimes, iframes don't have any document, like the - // one that are over deeply nested (bug 285395) - windows.push(iframe.contentWindow); - } - } } return actors; }), @@ -832,15 +877,13 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { /** * Check if we should be showing this stylesheet. * - * @param {Document} doc - * Document for which we're checking * @param {DOMCSSStyleSheet} sheet * Stylesheet we're interested in * * @return boolean * Whether the stylesheet should be listed. */ - _shouldListSheet: function (doc, sheet) { + _shouldListSheet: function (sheet) { // Special case about:PreferenceStyleSheet, as it is generated on the // fly and the URI is not registered with the about: handler. // https://bugzilla.mozilla.org/show_bug.cgi?id=935803#c37 @@ -851,6 +894,22 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { return true; }, + /** + * Event handler that is called when a new style sheet is added to + * a document. In particular, StyleSheetApplicableStateChanged is + * listened for, because StyleSheetAdded is sent too early, before + * the rules are ready. + * + * @param {Event} evt + * The triggering event. + */ + _onSheetAdded: function (evt) { + let sheet = evt.stylesheet; + if (this._shouldListSheet(sheet)) { + this.parentActor.createStyleSheetActor(sheet); + } + }, + /** * Add all the stylesheets for the document in this window to the map and * create an actor for each one if not already created. @@ -865,24 +924,16 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { { return Task.spawn(function* () { let doc = win.document; - // readyState can be uninitialized if an iframe has just been created but - // it has not started to load yet. - if (doc.readyState === "loading" || doc.readyState === "uninitialized") { - // Wait for the document to load first. - yield listenOnce(win, "DOMContentLoaded", true); - - // Make sure we have the actual document for this window. If the - // readyState was initially uninitialized, the initial dummy document - // was replaced with the actual document (bug 1171919). - doc = win.document; - } + // We have to set this flag in order to get the + // StyleSheetApplicableStateChanged events. See Document.webidl. + doc.styleSheetChangeEventsEnabled = true; let isChrome = Services.scriptSecurityManager.isSystemPrincipal(doc.nodePrincipal); let styleSheets = isChrome ? DOMUtils.getAllStyleSheets(doc) : doc.styleSheets; let actors = []; for (let i = 0; i < styleSheets.length; i++) { let sheet = styleSheets[i]; - if (!this._shouldListSheet(doc, sheet)) { + if (!this._shouldListSheet(sheet)) { continue; } @@ -917,7 +968,7 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) { // Associated styleSheet may be null if it has already been seen due // to duplicate @imports for the same URL. - if (!rule.styleSheet || !this._shouldListSheet(doc, rule.styleSheet)) { + if (!rule.styleSheet || !this._shouldListSheet(rule.styleSheet)) { continue; } let actor = this.parentActor.createStyleSheetActor(rule.styleSheet); @@ -948,6 +999,13 @@ var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, { * Object with 'styelSheet' property for form on new actor. */ addStyleSheet: function (text) { + // This is a bit convoluted. The style sheet actor may be created + // by a notification from platform. In this case, we can't easily + // pass the "new" flag through to createStyleSheetActor, so we set + // a flag locally and check it before sending an event to the + // client. See |_onNewStyleSheetActor|. + this._nextStyleSheetIsNew = true; + let parent = this.document.documentElement; let style = this.document.createElementNS("http://www.w3.org/1999/xhtml", "style"); style.setAttribute("type", "text/css"); -- cgit v1.2.3 From caebf13372415e59bd36c49fbee934e25ec46a32 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Thu, 1 Mar 2018 13:50:06 +0100 Subject: Checking NS_BINDING_ABORTED to avoid dispatching navigate event https://github.com/MoonchildProductions/moebius/pull/302 --- devtools/server/actors/webbrowser.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'devtools/server/actors') diff --git a/devtools/server/actors/webbrowser.js b/devtools/server/actors/webbrowser.js index 1808895b1..dffe49b91 100644 --- a/devtools/server/actors/webbrowser.js +++ b/devtools/server/actors/webbrowser.js @@ -2501,7 +2501,11 @@ DebuggerProgressListener.prototype = { if (isWindow && isStop) { // Don't dispatch "navigate" event just yet when there is a redirect to // about:neterror page. - if (request.status != Cr.NS_OK) { + // Navigating to about:neterror will make `status` be something else than NS_OK. + // But for some error like NS_BINDING_ABORTED we don't want to emit any `navigate` + // event as the page load has been cancelled and the related page document is going + // to be a dead wrapper. + if (request.status != Cr.NS_OK && request.status != Cr.NS_BINDING_ABORTED) { // Instead, listen for DOMContentLoaded as about:neterror is loaded // with LOAD_BACKGROUND flags and never dispatches load event. // That may be the same reason why there is no onStateChange event -- cgit v1.2.3 From 390894c822f1b163f16744646372a28c0d93a89e Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 2 Mar 2018 13:36:16 +0100 Subject: Bug 1146194: Multiple cookies with the same name not shown Issue #31 --- devtools/server/actors/storage.js | 114 +++++++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 38 deletions(-) (limited to 'devtools/server/actors') diff --git a/devtools/server/actors/storage.js b/devtools/server/actors/storage.js index 572cd6b68..894e282f0 100644 --- a/devtools/server/actors/storage.js +++ b/devtools/server/actors/storage.js @@ -15,6 +15,12 @@ const {isWindowIncluded} = require("devtools/shared/layout/utils"); const specs = require("devtools/shared/specs/storage"); const { Task } = require("devtools/shared/task"); +// GUID to be used as a separator in compound keys. This must match the same +// constant in devtools/client/storage/ui.js, +// devtools/client/storage/test/head.js and +// devtools/server/tests/browser/head.js +const SEPARATOR_GUID = "{9d414cc5-8319-0a04-0586-c0a6ae01670a}"; + loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm"); loader.lazyImporter(this, "Sqlite", "resource://gre/modules/Sqlite.jsm"); @@ -460,11 +466,13 @@ StorageActors.createActor({ } return { + uniqueKey: `${cookie.name}${SEPARATOR_GUID}${cookie.host}` + + `${SEPARATOR_GUID}${cookie.path}`, name: cookie.name, - path: cookie.path || "", host: cookie.host || "", + path: cookie.path || "", - // because expires is in seconds + // because creationTime is in micro seconds expires: (cookie.expires || 0) * 1000, // because it is in micro seconds @@ -488,7 +496,10 @@ StorageActors.createActor({ for (let cookie of cookies) { if (this.isCookieAtHost(cookie, host)) { - this.hostVsStores.get(host).set(cookie.name, cookie); + let uniqueKey = `${cookie.name}${SEPARATOR_GUID}${cookie.host}` + + `${SEPARATOR_GUID}${cookie.path}`; + + this.hostVsStores.get(host).set(uniqueKey, cookie); } } }, @@ -521,8 +532,11 @@ StorageActors.createActor({ case "changed": if (hosts.length) { for (let host of hosts) { - this.hostVsStores.get(host).set(subject.name, subject); - data[host] = [subject.name]; + let uniqueKey = `${subject.name}${SEPARATOR_GUID}${subject.host}` + + `${SEPARATOR_GUID}${subject.path}`; + + this.hostVsStores.get(host).set(uniqueKey, subject); + data[host] = [uniqueKey]; } this.storageActor.update(action, "cookies", data); } @@ -531,8 +545,11 @@ StorageActors.createActor({ case "deleted": if (hosts.length) { for (let host of hosts) { - this.hostVsStores.get(host).delete(subject.name); - data[host] = [subject.name]; + let uniqueKey = `${subject.name}${SEPARATOR_GUID}${subject.host}` + + `${SEPARATOR_GUID}${subject.path}`; + + this.hostVsStores.get(host).delete(uniqueKey); + data[host] = [uniqueKey]; } this.storageActor.update("deleted", "cookies", data); } @@ -543,8 +560,11 @@ StorageActors.createActor({ for (let host of hosts) { let stores = []; for (let cookie of subject) { - this.hostVsStores.get(host).delete(cookie.name); - stores.push(cookie.name); + let uniqueKey = `${cookie.name}${SEPARATOR_GUID}${cookie.host}` + + `${SEPARATOR_GUID}${cookie.path}`; + + this.hostVsStores.get(host).delete(uniqueKey); + stores.push(uniqueKey); } data[host] = stores; } @@ -566,15 +586,17 @@ StorageActors.createActor({ getFields: Task.async(function* () { return [ - { name: "name", editable: 1}, - { name: "path", editable: 1}, - { name: "host", editable: 1}, - { name: "expires", editable: 1}, - { name: "lastAccessed", editable: 0}, - { name: "value", editable: 1}, - { name: "isDomain", editable: 0}, - { name: "isSecure", editable: 1}, - { name: "isHttpOnly", editable: 1} + { name: "uniqueKey", editable: false, private: true }, + { name: "name", editable: true, hidden: false }, + { name: "host", editable: true, hidden: false }, + { name: "path", editable: true, hidden: false }, + { name: "expires", editable: true, hidden: false }, + { name: "lastAccessed", editable: false, hidden: false }, + { name: "creationTime", editable: false, hidden: true }, + { name: "value", editable: true, hidden: false }, + { name: "isDomain", editable: false, hidden: true }, + { name: "isSecure", editable: true, hidden: true }, + { name: "isHttpOnly", editable: true, hidden: false } ]; }), @@ -696,7 +718,7 @@ var cookieHelpers = { * { * host: "http://www.mozilla.org", * field: "value", - * key: "name", + * editCookie: "name", * oldValue: "%7BHello%7D", * newValue: "%7BHelloo%7D", * items: { @@ -720,10 +742,13 @@ var cookieHelpers = { let origPath = field === "path" ? oldValue : data.items.path; let cookie = null; - let enumerator = Services.cookies.getCookiesFromHost(origHost, data.originAttributes || {}); + let enumerator = + Services.cookies.getCookiesFromHost(origHost, data.originAttributes || {}); while (enumerator.hasMoreElements()) { let nsiCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2); - if (nsiCookie.name === origName && nsiCookie.host === origHost) { + if (nsiCookie.name === origName && + nsiCookie.host === origHost && + nsiCookie.path === origPath) { cookie = { host: nsiCookie.host, path: nsiCookie.path, @@ -743,7 +768,7 @@ var cookieHelpers = { return; } - // If the date is expired set it for 1 minute in the future. + // If the date is expired set it for 10 seconds in the future. let now = new Date(); if (!cookie.isSession && (cookie.expires * 1000) <= now) { let tenSecondsFromNow = (now.getTime() + 10 * 1000) / 1000; @@ -797,6 +822,15 @@ var cookieHelpers = { }, _removeCookies(host, opts = {}) { + // We use a uniqueId to emulate compound keys for cookies. We need to + // extract the cookie name to remove the correct cookie. + if (opts.name) { + let split = opts.name.split(SEPARATOR_GUID); + + opts.name = split[0]; + opts.path = split[2]; + } + function hostMatches(cookieHost, matchHost) { if (cookieHost == null) { return matchHost == null; @@ -807,12 +841,15 @@ var cookieHelpers = { return cookieHost == host; } - let enumerator = Services.cookies.getCookiesFromHost(host, opts.originAttributes || {}); + let enumerator = + Services.cookies.getCookiesFromHost(host, opts.originAttributes || {}); + while (enumerator.hasMoreElements()) { let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2); if (hostMatches(cookie.host, host) && (!opts.name || cookie.name === opts.name) && - (!opts.domain || cookie.host === opts.domain)) { + (!opts.domain || cookie.host === opts.domain) && + (!opts.path || cookie.path === opts.path)) { Services.cookies.remove( cookie.host, cookie.name, @@ -1024,8 +1061,8 @@ function getObjectForLocalOrSessionStorage(type) { getFields: Task.async(function* () { return [ - { name: "name", editable: 1}, - { name: "value", editable: 1} + { name: "name", editable: true }, + { name: "value", editable: true } ]; }), @@ -1205,8 +1242,8 @@ StorageActors.createActor({ getFields: Task.async(function* () { return [ - { name: "url", editable: 0 }, - { name: "status", editable: 0 } + { name: "url", editable: false }, + { name: "status", editable: false } ]; }), @@ -1725,26 +1762,26 @@ StorageActors.createActor({ // Detail of database case "database": return [ - { name: "objectStore", editable: 0 }, - { name: "keyPath", editable: 0 }, - { name: "autoIncrement", editable: 0 }, - { name: "indexes", editable: 0 }, + { name: "objectStore", editable: false }, + { name: "keyPath", editable: false }, + { name: "autoIncrement", editable: false }, + { name: "indexes", editable: false }, ]; // Detail of object store case "object store": return [ - { name: "name", editable: 0 }, - { name: "value", editable: 0 } + { name: "name", editable: false }, + { name: "value", editable: false } ]; // Detail of indexedDB for one origin default: return [ - { name: "db", editable: 0 }, - { name: "origin", editable: 0 }, - { name: "version", editable: 0 }, - { name: "objectStores", editable: 0 }, + { name: "db", editable: false }, + { name: "origin", editable: false }, + { name: "version", editable: false }, + { name: "objectStores", editable: false }, ]; } }) @@ -2480,6 +2517,7 @@ let StorageActor = protocol.ActorClassWithSpec(specs.storageSpec, { // added or changed update this.removeNamesFromUpdateList("added", storeType, data); this.removeNamesFromUpdateList("changed", storeType, data); + for (let host in data) { if (data[host].length == 0 && this.boundUpdate.added && this.boundUpdate.added[storeType] && -- cgit v1.2.3 From 43ddb9b8c08ac148a9b03f16f45ec2cb71243f81 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 2 Mar 2018 14:33:20 +0100 Subject: Bug 1276339: Storage inspector doesn't work on chrome:// pages and web extensions Issue #31 --- devtools/server/actors/storage.js | 280 ++++++++++++++++++++++++++++---------- 1 file changed, 205 insertions(+), 75 deletions(-) (limited to 'devtools/server/actors') diff --git a/devtools/server/actors/storage.js b/devtools/server/actors/storage.js index 894e282f0..22f4eaabe 100644 --- a/devtools/server/actors/storage.js +++ b/devtools/server/actors/storage.js @@ -2,6 +2,8 @@ * 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/. */ +/* globals StopIteration */ + "use strict"; const {Cc, Ci, Cu, CC} = require("chrome"); @@ -93,7 +95,7 @@ var StorageActors = {}; * - observe : Method which gets triggered on the notificaiton of the watched * topic. * - getNamesForHost : Given a host, get list of all known store names. - * - getValuesForHost : Given a host (and optianally a name) get all known + * - getValuesForHost : Given a host (and optionally a name) get all known * store objects. * - toStoreObject : Given a store object, convert it to the required format * so that it can be transferred over wire. @@ -141,6 +143,9 @@ StorageActors.defaults = function (typeName, observationTopic) { * Converts the window.location object into host. */ getHostName(location) { + if (location.protocol === "chrome:") { + return location.href; + } return location.hostname || location.href; }, @@ -744,6 +749,7 @@ var cookieHelpers = { let enumerator = Services.cookies.getCookiesFromHost(origHost, data.originAttributes || {}); + while (enumerator.hasMoreElements()) { let nsiCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2); if (nsiCookie.name === origName && @@ -1041,6 +1047,9 @@ function getObjectForLocalOrSessionStorage(type) { if (!location.host) { return location.href; } + if (location.protocol === "chrome:") { + return location.href; + } return location.protocol + "//" + location.host; }, @@ -1251,6 +1260,9 @@ StorageActors.createActor({ if (!location.host) { return location.href; } + if (location.protocol === "chrome:") { + return location.href; + } return location.protocol + "//" + location.host; }, @@ -1423,12 +1435,15 @@ ObjectStoreMetadata.prototype = { * The host associated with this indexed db. * @param {IDBDatabase} db * The particular indexed db. + * @param {String} storage + * Storage type, either "temporary", "default" or "persistent". */ -function DatabaseMetadata(origin, db) { +function DatabaseMetadata(origin, db, storage) { this._origin = origin; this._name = db.name; this._version = db.version; this._objectStores = []; + this.storage = storage; if (db.objectStoreNames.length) { let transaction = db.transaction(db.objectStoreNames, "readonly"); @@ -1448,7 +1463,7 @@ DatabaseMetadata.prototype = { toObject() { return { - name: this._name, + name: `${this._name} (${this.storage})`, origin: this._origin, version: this._version, objectStores: this._objectStores.size @@ -1524,6 +1539,9 @@ StorageActors.createActor({ if (!location.host) { return location.href; } + if (location.protocol === "chrome:") { + return location.href; + } return location.protocol + "//" + location.host; }, @@ -1616,15 +1634,17 @@ StorageActors.createActor({ populateStoresForHost: Task.async(function* (host) { let storeMap = new Map(); let {names} = yield this.getDBNamesForHost(host); + let win = this.storageActor.getWindowFromHost(host); if (win) { let principal = win.document.nodePrincipal; - for (let name of names) { - let metadata = yield this.getDBMetaData(host, principal, name); + for (let {name, storage} of names) { + let metadata = yield this.getDBMetaData(host, principal, name, storage); metadata = indexedDBHelpers.patchMetadataMapsAndProtos(metadata); - storeMap.set(name, metadata); + + storeMap.set(`${name} (${storage})`, metadata); } } @@ -1657,10 +1677,22 @@ StorageActors.createActor({ objectStores: item.objectStores }; } + + let value = JSON.stringify(item.value); + + // FIXME: Bug 1318029 - Due to a bug that is thrown whenever a + // LongStringActor string reaches DebuggerServer.LONG_STRING_LENGTH we need + // to trim the value. When the bug is fixed we should stop trimming the + // string here. + let maxLength = DebuggerServer.LONG_STRING_LENGTH - 1; + if (value.length > maxLength) { + value = value.substr(0, maxLength); + } + // Indexed db entry return { name: item.name, - value: new LongStringActor(this.conn, JSON.stringify(item.value)) + value: new LongStringActor(this.conn, value) }; }, @@ -1696,16 +1728,18 @@ StorageActors.createActor({ maybeSetupChildProcess() { if (!DebuggerServer.isInChildProcess) { this.backToChild = (func, rv) => rv; + this.clearDBStore = indexedDBHelpers.clearDBStore; + this.gatherFilesOrFolders = indexedDBHelpers.gatherFilesOrFolders; this.getDBMetaData = indexedDBHelpers.getDBMetaData; - this.openWithPrincipal = indexedDBHelpers.openWithPrincipal; this.getDBNamesForHost = indexedDBHelpers.getDBNamesForHost; - this.getSanitizedHost = indexedDBHelpers.getSanitizedHost; this.getNameFromDatabaseFile = indexedDBHelpers.getNameFromDatabaseFile; - this.getValuesForHost = indexedDBHelpers.getValuesForHost; this.getObjectStoreData = indexedDBHelpers.getObjectStoreData; + this.getSanitizedHost = indexedDBHelpers.getSanitizedHost; + this.getValuesForHost = indexedDBHelpers.getValuesForHost; + this.openWithPrincipal = indexedDBHelpers.openWithPrincipal; this.removeDB = indexedDBHelpers.removeDB; this.removeDBRecord = indexedDBHelpers.removeDBRecord; - this.clearDBStore = indexedDBHelpers.clearDBStore; + this.splitNameAndStorage = indexedDBHelpers.splitNameAndStorage; return; } @@ -1718,6 +1752,7 @@ StorageActors.createActor({ }); this.getDBMetaData = callParentProcessAsync.bind(null, "getDBMetaData"); + this.splitNameAndStorage = callParentProcessAsync.bind(null, "splitNameAndStorage"); this.getDBNamesForHost = callParentProcessAsync.bind(null, "getDBNamesForHost"); this.getValuesForHost = callParentProcessAsync.bind(null, "getValuesForHost"); this.removeDB = callParentProcessAsync.bind(null, "removeDB"); @@ -1813,14 +1848,14 @@ var indexedDBHelpers = { * `name` for the given `host` with its `principal`. The stored metadata * information is of `DatabaseMetadata` type. */ - getDBMetaData: Task.async(function* (host, principal, name) { - let request = this.openWithPrincipal(principal, name); + getDBMetaData: Task.async(function* (host, principal, name, storage) { + let request = this.openWithPrincipal(principal, name, storage); let success = promise.defer(); request.onsuccess = event => { let db = event.target.result; - let dbData = new DatabaseMetadata(host, db); + let dbData = new DatabaseMetadata(host, db, storage); db.close(); success.resolve(this.backToChild("getDBMetaData", dbData)); @@ -1833,21 +1868,37 @@ var indexedDBHelpers = { return success.promise; }), + splitNameAndStorage: function (name) { + let lastOpenBracketIndex = name.lastIndexOf("("); + let lastCloseBracketIndex = name.lastIndexOf(")"); + let delta = lastCloseBracketIndex - lastOpenBracketIndex - 1; + + let storage = name.substr(lastOpenBracketIndex + 1, delta); + + name = name.substr(0, lastOpenBracketIndex - 1); + + return { storage, name }; + }, + /** * Opens an indexed db connection for the given `principal` and * database `name`. */ - openWithPrincipal(principal, name) { - return indexedDBForStorage.openForPrincipal(principal, name); + openWithPrincipal: function (principal, name, storage) { + return indexedDBForStorage.openForPrincipal(principal, name, + { storage: storage }); }, - removeDB: Task.async(function* (host, principal, name) { + removeDB: Task.async(function* (host, principal, dbName) { let result = new promise(resolve => { - let request = indexedDBForStorage.deleteForPrincipal(principal, name); + let {name, storage} = this.splitNameAndStorage(dbName); + let request = + indexedDBForStorage.deleteForPrincipal(principal, name, + { storage: storage }); request.onsuccess = () => { resolve({}); - this.onItemUpdated("deleted", host, [name]); + this.onItemUpdated("deleted", host, [dbName]); }; request.onblocked = () => { @@ -1873,10 +1924,11 @@ var indexedDBHelpers = { removeDBRecord: Task.async(function* (host, principal, dbName, storeName, id) { let db; + let {name, storage} = this.splitNameAndStorage(dbName); try { db = yield new promise((resolve, reject) => { - let request = this.openWithPrincipal(principal, dbName); + let request = this.openWithPrincipal(principal, name, storage); request.onsuccess = ev => resolve(ev.target.result); request.onerror = ev => reject(ev.target.error); }); @@ -1905,10 +1957,11 @@ var indexedDBHelpers = { clearDBStore: Task.async(function* (host, principal, dbName, storeName) { let db; + let {name, storage} = this.splitNameAndStorage(dbName); try { db = yield new promise((resolve, reject) => { - let request = this.openWithPrincipal(principal, dbName); + let request = this.openWithPrincipal(principal, name, storage); request.onsuccess = ev => resolve(ev.target.result); request.onerror = ev => reject(ev.target.error); }); @@ -1940,46 +1993,106 @@ var indexedDBHelpers = { */ getDBNamesForHost: Task.async(function* (host) { let sanitizedHost = this.getSanitizedHost(host); - let directory = OS.Path.join(OS.Constants.Path.profileDir, "storage", - "default", sanitizedHost, "idb"); + let profileDir = OS.Constants.Path.profileDir; + let files = []; + let names = []; + let storagePath = OS.Path.join(profileDir, "storage"); + + // We expect sqlite DB paths to look something like this: + // - PathToProfileDir/storage/default/http+++www.example.com/ + // idb/1556056096MeysDaabta.sqlite + // - PathToProfileDir/storage/permanent/http+++www.example.com/ + // idb/1556056096MeysDaabta.sqlite + // - PathToProfileDir/storage/temporary/http+++www.example.com/ + // idb/1556056096MeysDaabta.sqlite + // + // The subdirectory inside the storage folder is determined by the storage + // type: + // - default: { storage: "default" } or not specified. + // - permanent: { storage: "persistent" }. + // - temporary: { storage: "temporary" }. + let sqliteFiles = yield this.gatherFilesOrFolders(storagePath, path => { + if (path.endsWith(".sqlite")) { + let { components } = OS.Path.split(path); + let isIDB = components[components.length - 2] === "idb"; + + return isIDB; + } + return false; + }); - let exists = yield OS.File.exists(directory); - if (!exists && host.startsWith("about:")) { - // try for moz-safe-about directory - sanitizedHost = this.getSanitizedHost("moz-safe-" + host); - directory = OS.Path.join(OS.Constants.Path.profileDir, "storage", - "permanent", sanitizedHost, "idb"); - exists = yield OS.File.exists(directory); - } - if (!exists) { - return this.backToChild("getDBNamesForHost", {names: []}); + for (let file of sqliteFiles) { + let splitPath = OS.Path.split(file).components; + let idbIndex = splitPath.indexOf("idb"); + let name = splitPath[idbIndex - 1]; + let storage = splitPath[idbIndex - 2]; + let relative = file.substr(profileDir.length + 1); + + if (name.startsWith(sanitizedHost)) { + files.push({ + file: relative, + storage: storage === "permanent" ? "persistent" : storage + }); + } } - let names = []; - let dirIterator = new OS.File.DirectoryIterator(directory); - try { - yield dirIterator.forEach(file => { - // Skip directories. - if (file.isDir) { - return null; + if (files.length > 0) { + for (let {file, storage} of files) { + let name = yield this.getNameFromDatabaseFile(file); + if (name) { + names.push({ + name, + storage + }); } + } + } + return this.backToChild("getDBNamesForHost", {names}); + }), - // Skip any non-sqlite files. - if (!file.name.endsWith(".sqlite")) { - return null; - } + /** + * Gather together all of the files in path and pass each path through a + * validation function. + * + * @param {String} + * Path in which to begin searching. + * @param {Function} + * Validation function, which checks each file path. If this function + * Returns true the file path is kept. + * + * @returns {Array} + * An array of file paths. + */ + gatherFilesOrFolders: Task.async(function* (path, validationFunc) { + let files = []; + let iterator; + let paths = [path]; + + while (paths.length > 0) { + try { + iterator = new OS.File.DirectoryIterator(paths.pop()); - return this.getNameFromDatabaseFile(file.path).then(name => { - if (name) { - names.push(name); + for (let child in iterator) { + child = yield child; + + path = child.path; + + if (child.isDir) { + paths.push(path); + } else if (validationFunc(path)) { + files.push(path); } - return null; - }); - }); - } finally { - dirIterator.close(); + } + } catch (ex) { + // Ignore StopIteration to prevent exiting the loop. + if (ex != StopIteration) { + throw ex; + } + } } - return this.backToChild("getDBNamesForHost", {names: names}); + iterator.close(); + + return files; }), /** @@ -1987,6 +2100,9 @@ var indexedDBHelpers = { * name. */ getSanitizedHost(host) { + if (host.startsWith("about:")) { + host = "moz-safe-" + host; + } return host.replace(ILLEGAL_CHAR_REGEX, "+"); }, @@ -2000,7 +2116,7 @@ var indexedDBHelpers = { // Content pages might be having an open transaction for the same indexed db // which this sqlite file belongs to. In that case, sqlite.openConnection - // will throw. Thus we retey for some time to see if lock is removed. + // will throw. Thus we retry for some time to see if lock is removed. while (!connection && retryCount++ < 25) { try { connection = yield Sqlite.openConnection({ path: path }); @@ -2061,8 +2177,14 @@ var indexedDBHelpers = { return this.backToChild("getValuesForHost", {objectStores: objectStores}); } // Get either all entries from the object store, or a particular id - let result = yield this.getObjectStoreData(host, principal, db2, - objectStore, id, options.index, options.size); + let storage = hostVsStores.get(host).get(db2).storage; + let result = yield this.getObjectStoreData(host, principal, db2, storage, { + objectStore: objectStore, + id: id, + index: options.index, + offset: 0, + size: options.size + }); return this.backToChild("getValuesForHost", {result: result}); }), @@ -2076,23 +2198,27 @@ var indexedDBHelpers = { * The principal of the given document. * @param {string} dbName * The name of the indexed db from the above host. - * @param {string} objectStore - * The name of the object store from the above db. - * @param {string} id - * id of the requested entry from the above object store. - * null if all entries from the above object store are requested. - * @param {string} index - * name of the IDBIndex to be iterated on while fetching entries. - * null or "name" if no index is to be iterated. - * @param {number} offset - * ofsset of the entries to be fetched. - * @param {number} size - * The intended size of the entries to be fetched. + * @param {String} storage + * Storage type, either "temporary", "default" or "persistent". + * @param {Object} requestOptions + * An object in the following format: + * { + * objectStore: The name of the object store from the above db, + * id: Id of the requested entry from the above object + * store. null if all entries from the above object + * store are requested, + * index: Name of the IDBIndex to be iterated on while fetching + * entries. null or "name" if no index is to be + * iterated, + * offset: offset of the entries to be fetched, + * size: The intended size of the entries to be fetched + * } */ - getObjectStoreData(host, principal, dbName, objectStore, id, index, - offset, size) { - let request = this.openWithPrincipal(principal, dbName); + getObjectStoreData(host, principal, dbName, storage, requestOptions) { + let {name} = this.splitNameAndStorage(dbName); + let request = this.openWithPrincipal(principal, name, storage); let success = promise.defer(); + let {objectStore, id, index, offset, size} = requestOptions; let data = []; let db; @@ -2194,8 +2320,12 @@ var indexedDBHelpers = { switch (msg.json.method) { case "getDBMetaData": { - let [host, principal, name] = args; - return indexedDBHelpers.getDBMetaData(host, principal, name); + let [host, principal, name, storage] = args; + return indexedDBHelpers.getDBMetaData(host, principal, name, storage); + } + case "splitNameAndStorage": { + let [name] = args; + return indexedDBHelpers.splitNameAndStorage(name); } case "getDBNamesForHost": { let [host] = args; @@ -2207,8 +2337,8 @@ var indexedDBHelpers = { hostVsStores, principal); } case "removeDB": { - let [host, principal, name] = args; - return indexedDBHelpers.removeDB(host, principal, name); + let [host, principal, dbName] = args; + return indexedDBHelpers.removeDB(host, principal, dbName); } case "removeDBRecord": { let [host, principal, db, store, id] = args; -- cgit v1.2.3 From 71fd51a863171d9f462d1311749de717361406a6 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 2 Mar 2018 14:59:24 +0100 Subject: Bug 1320362: Move indexedDb storage type in the storage inspector into a new column Issue #31 --- devtools/server/actors/storage.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'devtools/server/actors') diff --git a/devtools/server/actors/storage.js b/devtools/server/actors/storage.js index 22f4eaabe..f4fbbd8c3 100644 --- a/devtools/server/actors/storage.js +++ b/devtools/server/actors/storage.js @@ -1463,7 +1463,9 @@ DatabaseMetadata.prototype = { toObject() { return { - name: `${this._name} (${this.storage})`, + uniqueKey: `${this._name}${SEPARATOR_GUID}${this.storage}`, + name: this._name, + storage: this.storage, origin: this._origin, version: this._version, objectStores: this._objectStores.size @@ -1671,7 +1673,9 @@ StorageActors.createActor({ if ("objectStores" in item) { // DB meta data return { + uniqueKey: `${item.name} (${item.storage})`, db: item.name, + storage: item.storage, origin: item.origin, version: item.version, objectStores: item.objectStores @@ -1813,7 +1817,9 @@ StorageActors.createActor({ // Detail of indexedDB for one origin default: return [ + { name: "uniqueKey", editable: false, private: true }, { name: "db", editable: false }, + { name: "storage", editable: false }, { name: "origin", editable: false }, { name: "version", editable: false }, { name: "objectStores", editable: false }, -- cgit v1.2.3 From 4240e91e328b12c1b7f47b813e20c5671f4d8593 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 2 Mar 2018 15:06:49 +0100 Subject: Bug 1334252: Find IDB storage for host more directly Issue #31 --- devtools/server/actors/storage.js | 109 ++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 57 deletions(-) (limited to 'devtools/server/actors') diff --git a/devtools/server/actors/storage.js b/devtools/server/actors/storage.js index f4fbbd8c3..497051e57 100644 --- a/devtools/server/actors/storage.js +++ b/devtools/server/actors/storage.js @@ -2,8 +2,6 @@ * 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/. */ -/* globals StopIteration */ - "use strict"; const {Cc, Ci, Cu, CC} = require("chrome"); @@ -1733,7 +1731,9 @@ StorageActors.createActor({ if (!DebuggerServer.isInChildProcess) { this.backToChild = (func, rv) => rv; this.clearDBStore = indexedDBHelpers.clearDBStore; - this.gatherFilesOrFolders = indexedDBHelpers.gatherFilesOrFolders; + this.findIDBPathsForHost = indexedDBHelpers.findIDBPathsForHost; + this.findSqlitePathsForHost = indexedDBHelpers.findSqlitePathsForHost; + this.findStorageTypePaths = indexedDBHelpers.findStorageTypePaths; this.getDBMetaData = indexedDBHelpers.getDBMetaData; this.getDBNamesForHost = indexedDBHelpers.getDBNamesForHost; this.getNameFromDatabaseFile = indexedDBHelpers.getNameFromDatabaseFile; @@ -2011,35 +2011,23 @@ var indexedDBHelpers = { // idb/1556056096MeysDaabta.sqlite // - PathToProfileDir/storage/temporary/http+++www.example.com/ // idb/1556056096MeysDaabta.sqlite - // // The subdirectory inside the storage folder is determined by the storage // type: // - default: { storage: "default" } or not specified. // - permanent: { storage: "persistent" }. // - temporary: { storage: "temporary" }. - let sqliteFiles = yield this.gatherFilesOrFolders(storagePath, path => { - if (path.endsWith(".sqlite")) { - let { components } = OS.Path.split(path); - let isIDB = components[components.length - 2] === "idb"; - - return isIDB; - } - return false; - }); + let sqliteFiles = yield this.findSqlitePathsForHost(storagePath, sanitizedHost); for (let file of sqliteFiles) { let splitPath = OS.Path.split(file).components; let idbIndex = splitPath.indexOf("idb"); - let name = splitPath[idbIndex - 1]; let storage = splitPath[idbIndex - 2]; let relative = file.substr(profileDir.length + 1); - if (name.startsWith(sanitizedHost)) { - files.push({ - file: relative, - storage: storage === "permanent" ? "persistent" : storage - }); - } + files.push({ + file: relative, + storage: storage === "permanent" ? "persistent" : storage + }); } if (files.length > 0) { @@ -2057,48 +2045,55 @@ var indexedDBHelpers = { }), /** - * Gather together all of the files in path and pass each path through a - * validation function. - * - * @param {String} - * Path in which to begin searching. - * @param {Function} - * Validation function, which checks each file path. If this function - * Returns true the file path is kept. - * - * @returns {Array} - * An array of file paths. + * Find all SQLite files that hold IndexedDB data for a host, such as: + * storage/temporary/http+++www.example.com/idb/1556056096MeysDaabta.sqlite */ - gatherFilesOrFolders: Task.async(function* (path, validationFunc) { - let files = []; - let iterator; - let paths = [path]; - - while (paths.length > 0) { - try { - iterator = new OS.File.DirectoryIterator(paths.pop()); - - for (let child in iterator) { - child = yield child; - - path = child.path; - - if (child.isDir) { - paths.push(path); - } else if (validationFunc(path)) { - files.push(path); - } - } - } catch (ex) { - // Ignore StopIteration to prevent exiting the loop. - if (ex != StopIteration) { - throw ex; + findSqlitePathsForHost: Task.async(function* (storagePath, sanitizedHost) { + let sqlitePaths = []; + let idbPaths = yield this.findIDBPathsForHost(storagePath, sanitizedHost); + for (let idbPath of idbPaths) { + let iterator = new OS.File.DirectoryIterator(idbPath); + yield iterator.forEach(entry => { + if (!entry.isDir && entry.path.endsWith(".sqlite")) { + sqlitePaths.push(entry.path); } + }); + iterator.close(); + } + return sqlitePaths; + }), + + /** + * Find all paths that hold IndexedDB data for a host, such as: + * storage/temporary/http+++www.example.com/idb + */ + findIDBPathsForHost: Task.async(function* (storagePath, sanitizedHost) { + let idbPaths = []; + let typePaths = yield this.findStorageTypePaths(storagePath); + for (let typePath of typePaths) { + let idbPath = OS.Path.join(typePath, sanitizedHost, "idb"); + if (yield OS.File.exists(idbPath)) { + idbPaths.push(idbPath); } } - iterator.close(); + return idbPaths; + }), - return files; + /** + * Find all the storage types, such as "default", "permanent", or "temporary". + * These names have changed over time, so it seems simpler to look through all types + * that currently exist in the profile. + */ + findStorageTypePaths: Task.async(function* (storagePath) { + let iterator = new OS.File.DirectoryIterator(storagePath); + let typePaths = []; + yield iterator.forEach(entry => { + if (entry.isDir) { + typePaths.push(entry.path); + } + }); + iterator.close(); + return typePaths; }), /** -- cgit v1.2.3 From f0175fb7abd0aad3054236fb1a0cc430c6d05db9 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 2 Mar 2018 15:26:16 +0100 Subject: Bug 1321820: Storage Inspector fails with dom.caches.enabled=false Issue #31 --- devtools/server/actors/storage.js | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'devtools/server/actors') diff --git a/devtools/server/actors/storage.js b/devtools/server/actors/storage.js index 497051e57..6d069939d 100644 --- a/devtools/server/actors/storage.js +++ b/devtools/server/actors/storage.js @@ -1187,6 +1187,11 @@ StorageActors.createActor({ // The |chrome| cache is the cache implicitely cached by the platform, // hosting the source file of the service worker. let { CacheStorage } = this.storageActor.window; + + if (!CacheStorage) { + return []; + } + let cache = new CacheStorage("content", principal); return cache; }), -- cgit v1.2.3 From 26e53627d6922b3b965afd76fc5d72e3cc1d9ba5 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 2 Mar 2018 16:38:25 +0100 Subject: moebius#337: Added option to remove all session cookies for a specific domain Issue #31 https://github.com/MoonchildProductions/moebius/pull/337 --- devtools/server/actors/storage.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'devtools/server/actors') diff --git a/devtools/server/actors/storage.js b/devtools/server/actors/storage.js index 6d069939d..6d1e2dc99 100644 --- a/devtools/server/actors/storage.js +++ b/devtools/server/actors/storage.js @@ -628,6 +628,12 @@ StorageActors.createActor({ .originAttributes); }), + removeAllSessionCookies: Task.async(function* (host, domain) { + let doc = this.storageActor.document; + this.removeAllSessionCookies(host, domain, doc.nodePrincipal + .originAttributes); + }), + maybeSetupChildProcess() { cookieHelpers.onCookieChanged = this.onCookieChanged.bind(this); @@ -644,6 +650,8 @@ StorageActors.createActor({ cookieHelpers.removeCookie.bind(cookieHelpers); this.removeAllCookies = cookieHelpers.removeAllCookies.bind(cookieHelpers); + this.removeAllSessionCookies = + cookieHelpers.removeAllSessionCookies.bind(cookieHelpers); return; } @@ -667,6 +675,8 @@ StorageActors.createActor({ callParentProcess.bind(null, "removeCookie"); this.removeAllCookies = callParentProcess.bind(null, "removeAllCookies"); + this.removeAllSessionCookies = + callParentProcess.bind(null, "removeAllSessionCookies"); addMessageListener("debug:storage-cookie-request-child", cookieHelpers.handleParentRequest); @@ -853,7 +863,8 @@ var cookieHelpers = { if (hostMatches(cookie.host, host) && (!opts.name || cookie.name === opts.name) && (!opts.domain || cookie.host === opts.domain) && - (!opts.path || cookie.path === opts.path)) { + (!opts.path || cookie.path === opts.path) && + (!opts.session || (!cookie.expires && !cookie.maxAge))) { Services.cookies.remove( cookie.host, cookie.name, @@ -875,6 +886,10 @@ var cookieHelpers = { this._removeCookies(host, { domain, originAttributes }); }, + removeAllSessionCookies(host, domain, originAttributes) { + this._removeCookies(host, { domain, originAttributes, session: true }); + }, + addCookieObservers() { Services.obs.addObserver(cookieHelpers, "cookie-changed", false); return null; @@ -951,6 +966,12 @@ var cookieHelpers = { let originAttributes = msg.data.args[2]; return cookieHelpers.removeAllCookies(host, domain, originAttributes); } + case "removeAllSessionCookies": { + let host = msg.data.args[0]; + let domain = msg.data.args[1]; + let originAttributes = msg.data.args[2]; + return cookieHelpers.removeAllSessionCookies(host, domain, originAttributes); + } default: console.error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD", msg.json.method); throw new Error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD"); -- cgit v1.2.3 From 166fb9f2893dcfb3375aa3227d116fb0ce2c6d42 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 2 Mar 2018 17:52:34 +0100 Subject: moebius#339: Make it possible to add cookies, local and session storage entries Issue #31 https://github.com/MoonchildProductions/moebius/pull/339 --- devtools/server/actors/storage.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'devtools/server/actors') diff --git a/devtools/server/actors/storage.js b/devtools/server/actors/storage.js index 6d1e2dc99..620a9acb9 100644 --- a/devtools/server/actors/storage.js +++ b/devtools/server/actors/storage.js @@ -15,6 +15,8 @@ const {isWindowIncluded} = require("devtools/shared/layout/utils"); const specs = require("devtools/shared/specs/storage"); const { Task } = require("devtools/shared/task"); +const DEFAULT_VALUE = "value"; + // GUID to be used as a separator in compound keys. This must match the same // constant in devtools/client/storage/ui.js, // devtools/client/storage/test/head.js and @@ -616,6 +618,14 @@ StorageActors.createActor({ this.editCookie(data); }), + addItem: Task.async(function* (guid) { + let doc = this.storageActor.document; + let time = new Date().getTime(); + let expiry = new Date(time + 3600 * 24 * 1000).toGMTString(); + + doc.cookie = `${guid}=${DEFAULT_VALUE};expires=${expiry}`; + }), + removeItem: Task.async(function* (host, name) { let doc = this.storageActor.document; this.removeCookie(host, name, doc.nodePrincipal @@ -954,6 +964,12 @@ var cookieHelpers = { let rowdata = msg.data.args[0]; return cookieHelpers.editCookie(rowdata); } + case "createNewCookie": { + let host = msg.data.args[0]; + let guid = msg.data.args[1]; + let originAttributes = msg.data.args[2]; + return cookieHelpers.createNewCookie(host, guid, originAttributes); + } case "removeCookie": { let host = msg.data.args[0]; let name = msg.data.args[1]; @@ -1094,6 +1110,14 @@ function getObjectForLocalOrSessionStorage(type) { ]; }), + addItem: Task.async(function* (guid, host) { + let storage = this.hostVsStores.get(host); + if (!storage) { + return; + } + storage.setItem(guid, DEFAULT_VALUE); + }), + /** * Edit localStorage or sessionStorage fields. * -- cgit v1.2.3 From f22bc3fd885b28be74266c2c48bcc84a3a7ede26 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 2 Mar 2018 18:22:50 +0100 Subject: moebius#342: Columns are not sorted correctly (Natural Sort algorithm) Issue #31 https://github.com/MoonchildProductions/moebius/pull/342 --- devtools/server/actors/storage.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) (limited to 'devtools/server/actors') diff --git a/devtools/server/actors/storage.js b/devtools/server/actors/storage.js index 620a9acb9..015d849ee 100644 --- a/devtools/server/actors/storage.js +++ b/devtools/server/actors/storage.js @@ -17,6 +17,9 @@ const { Task } = require("devtools/shared/task"); const DEFAULT_VALUE = "value"; +loader.lazyRequireGetter(this, "naturalSortCaseInsensitive", + "devtools/client/shared/natural-sort", true); + // GUID to be used as a separator in compound keys. This must match the same // constant in devtools/client/storage/ui.js, // devtools/client/storage/test/head.js and @@ -326,15 +329,20 @@ StorageActors.defaults = function (typeName, observationTopic) { toReturn.data.push(...values); } } + toReturn.total = this.getObjectsSize(host, names, options); + if (offset > toReturn.total) { // In this case, toReturn.data is an empty array. toReturn.offset = toReturn.total; toReturn.data = []; } else { - toReturn.data = toReturn.data.sort((a, b) => { - return a[sortOn] - b[sortOn]; - }).slice(offset, offset + size).map(a => this.toStoreObject(a)); + // We need to use natural sort before slicing. + let sorted = toReturn.data.sort((a, b) => { + return naturalSortCaseInsensitive(a[sortOn], b[sortOn]); + }); + let sliced = sorted.slice(offset, offset + size); + toReturn.data = sliced.map(a => this.toStoreObject(a)); } } else { let obj = yield this.getValuesForHost(host, undefined, undefined, @@ -344,15 +352,18 @@ StorageActors.defaults = function (typeName, observationTopic) { } toReturn.total = obj.length; + if (offset > toReturn.total) { // In this case, toReturn.data is an empty array. toReturn.offset = offset = toReturn.total; toReturn.data = []; } else { - toReturn.data = obj.sort((a, b) => { - return a[sortOn] - b[sortOn]; - }).slice(offset, offset + size) - .map(object => this.toStoreObject(object)); + // We need to use natural sort before slicing. + let sorted = obj.sort((a, b) => { + return naturalSortCaseInsensitive(a[sortOn], b[sortOn]); + }); + let sliced = sorted.slice(offset, offset + size); + toReturn.data = sliced.map(object => this.toStoreObject(object)); } } -- cgit v1.2.3 From a53fe8f5c92ceeb091df55ce41b69f49f7d6c6d6 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 4 Mar 2018 01:35:21 +0100 Subject: Bug 1302989: Make storage inspector work with file:// when # is in the URL Issue #31 --- devtools/server/actors/storage.js | 95 +++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 38 deletions(-) (limited to 'devtools/server/actors') diff --git a/devtools/server/actors/storage.js b/devtools/server/actors/storage.js index 015d849ee..a7215b152 100644 --- a/devtools/server/actors/storage.js +++ b/devtools/server/actors/storage.js @@ -129,7 +129,11 @@ StorageActors.defaults = function (typeName, observationTopic) { get hosts() { let hosts = new Set(); for (let {location} of this.storageActor.windows) { - hosts.add(this.getHostName(location)); + let host = this.getHostName(location); + + if (host) { + hosts.add(host); + } } return hosts; }, @@ -143,13 +147,35 @@ StorageActors.defaults = function (typeName, observationTopic) { }, /** - * Converts the window.location object into host. + * Converts the window.location object into a URL (e.g. http://domain.com). */ getHostName(location) { - if (location.protocol === "chrome:") { - return location.href; + if (!location) { + // Debugging a legacy Firefox extension... no hostname available and no + // storage possible. + return null; + } + + switch (location.protocol) { + case "data:": + // data: URLs do not support storage of any type. + return null; + case "about:": + // Fallthrough. + case "chrome:": + // Fallthrough. + case "file:": + return location.protocol + location.pathname; + case "resource:": + return location.origin + location.pathname; + case "moz-extension:": + return location.origin; + case "javascript:": + return location.href; + default: + // http: or unknown protocol. + return `${location.protocol}//${location.host}`; } - return location.hostname || location.href; }, initialize(storageActor) { @@ -202,7 +228,7 @@ StorageActors.defaults = function (typeName, observationTopic) { */ onWindowReady: Task.async(function* (window) { let host = this.getHostName(window.location); - if (!this.hostVsStores.has(host)) { + if (host && !this.hostVsStores.has(host)) { yield this.populateStoresForHost(host, window); let data = {}; data[host] = this.getNamesForHost(host); @@ -223,7 +249,7 @@ StorageActors.defaults = function (typeName, observationTopic) { return; } let host = this.getHostName(window.location); - if (!this.hosts.has(host)) { + if (host && !this.hosts.has(host)) { this.hostVsStores.delete(host); let data = {}; data[host] = []; @@ -467,6 +493,9 @@ StorageActors.createActor({ if (cookie.host == null) { return host == null; } + + host = trimHttpHttps(host); + if (cookie.host.startsWith(".")) { return ("." + host).endsWith(cookie.host); } @@ -732,6 +761,8 @@ var cookieHelpers = { host = ""; } + host = trimHttpHttps(host); + let cookies = Services.cookies.getCookiesFromHost(host, originAttributes); let store = []; @@ -866,6 +897,8 @@ var cookieHelpers = { opts.path = split[2]; } + host = trimHttpHttps(host); + function hostMatches(cookieHost, matchHost) { if (cookieHost == null) { return matchHost == null; @@ -1089,16 +1122,6 @@ function getObjectForLocalOrSessionStorage(type) { })); }, - getHostName(location) { - if (!location.host) { - return location.href; - } - if (location.protocol === "chrome:") { - return location.href; - } - return location.protocol + "//" + location.host; - }, - populateStoresForHost(host, window) { try { this.hostVsStores.set(host, window[type]); @@ -1110,7 +1133,10 @@ function getObjectForLocalOrSessionStorage(type) { populateStoresForHosts() { this.hostVsStores = new Map(); for (let window of this.windows) { - this.populateStoresForHost(this.getHostName(window.location), window); + let host = this.getHostName(window.location); + if (host) { + this.populateStoresForHost(host, window); + } } }, @@ -1315,16 +1341,6 @@ StorageActors.createActor({ ]; }), - getHostName(location) { - if (!location.host) { - return location.href; - } - if (location.protocol === "chrome:") { - return location.href; - } - return location.protocol + "//" + location.host; - }, - populateStoresForHost: Task.async(function* (host) { let storeMap = new Map(); let caches = yield this.getCachesForHost(host); @@ -1596,16 +1612,6 @@ StorageActors.createActor({ this.removeDBRecord(host, principal, db, store, id); }), - getHostName(location) { - if (!location.host) { - return location.href; - } - if (location.protocol === "chrome:") { - return location.href; - } - return location.protocol + "//" + location.host; - }, - /** * This method is overriden and left blank as for indexedDB, this operation * cannot be performed synchronously. Thus, the preListStores method exists to @@ -2443,6 +2449,19 @@ exports.setupParentProcessForIndexedDB = function ({ mm, prefix }) { }; }; +/** + * General helpers + */ +function trimHttpHttps(url) { + if (url.startsWith("http://")) { + return url.substr(7); + } + if (url.startsWith("https://")) { + return url.substr(8); + } + return url; +} + /** * The main Storage Actor. */ -- cgit v1.2.3 From e78a87b4188fd266d6ec9662c95ef147dbbd92cd Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 4 Mar 2018 01:58:43 +0100 Subject: moebius#346: Storage Inspector should trim port from hosts for cookies Issue #31 https://github.com/MoonchildProductions/moebius/pull/346 --- devtools/server/actors/storage.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'devtools/server/actors') diff --git a/devtools/server/actors/storage.js b/devtools/server/actors/storage.js index a7215b152..c702e8145 100644 --- a/devtools/server/actors/storage.js +++ b/devtools/server/actors/storage.js @@ -494,7 +494,7 @@ StorageActors.createActor({ return host == null; } - host = trimHttpHttps(host); + host = trimHttpHttpsPort(host); if (cookie.host.startsWith(".")) { return ("." + host).endsWith(cookie.host); @@ -761,7 +761,7 @@ var cookieHelpers = { host = ""; } - host = trimHttpHttps(host); + host = trimHttpHttpsPort(host); let cookies = Services.cookies.getCookiesFromHost(host, originAttributes); let store = []; @@ -897,7 +897,7 @@ var cookieHelpers = { opts.path = split[2]; } - host = trimHttpHttps(host); + host = trimHttpHttpsPort(host); function hostMatches(cookieHost, matchHost) { if (cookieHost == null) { @@ -2452,7 +2452,12 @@ exports.setupParentProcessForIndexedDB = function ({ mm, prefix }) { /** * General helpers */ -function trimHttpHttps(url) { +function trimHttpHttpsPort(url) { + let match = url.match(/(.+):\d+$/); + + if (match) { + url = match[1]; + } if (url.startsWith("http://")) { return url.substr(7); } -- cgit v1.2.3 From af300f36f11293c12f2ee01580fc749a7e114376 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 16 Mar 2018 11:35:57 +0100 Subject: Bug 755821: Function() should use the parser's argument parsing code --- devtools/server/actors/errordocs.js | 1 - 1 file changed, 1 deletion(-) (limited to 'devtools/server/actors') diff --git a/devtools/server/actors/errordocs.js b/devtools/server/actors/errordocs.js index 27f687dc7..7c4b3acff 100644 --- a/devtools/server/actors/errordocs.js +++ b/devtools/server/actors/errordocs.js @@ -18,7 +18,6 @@ const ErrorDocs = { JSMSG_RESULTING_STRING_TOO_LARGE: "Resulting_string_too_large", JSMSG_BAD_RADIX: "Bad_radix", JSMSG_PRECISION_RANGE: "Precision_range", - JSMSG_BAD_FORMAL: "Malformed_formal_parameter", JSMSG_STMT_AFTER_RETURN: "Stmt_after_return", JSMSG_NOT_A_CODEPOINT: "Not_a_codepoint", JSMSG_BAD_SORT_ARG: "Array_sort_argument", -- cgit v1.2.3 From 3114d48cee98c43c5d980eccfc104854860ef2be Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Mon, 19 Mar 2018 10:47:16 +0100 Subject: Handle RegExp correctly in devtools Issue #77 --- devtools/server/actors/object.js | 5 ----- 1 file changed, 5 deletions(-) (limited to 'devtools/server/actors') diff --git a/devtools/server/actors/object.js b/devtools/server/actors/object.js index 1f417b951..06e95a5f0 100644 --- a/devtools/server/actors/object.js +++ b/devtools/server/actors/object.js @@ -1158,11 +1158,6 @@ DebuggerServer.ObjectActorPreviewers = { }], RegExp: [function ({obj, hooks}, grip) { - // Avoid having any special preview for the RegExp.prototype itself. - if (!obj.proto || obj.proto.class != "RegExp") { - return false; - } - let str = RegExp.prototype.toString.call(obj.unsafeDereference()); grip.displayString = hooks.createValueGrip(str); return true; -- cgit v1.2.3