mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Merge mozilla-inbound to mozilla-central a=merge
This commit is contained in:
commit
530163105d
@ -190,6 +190,7 @@ dom/grid/**
|
||||
dom/html/**
|
||||
dom/ipc/**
|
||||
dom/jsurl/**
|
||||
dom/localstorage/**
|
||||
dom/manifest/**
|
||||
dom/media/test/**
|
||||
dom/media/tests/**
|
||||
|
@ -277,7 +277,7 @@ add_task(async function deleteStorageInAboutURL() {
|
||||
|
||||
let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("about:newtab");
|
||||
await new Promise(aResolve => {
|
||||
let req = Services.qms.clearStoragesForPrincipal(principal, null, false);
|
||||
let req = Services.qms.clearStoragesForPrincipal(principal);
|
||||
req.callback = () => { aResolve(); };
|
||||
});
|
||||
});
|
||||
@ -313,7 +313,7 @@ add_task(async function deleteStorageOnlyCustomPermissionInAboutURL() {
|
||||
|
||||
let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("about:newtab");
|
||||
await new Promise(aResolve => {
|
||||
let req = Services.qms.clearStoragesForPrincipal(principal, null, false);
|
||||
let req = Services.qms.clearStoragesForPrincipal(principal);
|
||||
req.callback = () => { aResolve(); };
|
||||
});
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
support-files =
|
||||
head.js
|
||||
file_install_extensions.html
|
||||
browser_legacy.xpi
|
||||
browser_legacy_webext.xpi
|
||||
browser_webext_permissions.xpi
|
||||
browser_webext_nopermissions.xpi
|
||||
|
@ -1,7 +1,6 @@
|
||||
const {AddonManagerPrivate} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm", {});
|
||||
|
||||
const ID_PERMS = "update_perms@tests.mozilla.org";
|
||||
const ID_LEGACY = "legacy_update@tests.mozilla.org";
|
||||
const ID_ORIGINS = "update_origins@tests.mozilla.org";
|
||||
|
||||
function getBadgeStatus() {
|
||||
@ -80,11 +79,6 @@ async function testNoPrompt(origUrl, id) {
|
||||
add_task(() => testNoPrompt(`${BASE}/browser_webext_update_perms1.xpi`,
|
||||
ID_PERMS));
|
||||
|
||||
// Test that an update from a legacy extension to a webextension
|
||||
// doesn't show a prompt even when the webextension uses
|
||||
// promptable required permissions.
|
||||
add_task(() => testNoPrompt(`${BASE}/browser_legacy.xpi`, ID_LEGACY));
|
||||
|
||||
// Test that an update that narrows origin permissions is just applied without
|
||||
// showing a notification promt
|
||||
add_task(() => testNoPrompt(`${BASE}/browser_webext_update_origins1.xpi`,
|
||||
|
Binary file not shown.
@ -51,11 +51,6 @@ async function testUpdateNoPrompt(filename, id,
|
||||
await addon.uninstall();
|
||||
}
|
||||
|
||||
// Test that we don't see a prompt when updating from a legacy
|
||||
// extension to a webextension.
|
||||
add_task(() => testUpdateNoPrompt("browser_legacy.xpi",
|
||||
"legacy_update@tests.mozilla.org", "1.1"));
|
||||
|
||||
// Test that we don't see a prompt when no new promptable permissions
|
||||
// are added.
|
||||
add_task(() => testUpdateNoPrompt("browser_webext_update_perms1.xpi",
|
||||
|
@ -14,6 +14,7 @@
|
||||
background-position: 9px center;
|
||||
background-size: 12px 12px;
|
||||
padding-left: 30px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#prefs {
|
||||
@ -39,11 +40,23 @@
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#prefs > tr.locked {
|
||||
opacity: 0.4;
|
||||
background-image: url("chrome://browser/skin/preferences/in-content/privacy-security.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 9px center;
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
|
||||
#prefs > tr > td {
|
||||
padding: 4px;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#prefs > tr > td.cell-name {
|
||||
padding-inline-start: 30px;
|
||||
}
|
||||
|
||||
.cell-value {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
@ -19,14 +19,16 @@ function onLoad() {
|
||||
let hasUserValue = Services.prefs.prefHasUserValue(name);
|
||||
let pref = {
|
||||
name,
|
||||
value: Preferences.get(name),
|
||||
hasUserValue,
|
||||
hasDefaultValue: hasUserValue ? prefHasDefaultValue(name) : true,
|
||||
isLocked: Services.prefs.prefIsLocked(name),
|
||||
};
|
||||
// Try in case it's a localized string.
|
||||
// Throws an exception if there is no equivalent value in the localized files for the pref.
|
||||
// Try in case it's a localized string or locked user added pref
|
||||
// If an execption is thrown the pref value is set to the empty string.
|
||||
try {
|
||||
// Throws an exception in case locked user added pref without default value
|
||||
pref.value = Preferences.get(name);
|
||||
// Throws an exception if there is no equivalent value in the localized files for the pref.
|
||||
if (!pref.hasUserValue && /^chrome:\/\/.+\/locale\/.+\.properties/.test(pref.value)) {
|
||||
pref.value = Services.prefs.getComplexValue(name, Ci.nsIPrefLocalizedString).data;
|
||||
}
|
||||
@ -121,6 +123,9 @@ function createPrefsFragment(prefArray) {
|
||||
if (pref.hasUserValue) {
|
||||
row.classList.add("has-user-value");
|
||||
}
|
||||
if (pref.isLocked) {
|
||||
row.classList.add("locked");
|
||||
}
|
||||
row.setAttribute("aria-label", pref.name);
|
||||
|
||||
row.appendChild(getPrefRow(pref));
|
||||
@ -135,6 +140,7 @@ function createNewPrefFragment(name) {
|
||||
row.classList.add("has-user-value");
|
||||
row.setAttribute("aria-label", name);
|
||||
let nameCell = document.createElement("td");
|
||||
nameCell.className = "cell-name";
|
||||
nameCell.append(name);
|
||||
row.appendChild(nameCell);
|
||||
|
||||
@ -164,6 +170,7 @@ function createNewPrefFragment(name) {
|
||||
function getPrefRow(pref) {
|
||||
let rowFragment = document.createDocumentFragment();
|
||||
let nameCell = document.createElement("td");
|
||||
nameCell.className = "cell-name";
|
||||
// Add <wbr> behind dots to prevent line breaking in random mid-word places.
|
||||
let parts = pref.name.split(".");
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
@ -187,11 +194,14 @@ function getPrefRow(pref) {
|
||||
document.l10n.setAttributes(button, "about-config-pref-edit");
|
||||
button.className = "button-edit";
|
||||
}
|
||||
if (pref.isLocked) {
|
||||
button.disabled = true;
|
||||
}
|
||||
editCell.appendChild(button);
|
||||
rowFragment.appendChild(editCell);
|
||||
|
||||
let buttonCell = document.createElement("td");
|
||||
if (pref.hasUserValue) {
|
||||
if (!pref.isLocked && pref.hasUserValue) {
|
||||
let resetButton = document.createElement("button");
|
||||
if (!pref.hasDefaultValue) {
|
||||
document.l10n.setAttributes(resetButton, "about-config-pref-delete");
|
||||
@ -201,6 +211,7 @@ function getPrefRow(pref) {
|
||||
}
|
||||
buttonCell.appendChild(resetButton);
|
||||
}
|
||||
|
||||
rowFragment.appendChild(buttonCell);
|
||||
return rowFragment;
|
||||
}
|
||||
|
@ -5,3 +5,5 @@
|
||||
skip-if = debug # Bug 1507747
|
||||
[browser_search.js]
|
||||
skip-if = debug # Bug 1507747
|
||||
[browser_locked.js]
|
||||
|
||||
|
@ -0,0 +1,70 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const PAGE_URL = "chrome://browser/content/aboutconfig/aboutconfig.html";
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["test.aboutconfig.a", "some value"],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_locked() {
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.unlockPref("browser.search.searchEnginesURL");
|
||||
Services.prefs.unlockPref("test.aboutconfig.a");
|
||||
Services.prefs.unlockPref("accessibility.AOM.enabled");
|
||||
});
|
||||
|
||||
Services.prefs.lockPref("browser.search.searchEnginesURL");
|
||||
Services.prefs.lockPref("test.aboutconfig.a");
|
||||
Services.prefs.lockPref("accessibility.AOM.enabled");
|
||||
await BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: PAGE_URL,
|
||||
}, async browser => {
|
||||
await ContentTask.spawn(browser, null, () => {
|
||||
let list = [...content.document.getElementById("prefs")
|
||||
.getElementsByTagName("tr")];
|
||||
function getRow(name) {
|
||||
return list.find(row => row.querySelector("td").textContent == name);
|
||||
}
|
||||
function getValue(name) {
|
||||
return getRow(name).querySelector("td.cell-value").textContent;
|
||||
}
|
||||
function getButton(name) {
|
||||
return getRow(name).querySelector("button");
|
||||
}
|
||||
|
||||
// Test locked default string pref.
|
||||
let lockedPref = getRow("browser.search.searchEnginesURL");
|
||||
Assert.ok(lockedPref.classList.contains("locked"));
|
||||
Assert.equal(getValue("browser.search.searchEnginesURL"), "https://addons.mozilla.org/%LOCALE%/firefox/search-engines/");
|
||||
Assert.ok(getButton("browser.search.searchEnginesURL").classList.contains("button-edit"));
|
||||
Assert.equal(getButton("browser.search.searchEnginesURL").disabled, true);
|
||||
|
||||
// Test locked default boolean pref.
|
||||
lockedPref = getRow("accessibility.AOM.enabled");
|
||||
Assert.ok(lockedPref.classList.contains("locked"));
|
||||
Assert.equal(getValue("accessibility.AOM.enabled"), "false");
|
||||
Assert.ok(getButton("accessibility.AOM.enabled").classList.contains("button-toggle"));
|
||||
Assert.equal(getButton("accessibility.AOM.enabled").disabled, true);
|
||||
|
||||
// Test locked user added pref.
|
||||
lockedPref = getRow("test.aboutconfig.a");
|
||||
Assert.ok(lockedPref.classList.contains("locked"));
|
||||
Assert.equal(getValue("test.aboutconfig.a"), "");
|
||||
Assert.ok(getButton("test.aboutconfig.a").classList.contains("button-edit"));
|
||||
Assert.equal(getButton("test.aboutconfig.a").disabled, true);
|
||||
|
||||
// Test pref not locked
|
||||
let unlockedPref = getRow("accessibility.indicator.enabled");
|
||||
Assert.equal(unlockedPref.classList.contains("locked"), false);
|
||||
Assert.equal(getValue("accessibility.indicator.enabled"), "false");
|
||||
Assert.ok(getButton("accessibility.indicator.enabled").classList.contains("button-toggle"));
|
||||
Assert.equal(getButton("accessibility.indicator.enabled").disabled, false);
|
||||
});
|
||||
});
|
||||
});
|
@ -92,6 +92,10 @@ async function checkIndexedDB(browser) {
|
||||
} catch (e) {
|
||||
is(e.name, "NotFoundError", "The indexedDB does not exist as expected");
|
||||
}
|
||||
|
||||
db.close();
|
||||
|
||||
content.indexedDB.deleteDatabase("idb");
|
||||
});
|
||||
}
|
||||
|
||||
@ -127,7 +131,12 @@ add_task(async function test_quota_clearStoragesForPrincipal() {
|
||||
let httpURI = caUtils.makeURI("http://" + TEST_HOST);
|
||||
let httpPrincipal = Services.scriptSecurityManager
|
||||
.createCodebasePrincipal(httpURI, {});
|
||||
Services.qms.clearStoragesForPrincipal(httpPrincipal, null, true);
|
||||
let clearRequest = Services.qms.clearStoragesForPrincipal(httpPrincipal, null, null, true);
|
||||
await new Promise(resolve => {
|
||||
clearRequest.callback = () => {
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
|
||||
for (let userContextId of Object.keys(USER_CONTEXTS)) {
|
||||
// Open our tab in the given user context.
|
||||
|
@ -77,22 +77,26 @@ const clearHistory = options => {
|
||||
const clearIndexedDB = async function(options) {
|
||||
let promises = [];
|
||||
|
||||
await new Promise(resolve => {
|
||||
await new Promise((resolve, reject) => {
|
||||
quotaManagerService.getUsage(request => {
|
||||
if (request.resultCode != Cr.NS_OK) {
|
||||
// We are probably shutting down. We don't want to propagate the error,
|
||||
// rejecting the promise.
|
||||
resolve();
|
||||
reject({message: "Clear indexedDB failed"});
|
||||
return;
|
||||
}
|
||||
|
||||
for (let item of request.result) {
|
||||
let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(item.origin);
|
||||
let uri = principal.URI;
|
||||
if (uri.scheme == "http" || uri.scheme == "https" || uri.scheme == "file") {
|
||||
promises.push(new Promise(r => {
|
||||
let req = quotaManagerService.clearStoragesForPrincipal(principal, null, false);
|
||||
req.callback = () => { r(); };
|
||||
let scheme = principal.URI.scheme;
|
||||
if (scheme == "http" || scheme == "https" || scheme == "file") {
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
let clearRequest = quotaManagerService.clearStoragesForPrincipal(principal, null, "idb");
|
||||
clearRequest.callback = () => {
|
||||
if (clearRequest.resultCode == Cr.NS_OK) {
|
||||
resolve();
|
||||
} else {
|
||||
reject({message: "Clear indexedDB failed"});
|
||||
}
|
||||
};
|
||||
}));
|
||||
}
|
||||
}
|
||||
@ -109,6 +113,46 @@ const clearLocalStorage = async function(options) {
|
||||
return Promise.reject(
|
||||
{message: "Firefox does not support clearing localStorage with 'since'."});
|
||||
}
|
||||
|
||||
if (Services.lsm.nextGenLocalStorageEnabled) {
|
||||
// Ideally we could reuse the logic in Sanitizer.jsm or nsIClearDataService,
|
||||
// but this API exposes an ability to wipe data at a much finger granularity
|
||||
// than those APIs. So custom logic is used here to wipe only the QM
|
||||
// localStorage client (when in use).
|
||||
|
||||
let promises = [];
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
quotaManagerService.getUsage(request => {
|
||||
if (request.resultCode != Cr.NS_OK) {
|
||||
reject({message: "Clear localStorage failed"});
|
||||
return;
|
||||
}
|
||||
|
||||
for (let item of request.result) {
|
||||
let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(item.origin);
|
||||
let host = principal.URI.hostPort;
|
||||
if (!options.hostnames || options.hostnames.includes(host)) {
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
let clearRequest = quotaManagerService.clearStoragesForPrincipal(principal, "default", "ls");
|
||||
clearRequest.callback = () => {
|
||||
if (clearRequest.resultCode == Cr.NS_OK) {
|
||||
resolve();
|
||||
} else {
|
||||
reject({message: "Clear localStorage failed"});
|
||||
}
|
||||
};
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
if (options.hostnames) {
|
||||
for (let hostname of options.hostnames) {
|
||||
Services.obs.notifyObservers(null, "extension:purge-localStorage", hostname);
|
||||
|
@ -53,6 +53,9 @@ add_task(async function testIndexedDB() {
|
||||
let origins = [];
|
||||
Services.qms.getUsage(request => {
|
||||
for (let i = 0; i < request.result.length; ++i) {
|
||||
if (request.result[i].usage === 0) {
|
||||
continue;
|
||||
}
|
||||
if (request.result[i].origin.startsWith("http://mochi.test") ||
|
||||
request.result[i].origin.startsWith("http://example.com")) {
|
||||
origins.push(request.result[i].origin);
|
||||
|
@ -60,6 +60,9 @@ add_task(async function testLocalStorage() {
|
||||
await browser.browsingData.remove({}, {localStorage: true});
|
||||
await sendMessageToTabs(tabs, "checkLocalStorageCleared");
|
||||
|
||||
// Cleanup (checkLocalStorageCleared creates empty LS databases).
|
||||
await browser.browsingData.removeLocalStorage({});
|
||||
|
||||
browser.tabs.remove(tabs.map(tab => tab.id));
|
||||
|
||||
browser.test.notifyPass("done");
|
||||
|
@ -9,7 +9,7 @@ support-files =
|
||||
|
||||
[browser_clearSiteData.js]
|
||||
[browser_siteData.js]
|
||||
skip-if = (os == 'linux' && debug) # Bug 1439332
|
||||
skip-if = (os == 'linux' && debug) || verify # Bug 1439332 and bug 1436395
|
||||
[browser_siteData2.js]
|
||||
[browser_siteData3.js]
|
||||
[browser_siteData_multi_select.js]
|
||||
|
@ -44,7 +44,7 @@ add_task(async function() {
|
||||
await new Promise(resolve => {
|
||||
let principal = Services.scriptSecurityManager
|
||||
.createCodebasePrincipalFromOrigin(TEST_QUOTA_USAGE_ORIGIN);
|
||||
let request = Services.qms.clearStoragesForPrincipal(principal, null, true);
|
||||
let request = Services.qms.clearStoragesForPrincipal(principal, null, null, true);
|
||||
request.callback = resolve;
|
||||
});
|
||||
|
||||
|
@ -304,7 +304,7 @@ var SiteDataManager = {
|
||||
// We are clearing *All* across OAs so need to ensure a principal without suffix here,
|
||||
// or the call of `clearStoragesForPrincipal` would fail.
|
||||
principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(originNoSuffix);
|
||||
let request = this._qms.clearStoragesForPrincipal(principal, null, true);
|
||||
let request = this._qms.clearStoragesForPrincipal(principal, null, null, true);
|
||||
request.callback = resolve;
|
||||
}));
|
||||
}
|
||||
|
@ -396,8 +396,17 @@ GetSpecialBaseDomain(const nsCOMPtr<nsIURI>& aCodebase,
|
||||
{
|
||||
*aHandled = false;
|
||||
|
||||
// For a file URI, we return the file path.
|
||||
// Special handling for a file URI.
|
||||
if (NS_URIIsLocalFile(aCodebase)) {
|
||||
// If strict file origin policy is not in effect, all local files are
|
||||
// considered to be same-origin, so return a known dummy domain here.
|
||||
if (!nsScriptSecurityManager::GetStrictFileOriginPolicy()) {
|
||||
*aHandled = true;
|
||||
aBaseDomain.AssignLiteral("UNIVERSAL_FILE_URI_ORIGIN");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Otherwise, we return the file path.
|
||||
nsCOMPtr<nsIURL> url = do_QueryInterface(aCodebase);
|
||||
|
||||
if (url) {
|
||||
|
@ -48,6 +48,8 @@ def get_normalized_signatures(signature, fileAnnot=None):
|
||||
signature = signature.replace(';', ' ')
|
||||
# Normalize spaces.
|
||||
signature = re.sub(r'\s+', ' ', signature).strip()
|
||||
# Remove new-line induced spaces after opening braces.
|
||||
signature = re.sub(r'\(\s+', '(', signature).strip()
|
||||
# Match arguments, and keep only the type.
|
||||
signature = reMatchArg.sub('\g<type>', signature)
|
||||
# Remove class name
|
||||
@ -144,45 +146,62 @@ def get_macroassembler_definitions(filename):
|
||||
return []
|
||||
|
||||
style_section = False
|
||||
code_section = False
|
||||
lines = ''
|
||||
signatures = []
|
||||
with open(filename) as f:
|
||||
for line in f:
|
||||
if '//{{{ check_macroassembler_style' in line:
|
||||
if style_section:
|
||||
raise 'check_macroassembler_style section already opened.'
|
||||
style_section = True
|
||||
braces_depth = 0
|
||||
elif '//}}} check_macroassembler_style' in line:
|
||||
style_section = False
|
||||
if not style_section:
|
||||
continue
|
||||
|
||||
# Remove comments from the processed line.
|
||||
line = re.sub(r'//.*', '', line)
|
||||
if line.startswith('{') or line.strip() == "{}":
|
||||
|
||||
# Locate and count curly braces.
|
||||
open_curly_brace = line.find('{')
|
||||
was_braces_depth = braces_depth
|
||||
braces_depth = braces_depth + line.count('{') - line.count('}')
|
||||
|
||||
# Raise an error if the check_macroassembler_style macro is used
|
||||
# across namespaces / classes scopes.
|
||||
if braces_depth < 0:
|
||||
raise 'check_macroassembler_style annotations are not well scoped.'
|
||||
|
||||
# If the current line contains an opening curly brace, check if
|
||||
# this line combines with the previous one can be identified as a
|
||||
# MacroAssembler function signature.
|
||||
if open_curly_brace != -1 and was_braces_depth == 0:
|
||||
lines = lines + line[:open_curly_brace]
|
||||
if 'MacroAssembler::' in lines:
|
||||
signatures.extend(
|
||||
get_normalized_signatures(lines, fileAnnot))
|
||||
if line.strip() != "{}": # Empty declaration, no need to declare
|
||||
# a new code section
|
||||
code_section = True
|
||||
continue
|
||||
if line.startswith('}'):
|
||||
code_section = False
|
||||
lines = ''
|
||||
continue
|
||||
if code_section:
|
||||
continue
|
||||
|
||||
if len(line.strip()) == 0:
|
||||
lines = ''
|
||||
# We do not aggregate any lines if we are scanning lines which are
|
||||
# in-between a set of curly braces.
|
||||
if braces_depth > 0:
|
||||
continue
|
||||
if was_braces_depth != 0:
|
||||
line = line[line.rfind('}') + 1:]
|
||||
|
||||
# This logic is used to remove template instantiation, static
|
||||
# variable definitions and function declaration from the next
|
||||
# function definition.
|
||||
last_semi_colon = line.rfind(';')
|
||||
if last_semi_colon != -1:
|
||||
lines = ''
|
||||
line = line[last_semi_colon + 1:]
|
||||
|
||||
# Aggregate lines of non-braced text, which corresponds to the space
|
||||
# where we are expecting to find function definitions.
|
||||
lines = lines + line
|
||||
# Continue until we have a complete declaration
|
||||
if '{' not in lines:
|
||||
continue
|
||||
# Skip variable declarations
|
||||
if ')' not in lines:
|
||||
lines = ''
|
||||
continue
|
||||
|
||||
return signatures
|
||||
|
||||
@ -201,14 +220,17 @@ def get_macroassembler_declaration(filename):
|
||||
continue
|
||||
|
||||
line = re.sub(r'//.*', '', line)
|
||||
if len(line.strip()) == 0:
|
||||
if len(line.strip()) == 0 or 'public:' in line or 'private:' in line:
|
||||
lines = ''
|
||||
continue
|
||||
lines = lines + line
|
||||
|
||||
# Continue until we have a complete declaration
|
||||
if ';' not in lines:
|
||||
continue
|
||||
# Skip variable declarations
|
||||
|
||||
# Skip member declarations: which are lines ending with a
|
||||
# semi-colon without any list of arguments.
|
||||
if ')' not in lines:
|
||||
lines = ''
|
||||
continue
|
||||
|
@ -23,6 +23,8 @@ support-files =
|
||||
!/devtools/client/shared/test/telemetry-test-helpers.js
|
||||
|
||||
[browser_addons_debug_bootstrapped.js]
|
||||
# To be removed in bug 1497264
|
||||
skip-if = true
|
||||
[browser_addons_debug_info.js]
|
||||
[browser_addons_debug_webextension.js]
|
||||
tags = webextensions
|
||||
@ -34,11 +36,16 @@ tags = webextensions
|
||||
skip-if = (verify && debug) || (debug && os == "linux" && bits == 64) # verify: crashes on shutdown, timeouts linux debug Bug 1299001
|
||||
tags = webextensions
|
||||
[browser_addons_debugging_initial_state.js]
|
||||
# To be removed or updated in bug 1497264
|
||||
skip-if = true
|
||||
[browser_addons_install.js]
|
||||
skip-if = verify && debug
|
||||
# To be updated in bug 1497264 (was "verify && debug")
|
||||
skip-if = true
|
||||
[browser_addons_reload.js]
|
||||
[browser_addons_remove.js]
|
||||
[browser_addons_toggle_debug.js]
|
||||
# To be removed or updated in bug 1497264
|
||||
skip-if = true
|
||||
[browser_page_not_found.js]
|
||||
[browser_service_workers.js]
|
||||
[browser_service_workers_fetch_flag.js]
|
||||
|
@ -13,6 +13,8 @@ function testFilePath(container, expectedFilePath) {
|
||||
is(filePath.previousElementSibling.textContent, "Location", "file path has label");
|
||||
}
|
||||
|
||||
// Remove in Bug 1497264
|
||||
/*
|
||||
add_task(async function testLegacyAddon() {
|
||||
const addonId = "test-devtools@mozilla.org";
|
||||
const addonName = "test-devtools";
|
||||
@ -32,6 +34,7 @@ add_task(async function testLegacyAddon() {
|
||||
|
||||
await closeAboutDebugging(tab);
|
||||
});
|
||||
*/
|
||||
|
||||
add_task(async function testWebExtension() {
|
||||
const addonId = "test-devtools-webextension-nobg@mozilla.org";
|
||||
|
@ -7,9 +7,6 @@ loader.lazyImporter(this, "AddonTestUtils",
|
||||
|
||||
AddonTestUtils.initMochitest(this);
|
||||
|
||||
const ADDON_ID = "test-devtools@mozilla.org";
|
||||
const ADDON_NAME = "test-devtools";
|
||||
|
||||
function mockFilePicker(window, file) {
|
||||
// Mock the file picker to select a test addon
|
||||
const MockFilePicker = SpecialPowers.MockFilePicker;
|
||||
@ -35,6 +32,9 @@ function promiseWriteWebManifestForExtension(manifest, dir) {
|
||||
}
|
||||
|
||||
add_task(async function testLegacyInstallSuccess() {
|
||||
const ADDON_ID = "test-devtools@mozilla.org";
|
||||
const ADDON_NAME = "test-devtools";
|
||||
|
||||
const { tab, document } = await openAboutDebugging("addons");
|
||||
await waitForInitialAddonList(document);
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
"use strict";
|
||||
|
||||
const ADDON_ID = "test-devtools@mozilla.org";
|
||||
const ADDON_NAME = "test-devtools";
|
||||
|
||||
const PACKAGED_ADDON_ID = "bug1273184@tests";
|
||||
const PACKAGED_ADDON_NAME = "bug 1273184";
|
||||
@ -60,7 +59,10 @@ class TempWebExt {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove in Bug 1497264
|
||||
/*
|
||||
add_task(async function reloadButtonReloadsAddon() {
|
||||
const ADDON_NAME = "test-devtools";
|
||||
const { tab, document, window } = await openAboutDebugging("addons");
|
||||
const { AboutDebugging } = window;
|
||||
await waitForInitialAddonList(document);
|
||||
@ -96,6 +98,7 @@ add_task(async function reloadButtonReloadsAddon() {
|
||||
await tearDownAddon(AboutDebugging, reloadedAddon);
|
||||
await closeAboutDebugging(tab);
|
||||
});
|
||||
*/
|
||||
|
||||
add_task(async function reloadButtonRefreshesMetadata() {
|
||||
const { tab, document, window } = await openAboutDebugging("addons");
|
||||
|
@ -12,6 +12,8 @@ function getRemoveButton(document, id) {
|
||||
return document.querySelector(`[data-addon-id="${id}"] .uninstall-button`);
|
||||
}
|
||||
|
||||
// Remove in Bug 1497264
|
||||
/*
|
||||
add_task(async function removeLegacyExtension() {
|
||||
const addonID = "test-devtools@mozilla.org";
|
||||
const addonName = "test-devtools";
|
||||
@ -36,6 +38,7 @@ add_task(async function removeLegacyExtension() {
|
||||
|
||||
await closeAboutDebugging(tab);
|
||||
});
|
||||
*/
|
||||
|
||||
add_task(async function removeWebextension() {
|
||||
const addonID = "test-devtools-webextension@mozilla.org";
|
||||
|
@ -401,7 +401,7 @@ class FlexboxInspector {
|
||||
* highlighter is toggled on/off for.
|
||||
*/
|
||||
onToggleFlexboxHighlighter(node) {
|
||||
this.highlighters.toggleFlexboxHighlighter(node);
|
||||
this.highlighters.toggleFlexboxHighlighter(node, "layout");
|
||||
this.store.dispatch(updateFlexboxHighlighted(node !==
|
||||
this.highlighters.flexboxHighlighterShow));
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ support-files =
|
||||
[browser_flexbox_empty_state.js]
|
||||
[browser_flexbox_highlighter_color_picker_on_ESC.js]
|
||||
[browser_flexbox_highlighter_color_picker_on_RETURN.js]
|
||||
[browser_flexbox_highlighter_opened_telemetry.js]
|
||||
[browser_flexbox_item_list_01.js]
|
||||
[browser_flexbox_item_list_02.js]
|
||||
[browser_flexbox_item_list_updates_on_change.js]
|
||||
|
@ -0,0 +1,32 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the telemetry is correct when the flexbox highlighter is activated from
|
||||
// the layout view.
|
||||
|
||||
const TEST_URI = URL_ROOT + "doc_flexbox_specific_cases.html";
|
||||
|
||||
add_task(async function() {
|
||||
await addTab(TEST_URI);
|
||||
startTelemetry();
|
||||
const { inspector, flexboxInspector } = await openLayoutView();
|
||||
const { document: doc } = flexboxInspector;
|
||||
const { highlighters, store } = inspector;
|
||||
|
||||
const onFlexHighlighterToggleRendered = waitForDOM(doc, "#flexbox-checkbox-toggle");
|
||||
await selectNode("#container", inspector);
|
||||
const [flexHighlighterToggle] = await onFlexHighlighterToggleRendered;
|
||||
|
||||
await toggleHighlighterON(flexHighlighterToggle, highlighters, store);
|
||||
await toggleHighlighterOFF(flexHighlighterToggle, highlighters, store);
|
||||
|
||||
checkResults();
|
||||
});
|
||||
|
||||
function checkResults() {
|
||||
checkTelemetry("devtools.layout.flexboxhighlighter.opened", "", 1, "scalar");
|
||||
checkTelemetry("DEVTOOLS_FLEXBOX_HIGHLIGHTER_TIME_ACTIVE_SECONDS", "", null,
|
||||
"hasentries");
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* eslint no-unused-vars: [2, {"vars": "local"}] */
|
||||
/* import-globals-from ../../../shared/test/telemetry-test-helpers.js */
|
||||
/* import-globals-from ../../test/head.js */
|
||||
"use strict";
|
||||
|
||||
|
@ -39,6 +39,7 @@ skip-if = (verify && (os == 'win'))
|
||||
[browser_grids_grid-outline-writing-mode.js]
|
||||
skip-if = (verify && (os == 'win'))
|
||||
[browser_grids_highlighter-setting-rules-grid-toggle.js]
|
||||
[browser_grids_highlighter-toggle-telemetry.js]
|
||||
[browser_grids_number-of-css-grids-telemetry.js]
|
||||
[browser_grids_persist-color-palette.js]
|
||||
[browser_grids_restored-after-reload.js]
|
||||
|
@ -0,0 +1,46 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the telemetry count is correct when the grid highlighter is activated from
|
||||
// the layout view.
|
||||
|
||||
const TEST_URI = `
|
||||
<style type='text/css'>
|
||||
#grid {
|
||||
display: grid;
|
||||
}
|
||||
</style>
|
||||
<div id="grid">
|
||||
<div id="cell1">cell1</div>
|
||||
<div id="cell2">cell2</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
add_task(async function() {
|
||||
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
startTelemetry();
|
||||
const { gridInspector, inspector } = await openLayoutView();
|
||||
const { document: doc } = gridInspector;
|
||||
const { highlighters, store } = inspector;
|
||||
|
||||
await selectNode("#grid", inspector);
|
||||
const gridList = doc.getElementById("grid-list");
|
||||
const checkbox = gridList.children[0].querySelector("input");
|
||||
|
||||
info("Toggling ON the CSS grid highlighter from the layout panel.");
|
||||
const onHighlighterShown = highlighters.once("grid-highlighter-shown");
|
||||
const onCheckboxChange = waitUntilState(store, state =>
|
||||
state.grids.length == 1 &&
|
||||
state.grids[0].highlighted);
|
||||
checkbox.click();
|
||||
await onHighlighterShown;
|
||||
await onCheckboxChange;
|
||||
|
||||
checkResults();
|
||||
});
|
||||
|
||||
function checkResults() {
|
||||
checkTelemetry("devtools.grid.gridinspector.opened", "", 1, "scalar");
|
||||
}
|
@ -135,8 +135,10 @@ skip-if = true # Bug 1177550
|
||||
[browser_markup_events_source_map.js]
|
||||
[browser_markup_events-windowed-host.js]
|
||||
[browser_markup_flex_display_badge.js]
|
||||
[browser_markup_flex_display_badge_telemetry.js]
|
||||
[browser_markup_grid_display_badge_01.js]
|
||||
[browser_markup_grid_display_badge_02.js]
|
||||
[browser_markup_grid_display_badge_telemetry.js]
|
||||
[browser_markup_links_01.js]
|
||||
[browser_markup_links_02.js]
|
||||
[browser_markup_links_03.js]
|
||||
|
@ -0,0 +1,48 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the telemetry is correct when the flexbox highlighter is activated from
|
||||
// the markup view.
|
||||
|
||||
const TEST_URI = `
|
||||
<style type="text/css">
|
||||
#flex {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
<div id="flex"></div>
|
||||
`;
|
||||
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.inspector.flexboxHighlighter.enabled", true);
|
||||
await pushPref("devtools.flexboxinspector.enabled", true);
|
||||
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
startTelemetry();
|
||||
const { inspector } = await openLayoutView();
|
||||
const { highlighters } = inspector;
|
||||
|
||||
await selectNode("#flex", inspector);
|
||||
const flexContainer = await getContainerForSelector("#flex", inspector);
|
||||
const flexDisplayBadge = flexContainer.elt.querySelector(
|
||||
".inspector-badge.interactive[data-display]");
|
||||
|
||||
info("Toggling ON the flexbox highlighter from the flex display badge.");
|
||||
const onHighlighterShown = highlighters.once("flexbox-highlighter-shown");
|
||||
flexDisplayBadge.click();
|
||||
await onHighlighterShown;
|
||||
|
||||
info("Toggling OFF the flexbox highlighter from the flex display badge.");
|
||||
const onHighlighterHidden = highlighters.once("flexbox-highlighter-hidden");
|
||||
flexDisplayBadge.click();
|
||||
await onHighlighterHidden;
|
||||
|
||||
checkResults();
|
||||
});
|
||||
|
||||
function checkResults() {
|
||||
checkTelemetry("devtools.markup.flexboxhighlighter.opened", "", 1, "scalar");
|
||||
checkTelemetry("DEVTOOLS_FLEXBOX_HIGHLIGHTER_TIME_ACTIVE_SECONDS", "", null,
|
||||
"hasentries");
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the telemetry count is correct when the grid highlighter is activated from
|
||||
// the markup view.
|
||||
|
||||
const TEST_URI = `
|
||||
<style type="text/css">
|
||||
#grid {
|
||||
display: grid;
|
||||
}
|
||||
</style>
|
||||
<div id="grid"></div>
|
||||
`;
|
||||
|
||||
add_task(async function() {
|
||||
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
startTelemetry();
|
||||
const { inspector } = await openLayoutView();
|
||||
const { highlighters, store } = inspector;
|
||||
|
||||
await selectNode("#grid", inspector);
|
||||
const gridContainer = await getContainerForSelector("#grid", inspector);
|
||||
const gridDisplayBadge = gridContainer.elt.querySelector(
|
||||
".inspector-badge.interactive[data-display]");
|
||||
|
||||
info("Toggling ON the CSS grid highlighter from the grid display badge.");
|
||||
const onHighlighterShown = highlighters.once("grid-highlighter-shown");
|
||||
const onCheckboxChange = waitUntilState(store, state =>
|
||||
state.grids.length === 1 &&
|
||||
state.grids[0].highlighted);
|
||||
gridDisplayBadge.click();
|
||||
await onHighlighterShown;
|
||||
await onCheckboxChange;
|
||||
|
||||
checkResults();
|
||||
});
|
||||
|
||||
function checkResults() {
|
||||
checkTelemetry("devtools.markup.gridinspector.opened", "", 1, "scalar");
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
* 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/. */
|
||||
/* eslint no-unused-vars: [2, {"vars": "local"}] */
|
||||
/* import-globals-from ../../../shared/test/telemetry-test-helpers.js */
|
||||
/* import-globals-from ../../test/head.js */
|
||||
"use strict";
|
||||
|
||||
|
@ -749,7 +749,7 @@ ElementEditor.prototype = {
|
||||
this.stopTrackingFlexboxHighlighterEvents();
|
||||
|
||||
this._displayBadge.classList.toggle("active");
|
||||
await this.highlighters.toggleFlexboxHighlighter(this.node);
|
||||
await this.highlighters.toggleFlexboxHighlighter(this.node, "markup");
|
||||
|
||||
this.startTrackingFlexboxHighlighterEvents();
|
||||
}
|
||||
|
@ -166,6 +166,7 @@ skip-if = (os == "win" && debug) # bug 963492: win.
|
||||
[browser_rules_flexbox-highlighter-on-navigate.js]
|
||||
[browser_rules_flexbox-highlighter-on-reload.js]
|
||||
[browser_rules_flexbox-highlighter-restored-after-reload.js]
|
||||
[browser_rules_flexbox-toggle-telemetry.js]
|
||||
[browser_rules_flexbox-toggle_01.js]
|
||||
[browser_rules_flexbox-toggle_01b.js]
|
||||
[browser_rules_flexbox-toggle_02.js]
|
||||
@ -176,6 +177,7 @@ skip-if = (os == "win" && debug) # bug 963492: win.
|
||||
[browser_rules_grid-highlighter-on-navigate.js]
|
||||
[browser_rules_grid-highlighter-on-reload.js]
|
||||
[browser_rules_grid-highlighter-restored-after-reload.js]
|
||||
[browser_rules_grid-toggle-telemetry.js]
|
||||
[browser_rules_grid-toggle_01.js]
|
||||
[browser_rules_grid-toggle_01b.js]
|
||||
[browser_rules_grid-toggle_02.js]
|
||||
|
@ -0,0 +1,46 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the telemetry is correct when the flexbox highlighter is activated from
|
||||
// the rules view.
|
||||
|
||||
const TEST_URI = `
|
||||
<style type='text/css'>
|
||||
#flex {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
<div id="flex"></div>
|
||||
`;
|
||||
|
||||
add_task(async function() {
|
||||
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
startTelemetry();
|
||||
const {inspector, view} = await openRuleView();
|
||||
const {highlighters} = view;
|
||||
|
||||
await selectNode("#flex", inspector);
|
||||
const container = getRuleViewProperty(view, "#flex", "display").valueSpan;
|
||||
const flexboxToggle = container.querySelector(".ruleview-flex");
|
||||
|
||||
info("Toggling ON the flexbox highlighter from the rule-view.");
|
||||
const onHighlighterShown = highlighters.once("flexbox-highlighter-shown");
|
||||
flexboxToggle.click();
|
||||
await onHighlighterShown;
|
||||
|
||||
info("Toggling OFF the flexbox highlighter from the rule-view.");
|
||||
const onHighlighterHidden = highlighters.once("flexbox-highlighter-hidden");
|
||||
flexboxToggle.click();
|
||||
await onHighlighterHidden;
|
||||
|
||||
checkResults();
|
||||
});
|
||||
|
||||
function checkResults() {
|
||||
checkTelemetry("devtools.rules.flexboxhighlighter.opened", "", 1, "scalar");
|
||||
checkTelemetry("DEVTOOLS_FLEXBOX_HIGHLIGHTER_TIME_ACTIVE_SECONDS", "", null,
|
||||
"hasentries");
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the telemetry count is correct when the grid highlighter is activated from
|
||||
// the rules view.
|
||||
|
||||
const TEST_URI = `
|
||||
<style type='text/css'>
|
||||
#grid {
|
||||
display: grid;
|
||||
}
|
||||
</style>
|
||||
<div id="grid">
|
||||
<div id="cell1">cell1</div>
|
||||
<div id="cell2">cell2</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
add_task(async function() {
|
||||
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
startTelemetry();
|
||||
const {inspector, view} = await openRuleView();
|
||||
const highlighters = view.highlighters;
|
||||
|
||||
await selectNode("#grid", inspector);
|
||||
const container = getRuleViewProperty(view, "#grid", "display").valueSpan;
|
||||
const gridToggle = container.querySelector(".ruleview-grid");
|
||||
|
||||
info("Toggling ON the CSS grid highlighter from the rule-view.");
|
||||
const onHighlighterShown = highlighters.once("grid-highlighter-shown");
|
||||
gridToggle.click();
|
||||
await onHighlighterShown;
|
||||
|
||||
checkResults();
|
||||
});
|
||||
|
||||
function checkResults() {
|
||||
checkTelemetry("devtools.rules.gridinspector.opened", "", 1, "scalar");
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* eslint no-unused-vars: [2, {"vars": "local"}] */
|
||||
/* import-globals-from ../../../shared/test/telemetry-test-helpers.js */
|
||||
/* import-globals-from ../../test/head.js */
|
||||
"use strict";
|
||||
|
||||
|
@ -258,16 +258,18 @@ class HighlightersOverlay {
|
||||
*
|
||||
* @param {NodeFront} node
|
||||
* The NodeFront of the flexbox container element to highlight.
|
||||
* @param {Object} options
|
||||
* Object used for passing options to the flexbox highlighter.
|
||||
* @param. {String} trigger
|
||||
* String name matching "layout", "markup" or "rule" to indicate where the
|
||||
* flexbox highlighter was toggled on from. "layout" represents the layout view.
|
||||
* "markup" represents the markup view. "rule" represents the rule view.
|
||||
*/
|
||||
async toggleFlexboxHighlighter(node, options = {}) {
|
||||
async toggleFlexboxHighlighter(node, trigger) {
|
||||
if (node == this.flexboxHighlighterShown) {
|
||||
await this.hideFlexboxHighlighter(node);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.showFlexboxHighlighter(node, options);
|
||||
await this.showFlexboxHighlighter(node, {}, trigger);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -277,8 +279,12 @@ class HighlightersOverlay {
|
||||
* The NodeFront of the flexbox container element to highlight.
|
||||
* @param {Object} options
|
||||
* Object used for passing options to the flexbox highlighter.
|
||||
* @param. {String} trigger
|
||||
* String name matching "layout", "markup" or "rule" to indicate where the
|
||||
* flexbox highlighter was toggled on from. "layout" represents the layout view.
|
||||
* "markup" represents the markup view. "rule" represents the rule view.
|
||||
*/
|
||||
async showFlexboxHighlighter(node, options) {
|
||||
async showFlexboxHighlighter(node, options, trigger) {
|
||||
const highlighter = await this._getHighlighter("FlexboxHighlighter");
|
||||
if (!highlighter) {
|
||||
return;
|
||||
@ -293,6 +299,17 @@ class HighlightersOverlay {
|
||||
|
||||
this._toggleRuleViewIcon(node, true, ".ruleview-flex");
|
||||
|
||||
this.telemetry.toolOpened("flexbox_highlighter", this.inspector.toolbox.sessionId,
|
||||
this);
|
||||
|
||||
if (trigger === "layout") {
|
||||
this.telemetry.scalarAdd("devtools.layout.flexboxhighlighter.opened", 1);
|
||||
} else if (trigger === "markup") {
|
||||
this.telemetry.scalarAdd("devtools.markup.flexboxhighlighter.opened", 1);
|
||||
} else if (trigger === "rule") {
|
||||
this.telemetry.scalarAdd("devtools.rules.flexboxhighlighter.opened", 1);
|
||||
}
|
||||
|
||||
try {
|
||||
// Save flexbox highlighter state.
|
||||
const { url } = this.inspector.target;
|
||||
@ -319,6 +336,9 @@ class HighlightersOverlay {
|
||||
return;
|
||||
}
|
||||
|
||||
this.telemetry.toolClosed("flexbox_highlighter", this.inspector.toolbox.sessionId,
|
||||
this);
|
||||
|
||||
this._toggleRuleViewIcon(node, false, ".ruleview-flex");
|
||||
|
||||
await this.highlighters.FlexboxHighlighter.hide();
|
||||
@ -407,10 +427,10 @@ class HighlightersOverlay {
|
||||
*
|
||||
* @param {NodeFront} node
|
||||
* The NodeFront of the grid container element to highlight.
|
||||
* @param. {String|null} trigger
|
||||
* String name matching "grid" or "rule" to indicate where the
|
||||
* grid highlighter was toggled on from. "grid" represents the grid view
|
||||
* "rule" represents the rule view.
|
||||
* @param. {String} trigger
|
||||
* String name matching "grid", "markup" or "rule" to indicate where the
|
||||
* grid highlighter was toggled on from. "grid" represents the grid view.
|
||||
* "markup" represents the markup view. "rule" represents the rule view.
|
||||
*/
|
||||
async toggleGridHighlighter(node, trigger) {
|
||||
if (this.gridHighlighters.has(node)) {
|
||||
@ -428,10 +448,10 @@ class HighlightersOverlay {
|
||||
* The NodeFront of the grid container element to highlight.
|
||||
* @param {Object} options
|
||||
* Object used for passing options to the grid highlighter.
|
||||
* @param. {String|null} trigger
|
||||
* String name matching "grid" or "rule" to indicate where the
|
||||
* grid highlighter was toggled on from. "grid" represents the grid view
|
||||
* "rule" represents the rule view.
|
||||
* @param. {String} trigger
|
||||
* String name matching "grid", "markup" or "rule" to indicate where the
|
||||
* grid highlighter was toggled on from. "grid" represents the grid view.
|
||||
* "markup" represents the markup view. "rule" represents the rule view.
|
||||
*/
|
||||
async showGridHighlighter(node, options, trigger) {
|
||||
// When the grid highlighter has the given node, it is probably called with new
|
||||
@ -464,9 +484,11 @@ class HighlightersOverlay {
|
||||
|
||||
this._toggleRuleViewIcon(node, true, ".ruleview-grid");
|
||||
|
||||
if (trigger == "grid") {
|
||||
if (trigger === "grid") {
|
||||
this.telemetry.scalarAdd("devtools.grid.gridinspector.opened", 1);
|
||||
} else if (trigger == "rule") {
|
||||
} else if (trigger === "markup") {
|
||||
this.telemetry.scalarAdd("devtools.markup.gridinspector.opened", 1);
|
||||
} else if (trigger === "rule") {
|
||||
this.telemetry.scalarAdd("devtools.rules.gridinspector.opened", 1);
|
||||
}
|
||||
|
||||
@ -968,7 +990,7 @@ class HighlightersOverlay {
|
||||
|
||||
if (this._isRuleViewDisplayFlex(event.target)) {
|
||||
event.stopPropagation();
|
||||
this.toggleFlexboxHighlighter(this.inspector.selection.nodeFront);
|
||||
this.toggleFlexboxHighlighter(this.inspector.selection.nodeFront, "rule");
|
||||
}
|
||||
|
||||
if (this._isRuleViewShapeSwatch(event.target)) {
|
||||
|
@ -732,16 +732,14 @@ function getChartsFromToolId(id) {
|
||||
timerHist = `DEVTOOLS_${id}_TIME_ACTIVE_SECONDS`;
|
||||
countHist = `DEVTOOLS_${id}_OPENED_COUNT`;
|
||||
break;
|
||||
case "FLEXBOX_HIGHLIGHTER":
|
||||
timerHist = `DEVTOOLS_${id}_TIME_ACTIVE_SECONDS`;
|
||||
break;
|
||||
default:
|
||||
timerHist = `DEVTOOLS_CUSTOM_TIME_ACTIVE_SECONDS`;
|
||||
countHist = `DEVTOOLS_CUSTOM_OPENED_COUNT`;
|
||||
}
|
||||
|
||||
if (!timerHist || (!countHist && !countScalar)) {
|
||||
throw new Error(`getChartsFromToolId cannot be called without a timer ` +
|
||||
`histogram and either a count histogram or count scalar.`);
|
||||
}
|
||||
|
||||
return {
|
||||
useTimedEvent: useTimedEvent,
|
||||
timerHist: timerHist,
|
||||
|
@ -77,7 +77,9 @@ support-files =
|
||||
[browser_cubic-bezier-06.js]
|
||||
[browser_cubic-bezier-07.js]
|
||||
[browser_dbg_addon-console.js]
|
||||
skip-if = (e10s && debug || os == 'win' || verify)
|
||||
# To be removed or updated in bug 1497264
|
||||
# previously was: (e10s && debug || os == 'win' || verify)
|
||||
skip-if = true
|
||||
tags = addons
|
||||
[browser_dbg_debugger-statement.js]
|
||||
skip-if = e10s && debug
|
||||
@ -245,7 +247,9 @@ skip-if = !e10s || os == "win" # RDM only works for remote tabs, Win: bug 140419
|
||||
skip-if = verify
|
||||
[browser_theme_switching.js]
|
||||
[browser_dbg_listaddons.js]
|
||||
skip-if = e10s && debug
|
||||
# To be removed or updated in bug 1497264
|
||||
# previously was: e10s && debug
|
||||
skip-if = true
|
||||
tags = addons
|
||||
[browser_dbg_listtabs-01.js]
|
||||
[browser_dbg_listtabs-02.js]
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "nsAboutRedirector.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsAboutProtocolUtils.h"
|
||||
#include "nsBaseChannel.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "nsIProtocolHandler.h"
|
||||
|
||||
@ -27,6 +28,36 @@ struct RedirEntry
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
class CrashChannel final : public nsBaseChannel
|
||||
{
|
||||
public:
|
||||
explicit CrashChannel(nsIURI* aURI)
|
||||
{
|
||||
SetURI(aURI);
|
||||
}
|
||||
|
||||
nsresult OpenContentStream(bool async, nsIInputStream **stream,
|
||||
nsIChannel** channel) override
|
||||
{
|
||||
nsAutoCString spec;
|
||||
mURI->GetSpec(spec);
|
||||
|
||||
if (spec.EqualsASCII("about:crashparent") && XRE_IsParentProcess()) {
|
||||
MOZ_CRASH("Crash via about:crashparent");
|
||||
}
|
||||
|
||||
if (spec.EqualsASCII("about:crashcontent") && XRE_IsContentProcess()) {
|
||||
MOZ_CRASH("Crash via about:crashcontent");
|
||||
}
|
||||
|
||||
NS_WARNING("Unhandled about:crash* URI or wrong process");
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ~CrashChannel() = default;
|
||||
};
|
||||
|
||||
/*
|
||||
Entries which do not have URI_SAFE_FOR_UNTRUSTED_CONTENT will run with chrome
|
||||
privileges. This is potentially dangerous. Please use
|
||||
@ -145,12 +176,10 @@ static const RedirEntry kRedirMap[] = {
|
||||
},
|
||||
{
|
||||
"crashparent", "about:blank",
|
||||
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
|
||||
nsIAboutModule::HIDE_FROM_ABOUTABOUT
|
||||
},
|
||||
{
|
||||
"crashcontent", "about:blank",
|
||||
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
|
||||
nsIAboutModule::HIDE_FROM_ABOUTABOUT |
|
||||
nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
|
||||
nsIAboutModule::URI_MUST_LOAD_IN_CHILD
|
||||
@ -174,12 +203,10 @@ nsAboutRedirector::NewChannel(nsIURI* aURI,
|
||||
nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (XRE_IsParentProcess() && path.EqualsASCII("crashparent")) {
|
||||
MOZ_CRASH("Crash via about:crashparent");
|
||||
}
|
||||
|
||||
if (XRE_IsContentProcess() && path.EqualsASCII("crashcontent")) {
|
||||
MOZ_CRASH("Crash via about:crashcontent");
|
||||
if (path.EqualsASCII("crashparent") || path.EqualsASCII("crashcontent")) {
|
||||
nsCOMPtr<nsIChannel> channel = new CrashChannel(aURI);
|
||||
channel.forget(aResult);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#ifdef ABOUT_CONFIG_BLOCKED_GV
|
||||
|
@ -115,3 +115,4 @@ support-files = file_framedhistoryframes.html
|
||||
[test_pushState_after_document_open.html]
|
||||
[test_windowedhistoryframes.html]
|
||||
[test_triggeringprincipal_location_seturi.html]
|
||||
[test_bug1507702.html]
|
||||
|
57
docshell/test/mochitest/test_bug1507702.html
Normal file
57
docshell/test/mochitest/test_bug1507702.html
Normal file
@ -0,0 +1,57 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1507702
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 1507702</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<link rel="icon" href="about:crashparent"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1507702">Mozilla Bug 1507702</a>
|
||||
<img src="about:crashparent">
|
||||
<img src="about:crashcontent">
|
||||
<iframe src="about:crashparent"></iframe>
|
||||
<iframe src="about:crashcontent"></iframe>
|
||||
<script>
|
||||
let urls = ["about:crashparent", "about:crashcontent"];
|
||||
async function testFetch() {
|
||||
const url = urls.shift();
|
||||
if (!url) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let threw;
|
||||
try {
|
||||
await fetch(url);
|
||||
threw = false;
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
};
|
||||
|
||||
ok(threw === true, "fetch should reject");
|
||||
return testFetch();
|
||||
}
|
||||
|
||||
document.body.onload = async () => {
|
||||
for (const url of ["about:crashparent", "about:crashcontent"]) {
|
||||
SimpleTest.doesThrow(() => {
|
||||
top.location.href = url;
|
||||
}, "navigation should throw");
|
||||
|
||||
SimpleTest.doesThrow(() => {
|
||||
location.href = url;
|
||||
}, "navigation should throw");
|
||||
}
|
||||
|
||||
await testFetch();
|
||||
SimpleTest.finish();
|
||||
};
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -792,7 +792,9 @@ ParentRunnable::ReadMetadata()
|
||||
|
||||
nsresult rv =
|
||||
qm->EnsureOriginIsInitialized(quota::PERSISTENCE_TYPE_TEMPORARY, mSuffix,
|
||||
mGroup, mOrigin, getter_AddRefs(mDirectory));
|
||||
mGroup, mOrigin,
|
||||
/* aCreateIfNotExists */ true,
|
||||
getter_AddRefs(mDirectory));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
mResult = JS::AsmJSCache_StorageInitFailure;
|
||||
return rv;
|
||||
|
@ -619,10 +619,7 @@ ChromeUtils::IsOriginAttributesEqual(dom::GlobalObject& aGlobal,
|
||||
ChromeUtils::IsOriginAttributesEqual(const dom::OriginAttributesDictionary& aA,
|
||||
const dom::OriginAttributesDictionary& aB)
|
||||
{
|
||||
return aA.mAppId == aB.mAppId &&
|
||||
aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
|
||||
aA.mUserContextId == aB.mUserContextId &&
|
||||
aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
|
||||
return aA == aB;
|
||||
}
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
|
@ -23,6 +23,8 @@
|
||||
#include "mozilla/dom/DOMPrefs.h"
|
||||
#include "mozilla/dom/EventTarget.h"
|
||||
#include "mozilla/dom/LocalStorage.h"
|
||||
#include "mozilla/dom/LocalStorageCommon.h"
|
||||
#include "mozilla/dom/LSObject.h"
|
||||
#include "mozilla/dom/PartitionedLocalStorage.h"
|
||||
#include "mozilla/dom/Storage.h"
|
||||
#include "mozilla/dom/IdleRequest.h"
|
||||
@ -4908,32 +4910,38 @@ nsGlobalWindowInner::GetLocalStorage(ErrorResult& aError)
|
||||
if (access != nsContentUtils::StorageAccess::ePartitionedOrDeny &&
|
||||
(!mLocalStorage ||
|
||||
mLocalStorage->Type() == Storage::ePartitionedLocalStorage)) {
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIDOMStorageManager> storageManager =
|
||||
do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
aError.Throw(rv);
|
||||
return nullptr;
|
||||
}
|
||||
RefPtr<Storage> storage;
|
||||
|
||||
nsString documentURI;
|
||||
if (mDoc) {
|
||||
aError = mDoc->GetDocumentURI(documentURI);
|
||||
if (NS_WARN_IF(aError.Failed())) {
|
||||
if (NextGenLocalStorageEnabled()) {
|
||||
aError = LSObject::CreateForWindow(this, getter_AddRefs(storage));
|
||||
} else {
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIDOMStorageManager> storageManager =
|
||||
do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
aError.Throw(rv);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsString documentURI;
|
||||
if (mDoc) {
|
||||
aError = mDoc->GetDocumentURI(documentURI);
|
||||
if (NS_WARN_IF(aError.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
nsIPrincipal *principal = GetPrincipal();
|
||||
if (!principal) {
|
||||
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
aError = storageManager->CreateStorage(this, principal, documentURI,
|
||||
IsPrivateBrowsing(),
|
||||
getter_AddRefs(storage));
|
||||
}
|
||||
|
||||
nsIPrincipal *principal = GetPrincipal();
|
||||
if (!principal) {
|
||||
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<Storage> storage;
|
||||
aError = storageManager->CreateStorage(this, principal, documentURI,
|
||||
IsPrivateBrowsing(),
|
||||
getter_AddRefs(storage));
|
||||
if (aError.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -5809,7 +5817,8 @@ nsGlobalWindowInner::ObserveStorageNotification(StorageEvent* aEvent,
|
||||
MOZ_DIAGNOSTIC_ASSERT(StorageUtils::PrincipalsEqual(aEvent->GetPrincipal(),
|
||||
principal));
|
||||
|
||||
fireMozStorageChanged = mLocalStorage == aEvent->GetStorageArea();
|
||||
fireMozStorageChanged =
|
||||
mLocalStorage && mLocalStorage == aEvent->GetStorageArea();
|
||||
|
||||
if (fireMozStorageChanged) {
|
||||
eventType.AssignLiteral("MozLocalStorageChanged");
|
||||
@ -5821,7 +5830,7 @@ nsGlobalWindowInner::ObserveStorageNotification(StorageEvent* aEvent,
|
||||
IgnoredErrorResult error;
|
||||
RefPtr<StorageEvent> clonedEvent =
|
||||
CloneStorageEvent(eventType, aEvent, error);
|
||||
if (error.Failed()) {
|
||||
if (error.Failed() || !clonedEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -5856,16 +5865,18 @@ nsGlobalWindowInner::CloneStorageEvent(const nsAString& aType,
|
||||
// If null, this is a localStorage event received by IPC.
|
||||
if (!storageArea) {
|
||||
storage = GetLocalStorage(aRv);
|
||||
if (aRv.Failed() || !storage) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!NextGenLocalStorageEnabled()) {
|
||||
if (aRv.Failed() || !storage) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (storage->Type() == Storage::eLocalStorage) {
|
||||
RefPtr<LocalStorage> localStorage =
|
||||
static_cast<LocalStorage*>(storage.get());
|
||||
if (storage->Type() == Storage::eLocalStorage) {
|
||||
RefPtr<LocalStorage> localStorage =
|
||||
static_cast<LocalStorage*>(storage.get());
|
||||
|
||||
// We must apply the current change to the 'local' localStorage.
|
||||
localStorage->ApplyEvent(aEvent);
|
||||
// We must apply the current change to the 'local' localStorage.
|
||||
localStorage->ApplyEvent(aEvent);
|
||||
}
|
||||
}
|
||||
} else if (storageArea->Type() == Storage::eSessionStorage) {
|
||||
storage = GetSessionStorage(aRv);
|
||||
@ -6790,6 +6801,13 @@ nsGlobalWindowInner::EventListenerAdded(nsAtom* aType)
|
||||
ErrorResult rv;
|
||||
GetLocalStorage(rv);
|
||||
rv.SuppressException();
|
||||
|
||||
if (NextGenLocalStorageEnabled() &&
|
||||
mLocalStorage && mLocalStorage->Type() == Storage::eLocalStorage) {
|
||||
auto object = static_cast<LSObject*>(mLocalStorage.get());
|
||||
|
||||
Unused << NS_WARN_IF(NS_FAILED(object->EnsureObserver()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6803,6 +6821,20 @@ nsGlobalWindowInner::EventListenerRemoved(nsAtom* aType)
|
||||
MOZ_ASSERT(mBeforeUnloadListenerCount >= 0);
|
||||
mTabChild->BeforeUnloadRemoved();
|
||||
}
|
||||
|
||||
if (aType == nsGkAtoms::onstorage) {
|
||||
if (NextGenLocalStorageEnabled() &&
|
||||
mLocalStorage &&
|
||||
mLocalStorage->Type() == Storage::eLocalStorage &&
|
||||
// The remove event is fired even if this isn't the last listener, so
|
||||
// only remove if there are no other listeners left.
|
||||
mListenerManager &&
|
||||
!mListenerManager->HasListenersFor(nsGkAtoms::onstorage)) {
|
||||
auto object = static_cast<LSObject*>(mLocalStorage.get());
|
||||
|
||||
object->DropObserver();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -7964,6 +7996,14 @@ nsGlobalWindowInner::StorageAccessGranted()
|
||||
|
||||
MOZ_ASSERT(mLocalStorage &&
|
||||
mLocalStorage->Type() == Storage::eLocalStorage);
|
||||
|
||||
if (NextGenLocalStorageEnabled() &&
|
||||
mListenerManager &&
|
||||
mListenerManager->HasListenersFor(nsGkAtoms::onstorage)) {
|
||||
auto object = static_cast<LSObject*>(mLocalStorage.get());
|
||||
|
||||
object->EnsureObserver();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "mozilla/dom/ContentFrameMessageManager.h"
|
||||
#include "mozilla/dom/EventTarget.h"
|
||||
#include "mozilla/dom/LocalStorage.h"
|
||||
#include "mozilla/dom/LSObject.h"
|
||||
#include "mozilla/dom/Storage.h"
|
||||
#include "mozilla/dom/IdleRequest.h"
|
||||
#include "mozilla/dom/Performance.h"
|
||||
|
@ -632,7 +632,7 @@ subsuite = clipboard
|
||||
skip-if = toolkit == 'android' #bug 904183
|
||||
[test_copypaste.xhtml]
|
||||
subsuite = clipboard
|
||||
skip-if = toolkit == 'android' #bug 904183
|
||||
skip-if = toolkit == 'android' && !e10s #bug 904183
|
||||
[test_createHTMLDocument.html]
|
||||
[test_data_uri.html]
|
||||
skip-if = verify
|
||||
|
@ -163,6 +163,11 @@ public:
|
||||
return mImpl == aOther.mImpl;
|
||||
}
|
||||
|
||||
bool operator!=(const Optional_base<T, InternalType>& aOther) const
|
||||
{
|
||||
return mImpl != aOther.mImpl;
|
||||
}
|
||||
|
||||
template<typename T1, typename T2>
|
||||
explicit Optional_base(const T1& aValue1, const T2& aValue2)
|
||||
{
|
||||
|
@ -12996,6 +12996,30 @@ class CGDictionary(CGThing):
|
||||
"aOther")],
|
||||
body=body.define())
|
||||
|
||||
def canHaveEqualsOperator(self):
|
||||
return all(m.type.isString() or m.type.isPrimitive() for (m,_) in
|
||||
self.memberInfo)
|
||||
|
||||
def equalsOperator(self):
|
||||
body = CGList([])
|
||||
|
||||
for m, _ in self.memberInfo:
|
||||
memberName = self.makeMemberName(m.identifier.name)
|
||||
memberTest = CGGeneric(fill(
|
||||
"""
|
||||
if (${memberName} != aOther.${memberName}) {
|
||||
return false;
|
||||
}
|
||||
""",
|
||||
memberName=memberName))
|
||||
body.append(memberTest)
|
||||
body.append(CGGeneric("return true;\n"))
|
||||
return ClassMethod(
|
||||
"operator==", "bool",
|
||||
[Argument("const %s&" % self.makeClassName(self.dictionary),
|
||||
"aOther")
|
||||
], const=True, body=body.define())
|
||||
|
||||
def getStructs(self):
|
||||
d = self.dictionary
|
||||
selfName = self.makeClassName(d)
|
||||
@ -13076,6 +13100,9 @@ class CGDictionary(CGThing):
|
||||
else:
|
||||
disallowCopyConstruction = True
|
||||
|
||||
if self.canHaveEqualsOperator():
|
||||
methods.append(self.equalsOperator())
|
||||
|
||||
struct = CGClass(selfName,
|
||||
bases=[ClassBase(self.base())],
|
||||
members=members,
|
||||
|
@ -107,7 +107,7 @@ support-files =
|
||||
[test_browserElement_inproc_CookiesNotThirdParty.html]
|
||||
[test_browserElement_inproc_CopyPaste.html]
|
||||
subsuite = clipboard
|
||||
skip-if = (os == "android") # Disabled on Android, see bug 1230421
|
||||
skip-if = (os == "android" && !e10s) # Disabled on Android, see bug 1230421
|
||||
[test_browserElement_inproc_DataURI.html]
|
||||
[test_browserElement_inproc_ExposableURI.html]
|
||||
[test_browserElement_inproc_FirstPaint.html]
|
||||
|
1
dom/cache/Context.cpp
vendored
1
dom/cache/Context.cpp
vendored
@ -435,6 +435,7 @@ Context::QuotaInitRunnable::Run()
|
||||
mQuotaInfo.mSuffix,
|
||||
mQuotaInfo.mGroup,
|
||||
mQuotaInfo.mOrigin,
|
||||
/* aCreateIfNotExists */ true,
|
||||
getter_AddRefs(mQuotaInfo.mDir));
|
||||
if (NS_FAILED(rv)) {
|
||||
resolver->Resolve(rv);
|
||||
|
2
dom/cache/FileUtils.cpp
vendored
2
dom/cache/FileUtils.cpp
vendored
@ -294,7 +294,7 @@ BodyMaybeUpdatePaddingSize(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir,
|
||||
int64_t fileSize = 0;
|
||||
RefPtr<QuotaObject> quotaObject =
|
||||
quotaManager->GetQuotaObject(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup,
|
||||
aQuotaInfo.mOrigin, bodyFile, &fileSize);
|
||||
aQuotaInfo.mOrigin, bodyFile, -1, &fileSize);
|
||||
MOZ_DIAGNOSTIC_ASSERT(quotaObject);
|
||||
MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0);
|
||||
// XXXtt: bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1422815
|
||||
|
@ -247,5 +247,22 @@ IPCBlobInputStreamThread::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
bool
|
||||
IsOnDOMFileThread()
|
||||
{
|
||||
mozilla::StaticMutexAutoLock lock(gIPCBlobThreadMutex);
|
||||
|
||||
MOZ_ASSERT(!gShutdownHasStarted);
|
||||
MOZ_ASSERT(gIPCBlobThread);
|
||||
|
||||
return gIPCBlobThread->IsOnCurrentThreadInfallible();
|
||||
}
|
||||
|
||||
void
|
||||
AssertIsOnDOMFileThread()
|
||||
{
|
||||
MOZ_ASSERT(IsOnDOMFileThread());
|
||||
}
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
|
@ -54,6 +54,12 @@ private:
|
||||
nsTArray<RefPtr<IPCBlobInputStreamChild>> mPendingActors;
|
||||
};
|
||||
|
||||
bool
|
||||
IsOnDOMFileThread();
|
||||
|
||||
void
|
||||
AssertIsOnDOMFileThread();
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
|
||||
|
@ -18419,11 +18419,13 @@ Maintenance::DirectoryWork()
|
||||
// Idle maintenance may occur before origin is initailized.
|
||||
// Ensure origin is initialized first. It will initialize all origins
|
||||
// for temporary storage including IDB origins.
|
||||
rv = quotaManager->EnsureOriginIsInitialized(persistenceType,
|
||||
suffix,
|
||||
group,
|
||||
origin,
|
||||
getter_AddRefs(directory));
|
||||
rv = quotaManager->EnsureOriginIsInitialized(
|
||||
persistenceType,
|
||||
suffix,
|
||||
group,
|
||||
origin,
|
||||
/* aCreateIfNotExists */ true,
|
||||
getter_AddRefs(directory));
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
@ -21319,6 +21321,7 @@ OpenDatabaseOp::DoDatabaseWork()
|
||||
mSuffix,
|
||||
mGroup,
|
||||
mOrigin,
|
||||
/* aCreateIfNotExists */ true,
|
||||
getter_AddRefs(dbDirectory));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
|
@ -49,6 +49,11 @@ interface nsIDOMStorageManager : nsISupports
|
||||
in AString aDocumentURI,
|
||||
[optional] in bool aPrivate);
|
||||
/**
|
||||
* DEPRECATED. The only good reason to use this was if you were writing a
|
||||
* test and wanted to hackily determine if a preload happened. That's now
|
||||
* covered by `nsILocalStorageManager.isPreloaded` and you should use that if
|
||||
* that's what you want. If LSNG is in use, this will throw.
|
||||
*
|
||||
* Returns instance of DOM storage object for given principal.
|
||||
* If there is no storage managed for the scope, then null is returned and
|
||||
* no object is created. Otherwise, an object (new) for the existing storage
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "mozilla/dom/FileCreatorHelper.h"
|
||||
#include "mozilla/dom/GetFilesHelper.h"
|
||||
#include "mozilla/dom/IPCBlobUtils.h"
|
||||
#include "mozilla/dom/LSObject.h"
|
||||
#include "mozilla/dom/MemoryReportRequest.h"
|
||||
#include "mozilla/dom/PLoginReputationChild.h"
|
||||
#include "mozilla/dom/PushNotifier.h"
|
||||
@ -3811,15 +3812,21 @@ ContentChild::GetSpecificMessageEventTarget(const Message& aMsg)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
void
|
||||
ContentChild::OnChannelReceivedMessage(const Message& aMsg)
|
||||
{
|
||||
if (aMsg.is_sync()) {
|
||||
LSObject::CancelSyncLoop();
|
||||
}
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
if (nsContentUtils::IsMessageInputEvent(aMsg)) {
|
||||
mPendingInputEvents++;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
PContentChild::Result
|
||||
ContentChild::OnMessageReceived(const Message& aMsg)
|
||||
{
|
||||
|
@ -768,10 +768,10 @@ private:
|
||||
virtual already_AddRefed<nsIEventTarget>
|
||||
GetSpecificMessageEventTarget(const Message& aMsg) override;
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
virtual void
|
||||
OnChannelReceivedMessage(const Message& aMsg) override;
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
virtual PContentChild::Result
|
||||
OnMessageReceived(const Message& aMsg) override;
|
||||
|
||||
|
@ -49,6 +49,7 @@
|
||||
#include "mozilla/dom/ExternalHelperAppParent.h"
|
||||
#include "mozilla/dom/GetFilesHelper.h"
|
||||
#include "mozilla/dom/GeolocationBinding.h"
|
||||
#include "mozilla/dom/LocalStorageCommon.h"
|
||||
#include "mozilla/dom/MemoryReportRequest.h"
|
||||
#include "mozilla/dom/Notification.h"
|
||||
#include "mozilla/dom/PContentBridgeParent.h"
|
||||
@ -141,6 +142,7 @@
|
||||
#include "nsIGfxInfo.h"
|
||||
#include "nsIIdleService.h"
|
||||
#include "nsIInterfaceRequestorUtils.h"
|
||||
#include "nsILocalStorageManager.h"
|
||||
#include "nsIMemoryInfoDumper.h"
|
||||
#include "nsIMemoryReporter.h"
|
||||
#include "nsIMozBrowserFrame.h"
|
||||
@ -5707,6 +5709,24 @@ ContentParent::AboutToLoadHttpFtpWyciwygDocumentForChild(nsIChannel* aChannel)
|
||||
UpdateCookieStatus(aChannel);
|
||||
}
|
||||
|
||||
if (!NextGenLocalStorageEnabled()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (principal->GetIsCodebasePrincipal()) {
|
||||
nsCOMPtr<nsILocalStorageManager> lsm =
|
||||
do_GetService("@mozilla.org/dom/localStorage-manager;1");
|
||||
if (NS_WARN_IF(!lsm)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsISupports> dummy;
|
||||
rv = lsm->Preload(principal, nullptr, getter_AddRefs(dummy));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
336
dom/localstorage/ActorsChild.cpp
Normal file
336
dom/localstorage/ActorsChild.cpp
Normal file
@ -0,0 +1,336 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "ActorsChild.h"
|
||||
|
||||
#include "LocalStorageCommon.h"
|
||||
#include "LSDatabase.h"
|
||||
#include "LSObject.h"
|
||||
#include "LSObserver.h"
|
||||
#include "LSSnapshot.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
/*******************************************************************************
|
||||
* LSDatabaseChild
|
||||
******************************************************************************/
|
||||
|
||||
LSDatabaseChild::LSDatabaseChild(LSDatabase* aDatabase)
|
||||
: mDatabase(aDatabase)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aDatabase);
|
||||
|
||||
MOZ_COUNT_CTOR(LSDatabaseChild);
|
||||
}
|
||||
|
||||
LSDatabaseChild::~LSDatabaseChild()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
MOZ_COUNT_DTOR(LSDatabaseChild);
|
||||
}
|
||||
|
||||
void
|
||||
LSDatabaseChild::SendDeleteMeInternal()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (mDatabase) {
|
||||
mDatabase->ClearActor();
|
||||
mDatabase = nullptr;
|
||||
|
||||
MOZ_ALWAYS_TRUE(PBackgroundLSDatabaseChild::SendDeleteMe());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LSDatabaseChild::ActorDestroy(ActorDestroyReason aWhy)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (mDatabase) {
|
||||
mDatabase->ClearActor();
|
||||
#ifdef DEBUG
|
||||
mDatabase = nullptr;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
LSDatabaseChild::RecvRequestAllowToClose()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (mDatabase) {
|
||||
mDatabase->RequestAllowToClose();
|
||||
|
||||
// TODO: A new datastore will be prepared at first LocalStorage API
|
||||
// synchronous call. It would be better to start preparing a new
|
||||
// datastore right here, but asynchronously.
|
||||
// However, we probably shouldn't do that if we are shutting down.
|
||||
}
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
PBackgroundLSSnapshotChild*
|
||||
LSDatabaseChild::AllocPBackgroundLSSnapshotChild(const nsString& aDocumentURI,
|
||||
const bool& aIncreasePeakUsage,
|
||||
const int64_t& aRequestedSize,
|
||||
const int64_t& aMinSize,
|
||||
LSSnapshotInitInfo* aInitInfo)
|
||||
{
|
||||
MOZ_CRASH("PBackgroundLSSnapshotChild actor should be manually constructed!");
|
||||
}
|
||||
|
||||
bool
|
||||
LSDatabaseChild::DeallocPBackgroundLSSnapshotChild(
|
||||
PBackgroundLSSnapshotChild* aActor)
|
||||
{
|
||||
MOZ_ASSERT(aActor);
|
||||
|
||||
delete aActor;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* LSObserverChild
|
||||
******************************************************************************/
|
||||
|
||||
LSObserverChild::LSObserverChild(LSObserver* aObserver)
|
||||
: mObserver(aObserver)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aObserver);
|
||||
|
||||
MOZ_COUNT_CTOR(LSObserverChild);
|
||||
}
|
||||
|
||||
LSObserverChild::~LSObserverChild()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
MOZ_COUNT_DTOR(LSObserverChild);
|
||||
}
|
||||
|
||||
void
|
||||
LSObserverChild::SendDeleteMeInternal()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (mObserver) {
|
||||
mObserver->ClearActor();
|
||||
mObserver = nullptr;
|
||||
|
||||
MOZ_ALWAYS_TRUE(PBackgroundLSObserverChild::SendDeleteMe());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LSObserverChild::ActorDestroy(ActorDestroyReason aWhy)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (mObserver) {
|
||||
mObserver->ClearActor();
|
||||
#ifdef DEBUG
|
||||
mObserver = nullptr;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
LSObserverChild::RecvObserve(const PrincipalInfo& aPrincipalInfo,
|
||||
const uint32_t& aPrivateBrowsingId,
|
||||
const nsString& aDocumentURI,
|
||||
const nsString& aKey,
|
||||
const nsString& aOldValue,
|
||||
const nsString& aNewValue)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (!mObserver) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIPrincipal> principal =
|
||||
PrincipalInfoToPrincipal(aPrincipalInfo, &rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return IPC_FAIL_NO_REASON(this);
|
||||
}
|
||||
|
||||
Storage::NotifyChange(/* aStorage */ nullptr,
|
||||
principal,
|
||||
aKey,
|
||||
aOldValue,
|
||||
aNewValue,
|
||||
/* aStorageType */ kLocalStorageType,
|
||||
aDocumentURI,
|
||||
/* aIsPrivate */ !!aPrivateBrowsingId,
|
||||
/* aImmediateDispatch */ true);
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* LocalStorageRequestChild
|
||||
******************************************************************************/
|
||||
|
||||
LSRequestChild::LSRequestChild(LSRequestChildCallback* aCallback)
|
||||
: mCallback(aCallback)
|
||||
, mFinishing(false)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
MOZ_COUNT_CTOR(LSRequestChild);
|
||||
}
|
||||
|
||||
LSRequestChild::~LSRequestChild()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
MOZ_COUNT_DTOR(LSRequestChild);
|
||||
}
|
||||
|
||||
bool
|
||||
LSRequestChild::Finishing() const
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
return mFinishing;
|
||||
}
|
||||
|
||||
void
|
||||
LSRequestChild::ActorDestroy(ActorDestroyReason aWhy)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
LSRequestChild::Recv__delete__(const LSRequestResponse& aResponse)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mCallback);
|
||||
|
||||
mCallback->OnResponse(aResponse);
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
LSRequestChild::RecvReady()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
mFinishing = true;
|
||||
|
||||
SendFinish();
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* LSSimpleRequestChild
|
||||
******************************************************************************/
|
||||
|
||||
LSSimpleRequestChild::LSSimpleRequestChild(
|
||||
LSSimpleRequestChildCallback* aCallback)
|
||||
: mCallback(aCallback)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aCallback);
|
||||
|
||||
MOZ_COUNT_CTOR(LSSimpleRequestChild);
|
||||
}
|
||||
|
||||
LSSimpleRequestChild::~LSSimpleRequestChild()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
MOZ_COUNT_DTOR(LSSimpleRequestChild);
|
||||
}
|
||||
|
||||
void
|
||||
LSSimpleRequestChild::ActorDestroy(ActorDestroyReason aWhy)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
LSSimpleRequestChild::Recv__delete__(const LSSimpleRequestResponse& aResponse)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
mCallback->OnResponse(aResponse);
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* LSSnapshotChild
|
||||
******************************************************************************/
|
||||
|
||||
LSSnapshotChild::LSSnapshotChild(LSSnapshot* aSnapshot)
|
||||
: mSnapshot(aSnapshot)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aSnapshot);
|
||||
|
||||
MOZ_COUNT_CTOR(LSSnapshotChild);
|
||||
}
|
||||
|
||||
LSSnapshotChild::~LSSnapshotChild()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
MOZ_COUNT_DTOR(LSSnapshotChild);
|
||||
}
|
||||
|
||||
void
|
||||
LSSnapshotChild::SendDeleteMeInternal()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (mSnapshot) {
|
||||
mSnapshot->ClearActor();
|
||||
mSnapshot = nullptr;
|
||||
|
||||
MOZ_ALWAYS_TRUE(PBackgroundLSSnapshotChild::SendDeleteMe());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LSSnapshotChild::ActorDestroy(ActorDestroyReason aWhy)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (mSnapshot) {
|
||||
mSnapshot->ClearActor();
|
||||
#ifdef DEBUG
|
||||
mSnapshot = nullptr;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
LSSnapshotChild::RecvMarkDirty()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (!mSnapshot) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mSnapshot->MarkDirty();
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
313
dom/localstorage/ActorsChild.h
Normal file
313
dom/localstorage/ActorsChild.h
Normal file
@ -0,0 +1,313 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_localstorage_ActorsChild_h
|
||||
#define mozilla_dom_localstorage_ActorsChild_h
|
||||
|
||||
#include "mozilla/dom/PBackgroundLSDatabaseChild.h"
|
||||
#include "mozilla/dom/PBackgroundLSObserverChild.h"
|
||||
#include "mozilla/dom/PBackgroundLSRequestChild.h"
|
||||
#include "mozilla/dom/PBackgroundLSSimpleRequestChild.h"
|
||||
#include "mozilla/dom/PBackgroundLSSnapshotChild.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace ipc {
|
||||
|
||||
class BackgroundChildImpl;
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
namespace dom {
|
||||
|
||||
class LocalStorageManager2;
|
||||
class LSDatabase;
|
||||
class LSObject;
|
||||
class LSObserver;
|
||||
class LSRequestChildCallback;
|
||||
class LSSimpleRequestChildCallback;
|
||||
class LSSnapshot;
|
||||
|
||||
/**
|
||||
* Minimal glue actor with standard IPC-managed new/delete existence that exists
|
||||
* primarily to track the continued existence of the LSDatabase in the child.
|
||||
* Most of the interesting bits happen via PBackgroundLSSnapshot.
|
||||
*
|
||||
* Mutual raw pointers are maintained between LSDatabase and this class that are
|
||||
* cleared at either (expected) when the child starts the deletion process
|
||||
* (SendDeleteMeInternal) or unexpected actor death (ActorDestroy).
|
||||
*
|
||||
* See `PBackgroundLSDatabase.ipdl` for more information.
|
||||
*
|
||||
*
|
||||
* ## Low-Level Lifecycle ##
|
||||
* - Created by LSObject::EnsureDatabase if it had to create a database.
|
||||
* - Deletion begun by LSDatabase's destructor invoking SendDeleteMeInternal
|
||||
* which will result in the parent sending __delete__ which destroys the
|
||||
* actor.
|
||||
*/
|
||||
class LSDatabaseChild final
|
||||
: public PBackgroundLSDatabaseChild
|
||||
{
|
||||
friend class mozilla::ipc::BackgroundChildImpl;
|
||||
friend class LSDatabase;
|
||||
friend class LSObject;
|
||||
|
||||
LSDatabase* mDatabase;
|
||||
|
||||
NS_DECL_OWNINGTHREAD
|
||||
|
||||
public:
|
||||
void
|
||||
AssertIsOnOwningThread() const
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(LSDatabaseChild);
|
||||
}
|
||||
|
||||
private:
|
||||
// Only created by LSObject.
|
||||
explicit LSDatabaseChild(LSDatabase* aDatabase);
|
||||
|
||||
// Only destroyed by mozilla::ipc::BackgroundChildImpl.
|
||||
~LSDatabaseChild();
|
||||
|
||||
void
|
||||
SendDeleteMeInternal();
|
||||
|
||||
// IPDL methods are only called by IPDL.
|
||||
void
|
||||
ActorDestroy(ActorDestroyReason aWhy) override;
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RecvRequestAllowToClose() override;
|
||||
|
||||
PBackgroundLSSnapshotChild*
|
||||
AllocPBackgroundLSSnapshotChild(const nsString& aDocumentURI,
|
||||
const bool& aIncreasePeakUsage,
|
||||
const int64_t& aRequestedSize,
|
||||
const int64_t& aMinSize,
|
||||
LSSnapshotInitInfo* aInitInfo) override;
|
||||
|
||||
bool
|
||||
DeallocPBackgroundLSSnapshotChild(PBackgroundLSSnapshotChild* aActor)
|
||||
override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Minimal IPC-managed (new/delete) actor that exists to receive and relay
|
||||
* "storage" events from changes to LocalStorage that take place in other
|
||||
* processes as their Snapshots are checkpointed to the canonical Datastore in
|
||||
* the parent process.
|
||||
*
|
||||
* See `PBackgroundLSObserver.ipdl` for more info.
|
||||
*/
|
||||
class LSObserverChild final
|
||||
: public PBackgroundLSObserverChild
|
||||
{
|
||||
friend class mozilla::ipc::BackgroundChildImpl;
|
||||
friend class LSObserver;
|
||||
friend class LSObject;
|
||||
|
||||
LSObserver* mObserver;
|
||||
|
||||
NS_DECL_OWNINGTHREAD
|
||||
|
||||
public:
|
||||
void
|
||||
AssertIsOnOwningThread() const
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(LSObserverChild);
|
||||
}
|
||||
|
||||
private:
|
||||
// Only created by LSObject.
|
||||
explicit LSObserverChild(LSObserver* aObserver);
|
||||
|
||||
// Only destroyed by mozilla::ipc::BackgroundChildImpl.
|
||||
~LSObserverChild();
|
||||
|
||||
void
|
||||
SendDeleteMeInternal();
|
||||
|
||||
// IPDL methods are only called by IPDL.
|
||||
void
|
||||
ActorDestroy(ActorDestroyReason aWhy) override;
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RecvObserve(const PrincipalInfo& aPrinciplaInfo,
|
||||
const uint32_t& aPrivateBrowsingId,
|
||||
const nsString& aDocumentURI,
|
||||
const nsString& aKey,
|
||||
const nsString& aOldValue,
|
||||
const nsString& aNewValue) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Minimal glue IPC-managed (new/delete) actor that is used by LSObject and its
|
||||
* RequestHelper to perform synchronous requests on top of an asynchronous
|
||||
* protocol.
|
||||
*
|
||||
* Takes an `LSReuestChildCallback` to be invoked when a response is received
|
||||
* via __delete__.
|
||||
*
|
||||
* See `PBackgroundLSRequest.ipdl`, `LSObject`, and `RequestHelper` for more
|
||||
* info.
|
||||
*/
|
||||
class LSRequestChild final
|
||||
: public PBackgroundLSRequestChild
|
||||
{
|
||||
friend class LSObject;
|
||||
friend class LocalStorageManager2;
|
||||
|
||||
RefPtr<LSRequestChildCallback> mCallback;
|
||||
|
||||
bool mFinishing;
|
||||
|
||||
NS_DECL_OWNINGTHREAD
|
||||
|
||||
public:
|
||||
void
|
||||
AssertIsOnOwningThread() const
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(LSReqeustChild);
|
||||
}
|
||||
|
||||
bool
|
||||
Finishing() const;
|
||||
|
||||
private:
|
||||
// Only created by LSObject.
|
||||
explicit LSRequestChild(LSRequestChildCallback* aCallback);
|
||||
|
||||
// Only destroyed by mozilla::ipc::BackgroundChildImpl.
|
||||
~LSRequestChild();
|
||||
|
||||
// IPDL methods are only called by IPDL.
|
||||
void
|
||||
ActorDestroy(ActorDestroyReason aWhy) override;
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
Recv__delete__(const LSRequestResponse& aResponse) override;
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RecvReady() override;
|
||||
};
|
||||
|
||||
class NS_NO_VTABLE LSRequestChildCallback
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
|
||||
|
||||
virtual void
|
||||
OnResponse(const LSRequestResponse& aResponse) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~LSRequestChildCallback()
|
||||
{ }
|
||||
};
|
||||
|
||||
/**
|
||||
* Minimal glue IPC-managed (new/delete) actor used by `LocalStorageManager2` to
|
||||
* issue asynchronous requests in an asynchronous fashion.
|
||||
*
|
||||
* Takes an `LSSimpleRequestChildCallback` to be invoked when a response is
|
||||
* received via __delete__.
|
||||
*
|
||||
* See `PBackgroundLSSimpleRequest.ipdl` for more info.
|
||||
*/
|
||||
class LSSimpleRequestChild final
|
||||
: public PBackgroundLSSimpleRequestChild
|
||||
{
|
||||
friend class LocalStorageManager2;
|
||||
|
||||
RefPtr<LSSimpleRequestChildCallback> mCallback;
|
||||
|
||||
NS_DECL_OWNINGTHREAD
|
||||
|
||||
public:
|
||||
void
|
||||
AssertIsOnOwningThread() const
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(LSSimpleReqeustChild);
|
||||
}
|
||||
|
||||
private:
|
||||
// Only created by LocalStorageManager2.
|
||||
explicit LSSimpleRequestChild(LSSimpleRequestChildCallback* aCallback);
|
||||
|
||||
// Only destroyed by mozilla::ipc::BackgroundChildImpl.
|
||||
~LSSimpleRequestChild();
|
||||
|
||||
// IPDL methods are only called by IPDL.
|
||||
void
|
||||
ActorDestroy(ActorDestroyReason aWhy) override;
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
Recv__delete__(const LSSimpleRequestResponse& aResponse) override;
|
||||
};
|
||||
|
||||
class NS_NO_VTABLE LSSimpleRequestChildCallback
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
|
||||
|
||||
virtual void
|
||||
OnResponse(const LSSimpleRequestResponse& aResponse) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~LSSimpleRequestChildCallback()
|
||||
{ }
|
||||
};
|
||||
|
||||
/**
|
||||
* Minimal IPC-managed (new/delete) actor that lasts as long as its owning
|
||||
* LSSnapshot.
|
||||
*
|
||||
* Mutual raw pointers are maintained between LSSnapshot and this class that are
|
||||
* cleared at either (expected) when the child starts the deletion process
|
||||
* (SendDeleteMeInternal) or unexpected actor death (ActorDestroy).
|
||||
*
|
||||
* See `PBackgroundLSSnapshot.ipdl` and `LSSnapshot` for more info.
|
||||
*/
|
||||
class LSSnapshotChild final
|
||||
: public PBackgroundLSSnapshotChild
|
||||
{
|
||||
friend class LSDatabase;
|
||||
friend class LSSnapshot;
|
||||
|
||||
LSSnapshot* mSnapshot;
|
||||
|
||||
NS_DECL_OWNINGTHREAD
|
||||
|
||||
public:
|
||||
void
|
||||
AssertIsOnOwningThread() const
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(LSSnapshotChild);
|
||||
}
|
||||
|
||||
private:
|
||||
// Only created by LSDatabase.
|
||||
explicit LSSnapshotChild(LSSnapshot* aSnapshot);
|
||||
|
||||
// Only destroyed by LSDatabaseChild.
|
||||
~LSSnapshotChild();
|
||||
|
||||
void
|
||||
SendDeleteMeInternal();
|
||||
|
||||
// IPDL methods are only called by IPDL.
|
||||
void
|
||||
ActorDestroy(ActorDestroyReason aWhy) override;
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RecvMarkDirty() override;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_localstorage_ActorsChild_h
|
8539
dom/localstorage/ActorsParent.cpp
Normal file
8539
dom/localstorage/ActorsParent.cpp
Normal file
File diff suppressed because it is too large
Load Diff
94
dom/localstorage/ActorsParent.h
Normal file
94
dom/localstorage/ActorsParent.h
Normal file
@ -0,0 +1,94 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_localstorage_ActorsParent_h
|
||||
#define mozilla_dom_localstorage_ActorsParent_h
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace ipc {
|
||||
|
||||
class PBackgroundParent;
|
||||
class PrincipalInfo;
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
namespace dom {
|
||||
|
||||
class LSRequestParams;
|
||||
class LSSimpleRequestParams;
|
||||
class PBackgroundLSDatabaseParent;
|
||||
class PBackgroundLSObserverParent;
|
||||
class PBackgroundLSRequestParent;
|
||||
class PBackgroundLSSimpleRequestParent;
|
||||
|
||||
namespace quota {
|
||||
|
||||
class Client;
|
||||
|
||||
} // namespace quota
|
||||
|
||||
PBackgroundLSDatabaseParent*
|
||||
AllocPBackgroundLSDatabaseParent(
|
||||
const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
|
||||
const uint32_t& aPrivateBrowsingId,
|
||||
const uint64_t& aDatastoreId);
|
||||
|
||||
bool
|
||||
RecvPBackgroundLSDatabaseConstructor(
|
||||
PBackgroundLSDatabaseParent* aActor,
|
||||
const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
|
||||
const uint32_t& aPrivateBrowsingId,
|
||||
const uint64_t& aDatastoreId);
|
||||
|
||||
bool
|
||||
DeallocPBackgroundLSDatabaseParent(PBackgroundLSDatabaseParent* aActor);
|
||||
|
||||
PBackgroundLSObserverParent*
|
||||
AllocPBackgroundLSObserverParent(const uint64_t& aObserverId);
|
||||
|
||||
bool
|
||||
RecvPBackgroundLSObserverConstructor(PBackgroundLSObserverParent* aActor,
|
||||
const uint64_t& aObservereId);
|
||||
|
||||
bool
|
||||
DeallocPBackgroundLSObserverParent(PBackgroundLSObserverParent* aActor);
|
||||
|
||||
PBackgroundLSRequestParent*
|
||||
AllocPBackgroundLSRequestParent(
|
||||
mozilla::ipc::PBackgroundParent* aBackgroundActor,
|
||||
const LSRequestParams& aParams);
|
||||
|
||||
bool
|
||||
RecvPBackgroundLSRequestConstructor(PBackgroundLSRequestParent* aActor,
|
||||
const LSRequestParams& aParams);
|
||||
|
||||
bool
|
||||
DeallocPBackgroundLSRequestParent(PBackgroundLSRequestParent* aActor);
|
||||
|
||||
PBackgroundLSSimpleRequestParent*
|
||||
AllocPBackgroundLSSimpleRequestParent(const LSSimpleRequestParams& aParams);
|
||||
|
||||
bool
|
||||
RecvPBackgroundLSSimpleRequestConstructor(
|
||||
PBackgroundLSSimpleRequestParent* aActor,
|
||||
const LSSimpleRequestParams& aParams);
|
||||
|
||||
bool
|
||||
DeallocPBackgroundLSSimpleRequestParent(
|
||||
PBackgroundLSSimpleRequestParent* aActor);
|
||||
|
||||
namespace localstorage {
|
||||
|
||||
already_AddRefed<mozilla::dom::quota::Client>
|
||||
CreateQuotaClient();
|
||||
|
||||
} // namespace localstorage
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_localstorage_ActorsParent_h
|
364
dom/localstorage/LSDatabase.cpp
Normal file
364
dom/localstorage/LSDatabase.cpp
Normal file
@ -0,0 +1,364 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "LSDatabase.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
namespace {
|
||||
|
||||
typedef nsDataHashtable<nsCStringHashKey, LSDatabase*> LSDatabaseHashtable;
|
||||
|
||||
StaticAutoPtr<LSDatabaseHashtable> gLSDatabases;
|
||||
|
||||
} // namespace
|
||||
|
||||
LSDatabase::LSDatabase(const nsACString& aOrigin)
|
||||
: mActor(nullptr)
|
||||
, mSnapshot(nullptr)
|
||||
, mOrigin(aOrigin)
|
||||
, mAllowedToClose(false)
|
||||
, mRequestedAllowToClose(false)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (!gLSDatabases) {
|
||||
gLSDatabases = new LSDatabaseHashtable();
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!gLSDatabases->Get(mOrigin));
|
||||
gLSDatabases->Put(mOrigin, this);
|
||||
}
|
||||
|
||||
LSDatabase::~LSDatabase()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(!mSnapshot);
|
||||
|
||||
if (!mAllowedToClose) {
|
||||
AllowToClose();
|
||||
}
|
||||
|
||||
if (mActor) {
|
||||
mActor->SendDeleteMeInternal();
|
||||
MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
LSDatabase*
|
||||
LSDatabase::Get(const nsACString& aOrigin)
|
||||
{
|
||||
return gLSDatabases ? gLSDatabases->Get(aOrigin) : nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
LSDatabase::SetActor(LSDatabaseChild* aActor)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aActor);
|
||||
MOZ_ASSERT(!mActor);
|
||||
|
||||
mActor = aActor;
|
||||
}
|
||||
|
||||
void
|
||||
LSDatabase::RequestAllowToClose()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(!mRequestedAllowToClose);
|
||||
|
||||
mRequestedAllowToClose = true;
|
||||
|
||||
if (mSnapshot) {
|
||||
mSnapshot->MarkDirty();
|
||||
} else {
|
||||
AllowToClose();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LSDatabase::NoteFinishedSnapshot(LSSnapshot* aSnapshot)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aSnapshot == mSnapshot);
|
||||
|
||||
mSnapshot = nullptr;
|
||||
|
||||
if (mRequestedAllowToClose) {
|
||||
AllowToClose();
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSDatabase::GetLength(LSObject* aObject,
|
||||
uint32_t* aResult)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aObject);
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(!mAllowedToClose);
|
||||
|
||||
nsresult rv = EnsureSnapshot(aObject);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = mSnapshot->GetLength(aResult);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSDatabase::GetKey(LSObject* aObject,
|
||||
uint32_t aIndex,
|
||||
nsAString& aResult)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aObject);
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(!mAllowedToClose);
|
||||
|
||||
nsresult rv = EnsureSnapshot(aObject);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = mSnapshot->GetKey(aIndex, aResult);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSDatabase::GetItem(LSObject* aObject,
|
||||
const nsAString& aKey,
|
||||
nsAString& aResult)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aObject);
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(!mAllowedToClose);
|
||||
|
||||
nsresult rv = EnsureSnapshot(aObject);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = mSnapshot->GetItem(aKey, aResult);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSDatabase::GetKeys(LSObject* aObject,
|
||||
nsTArray<nsString>& aKeys)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aObject);
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(!mAllowedToClose);
|
||||
|
||||
nsresult rv = EnsureSnapshot(aObject);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = mSnapshot->GetKeys(aKeys);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSDatabase::SetItem(LSObject* aObject,
|
||||
const nsAString& aKey,
|
||||
const nsAString& aValue,
|
||||
LSNotifyInfo& aNotifyInfo)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aObject);
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(!mAllowedToClose);
|
||||
|
||||
nsresult rv = EnsureSnapshot(aObject);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = mSnapshot->SetItem(aKey, aValue, aNotifyInfo);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSDatabase::RemoveItem(LSObject* aObject,
|
||||
const nsAString& aKey,
|
||||
LSNotifyInfo& aNotifyInfo)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aObject);
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(!mAllowedToClose);
|
||||
|
||||
nsresult rv = EnsureSnapshot(aObject);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = mSnapshot->RemoveItem(aKey, aNotifyInfo);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSDatabase::Clear(LSObject* aObject,
|
||||
LSNotifyInfo& aNotifyInfo)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aObject);
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(!mAllowedToClose);
|
||||
|
||||
nsresult rv = EnsureSnapshot(aObject);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = mSnapshot->Clear(aNotifyInfo);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSDatabase::BeginExplicitSnapshot(LSObject* aObject)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aObject);
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(!mAllowedToClose);
|
||||
|
||||
if (mSnapshot) {
|
||||
return NS_ERROR_ALREADY_INITIALIZED;
|
||||
}
|
||||
|
||||
nsresult rv = EnsureSnapshot(aObject, /* aExplicit */ true);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSDatabase::EndExplicitSnapshot(LSObject* aObject)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aObject);
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(!mAllowedToClose);
|
||||
|
||||
if (!mSnapshot) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mSnapshot->Explicit());
|
||||
|
||||
nsresult rv = mSnapshot->End();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSDatabase::EnsureSnapshot(LSObject* aObject,
|
||||
bool aExplicit)
|
||||
{
|
||||
MOZ_ASSERT(aObject);
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT_IF(mSnapshot, !aExplicit);
|
||||
MOZ_ASSERT(!mAllowedToClose);
|
||||
|
||||
if (mSnapshot) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
RefPtr<LSSnapshot> snapshot = new LSSnapshot(this);
|
||||
|
||||
LSSnapshotChild* actor = new LSSnapshotChild(snapshot);
|
||||
|
||||
LSSnapshotInitInfo initInfo;
|
||||
bool ok =
|
||||
mActor->SendPBackgroundLSSnapshotConstructor(actor,
|
||||
aObject->DocumentURI(),
|
||||
/* increasePeakUsage */ true,
|
||||
/* requestedSize */ 131072,
|
||||
/* minSize */ 4096,
|
||||
&initInfo);
|
||||
if (NS_WARN_IF(!ok)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
snapshot->SetActor(actor);
|
||||
|
||||
// This add refs snapshot.
|
||||
nsresult rv = snapshot->Init(initInfo, aExplicit);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// This is cleared in LSSnapshot::Run() before the snapshot is destroyed.
|
||||
mSnapshot = snapshot;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
LSDatabase::AllowToClose()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(!mAllowedToClose);
|
||||
MOZ_ASSERT(!mSnapshot);
|
||||
|
||||
mAllowedToClose = true;
|
||||
|
||||
if (mActor) {
|
||||
mActor->SendAllowToClose();
|
||||
}
|
||||
|
||||
MOZ_ASSERT(gLSDatabases);
|
||||
MOZ_ASSERT(gLSDatabases->Get(mOrigin));
|
||||
gLSDatabases->Remove(mOrigin);
|
||||
|
||||
if (!gLSDatabases->Count()) {
|
||||
gLSDatabases = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
120
dom/localstorage/LSDatabase.h
Normal file
120
dom/localstorage/LSDatabase.h
Normal file
@ -0,0 +1,120 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_localstorage_LSDatabase_h
|
||||
#define mozilla_dom_localstorage_LSDatabase_h
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class LSDatabaseChild;
|
||||
class LSSnapshot;
|
||||
|
||||
class LSDatabase final
|
||||
{
|
||||
LSDatabaseChild* mActor;
|
||||
|
||||
LSSnapshot* mSnapshot;
|
||||
|
||||
const nsCString mOrigin;
|
||||
|
||||
bool mAllowedToClose;
|
||||
bool mRequestedAllowToClose;
|
||||
|
||||
public:
|
||||
explicit LSDatabase(const nsACString& aOrigin);
|
||||
|
||||
static LSDatabase*
|
||||
Get(const nsACString& aOrigin);
|
||||
|
||||
NS_INLINE_DECL_REFCOUNTING(LSDatabase)
|
||||
|
||||
void
|
||||
AssertIsOnOwningThread() const
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(LSDatabase);
|
||||
}
|
||||
|
||||
void
|
||||
SetActor(LSDatabaseChild* aActor);
|
||||
|
||||
void
|
||||
ClearActor()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mActor);
|
||||
|
||||
mActor = nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
IsAllowedToClose() const
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
return mAllowedToClose;
|
||||
}
|
||||
|
||||
void
|
||||
RequestAllowToClose();
|
||||
|
||||
void
|
||||
NoteFinishedSnapshot(LSSnapshot* aSnapshot);
|
||||
|
||||
nsresult
|
||||
GetLength(LSObject* aObject,
|
||||
uint32_t* aResult);
|
||||
|
||||
nsresult
|
||||
GetKey(LSObject* aObject,
|
||||
uint32_t aIndex,
|
||||
nsAString& aResult);
|
||||
|
||||
nsresult
|
||||
GetItem(LSObject* aObject,
|
||||
const nsAString& aKey,
|
||||
nsAString& aResult);
|
||||
|
||||
nsresult
|
||||
GetKeys(LSObject* aObject,
|
||||
nsTArray<nsString>& aKeys);
|
||||
|
||||
nsresult
|
||||
SetItem(LSObject* aObject,
|
||||
const nsAString& aKey,
|
||||
const nsAString& aValue,
|
||||
LSNotifyInfo& aNotifyInfo);
|
||||
|
||||
nsresult
|
||||
RemoveItem(LSObject* aObject,
|
||||
const nsAString& aKey,
|
||||
LSNotifyInfo& aNotifyInfo);
|
||||
|
||||
nsresult
|
||||
Clear(LSObject* aObject,
|
||||
LSNotifyInfo& aNotifyInfo);
|
||||
|
||||
nsresult
|
||||
BeginExplicitSnapshot(LSObject* aObject);
|
||||
|
||||
nsresult
|
||||
EndExplicitSnapshot(LSObject* aObject);
|
||||
|
||||
private:
|
||||
~LSDatabase();
|
||||
|
||||
nsresult
|
||||
EnsureSnapshot(LSObject* aObject,
|
||||
bool aExplicit = false);
|
||||
|
||||
void
|
||||
AllowToClose();
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_localstorage_LSDatabase_h
|
1140
dom/localstorage/LSObject.cpp
Normal file
1140
dom/localstorage/LSObject.cpp
Normal file
File diff suppressed because it is too large
Load Diff
262
dom/localstorage/LSObject.h
Normal file
262
dom/localstorage/LSObject.h
Normal file
@ -0,0 +1,262 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_localstorage_LSObject_h
|
||||
#define mozilla_dom_localstorage_LSObject_h
|
||||
|
||||
#include "mozilla/dom/Storage.h"
|
||||
|
||||
class nsGlobalWindowInner;
|
||||
class nsIPrincipal;
|
||||
class nsPIDOMWindowInner;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ErrorResult;
|
||||
|
||||
namespace ipc {
|
||||
|
||||
class PrincipalInfo;
|
||||
|
||||
} // namespace ipc
|
||||
|
||||
namespace dom {
|
||||
|
||||
class LSDatabase;
|
||||
class LSObjectChild;
|
||||
class LSObserver;
|
||||
class LSRequestChild;
|
||||
class LSRequestChildCallback;
|
||||
class LSRequestParams;
|
||||
class LSRequestResponse;
|
||||
|
||||
/**
|
||||
* Backs the WebIDL `Storage` binding; all content LocalStorage calls are
|
||||
* handled by this class.
|
||||
*
|
||||
* ## Semantics under e10s / multi-process ##
|
||||
*
|
||||
* A snapshot mechanism used in conjuction with stable points ensures that JS
|
||||
* run-to-completion semantics are experienced even if the same origin is
|
||||
* concurrently accessing LocalStorage across multiple content processes.
|
||||
*
|
||||
* ### Snapshot Consistency ###
|
||||
*
|
||||
* An LSSnapshot is created locally whenever the contents of LocalStorage are
|
||||
* about to be read or written (including length). This synchronously
|
||||
* establishes a corresponding Snapshot in PBackground in the parent process.
|
||||
* An effort is made to send as much data from the parent process as possible,
|
||||
* so sites using a small/reasonable amount of LocalStorage data will have it
|
||||
* sent to the content process for immediate access. Sites with greater
|
||||
* LocalStorage usage may only have some of the information relayed. In that
|
||||
* case, the parent Snapshot will ensure that it retains the exact state of the
|
||||
* parent Datastore at the moment the Snapshot was created.
|
||||
*/
|
||||
class LSObject final
|
||||
: public Storage
|
||||
{
|
||||
typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
|
||||
|
||||
friend nsGlobalWindowInner;
|
||||
|
||||
nsAutoPtr<PrincipalInfo> mPrincipalInfo;
|
||||
|
||||
RefPtr<LSDatabase> mDatabase;
|
||||
RefPtr<LSObserver> mObserver;
|
||||
|
||||
uint32_t mPrivateBrowsingId;
|
||||
nsCString mOrigin;
|
||||
nsString mDocumentURI;
|
||||
|
||||
bool mInExplicitSnapshot;
|
||||
|
||||
public:
|
||||
/**
|
||||
* The normal creation path invoked by nsGlobalWindowInner.
|
||||
*/
|
||||
static nsresult
|
||||
CreateForWindow(nsPIDOMWindowInner* aWindow,
|
||||
Storage** aStorage);
|
||||
|
||||
/**
|
||||
* nsIDOMStorageManager creation path for use in testing logic. Supports the
|
||||
* system principal where CreateForWindow does not. This is also why aPrivate
|
||||
* exists separate from the principal; because the system principal can never
|
||||
* be mutated to have a private browsing id even though it can be used in a
|
||||
* window/document marked as private browsing. That's a legacy issue that is
|
||||
* being dealt with, but it's why it exists here.
|
||||
*/
|
||||
static nsresult
|
||||
CreateForPrincipal(nsPIDOMWindowInner* aWindow,
|
||||
nsIPrincipal* aPrincipal,
|
||||
const nsAString& aDocumentURI,
|
||||
bool aPrivate,
|
||||
LSObject** aObject);
|
||||
|
||||
/**
|
||||
* Used for requests from the parent process to the parent process; in that
|
||||
* case we want ActorsParent to know our event-target and this is better than
|
||||
* trying to tunnel the pointer through IPC.
|
||||
*/
|
||||
static already_AddRefed<nsIEventTarget>
|
||||
GetSyncLoopEventTarget();
|
||||
|
||||
/**
|
||||
* Helper invoked by ContentChild::OnChannelReceivedMessage when a sync IPC
|
||||
* message is received. This will be invoked on the IPC I/O thread and it's
|
||||
* necessary to unblock the main thread when this happens to avoid the
|
||||
* potential for browser deadlock. This should only occur in (ugly) testing
|
||||
* scenarios where CPOWs are in use.
|
||||
*
|
||||
* Cancellation will result in the underlying LSRequest being explicitly
|
||||
* canceled, resulting in the parent sending an NS_ERROR_FAILURE result.
|
||||
*/
|
||||
static void
|
||||
CancelSyncLoop();
|
||||
|
||||
void
|
||||
AssertIsOnOwningThread() const
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(LSObject);
|
||||
}
|
||||
|
||||
const nsString&
|
||||
DocumentURI() const
|
||||
{
|
||||
return mDocumentURI;
|
||||
}
|
||||
|
||||
LSRequestChild*
|
||||
StartRequest(nsIEventTarget* aMainEventTarget,
|
||||
const LSRequestParams& aParams,
|
||||
LSRequestChildCallback* aCallback);
|
||||
|
||||
// Storage overrides.
|
||||
StorageType
|
||||
Type() const override;
|
||||
|
||||
bool
|
||||
IsForkOf(const Storage* aStorage) const override;
|
||||
|
||||
int64_t
|
||||
GetOriginQuotaUsage() const override;
|
||||
|
||||
uint32_t
|
||||
GetLength(nsIPrincipal& aSubjectPrincipal,
|
||||
ErrorResult& aError) override;
|
||||
|
||||
void
|
||||
Key(uint32_t aIndex,
|
||||
nsAString& aResult,
|
||||
nsIPrincipal& aSubjectPrincipal,
|
||||
ErrorResult& aError) override;
|
||||
|
||||
void
|
||||
GetItem(const nsAString& aKey,
|
||||
nsAString& aResult,
|
||||
nsIPrincipal& aSubjectPrincipal,
|
||||
ErrorResult& aError) override;
|
||||
|
||||
void
|
||||
GetSupportedNames(nsTArray<nsString>& aNames) override;
|
||||
|
||||
void
|
||||
SetItem(const nsAString& aKey,
|
||||
const nsAString& aValue,
|
||||
nsIPrincipal& aSubjectPrincipal,
|
||||
ErrorResult& aError) override;
|
||||
|
||||
void
|
||||
RemoveItem(const nsAString& aKey,
|
||||
nsIPrincipal& aSubjectPrincipal,
|
||||
ErrorResult& aError) override;
|
||||
|
||||
void
|
||||
Clear(nsIPrincipal& aSubjectPrincipal,
|
||||
ErrorResult& aError) override;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Testing Methods: See Storage.h
|
||||
void
|
||||
Open(nsIPrincipal& aSubjectPrincipal,
|
||||
ErrorResult& aError) override;
|
||||
|
||||
void
|
||||
Close(nsIPrincipal& aSubjectPrincipal,
|
||||
ErrorResult& aError) override;
|
||||
|
||||
void
|
||||
BeginExplicitSnapshot(nsIPrincipal& aSubjectPrincipal,
|
||||
ErrorResult& aError) override;
|
||||
|
||||
void
|
||||
EndExplicitSnapshot(nsIPrincipal& aSubjectPrincipal,
|
||||
ErrorResult& aError) override;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(LSObject, Storage)
|
||||
|
||||
private:
|
||||
LSObject(nsPIDOMWindowInner* aWindow,
|
||||
nsIPrincipal* aPrincipal);
|
||||
|
||||
~LSObject();
|
||||
|
||||
nsresult
|
||||
DoRequestSynchronously(const LSRequestParams& aParams,
|
||||
LSRequestResponse& aResponse);
|
||||
|
||||
nsresult
|
||||
EnsureDatabase();
|
||||
|
||||
void
|
||||
DropDatabase();
|
||||
|
||||
/**
|
||||
* Invoked by nsGlobalWindowInner whenever a new "storage" event listener is
|
||||
* added to the window in order to ensure that "storage" events are received
|
||||
* from other processes. (`LSObject::OnChange` directly invokes
|
||||
* `Storage::NotifyChange` to notify in-process listeners.)
|
||||
*
|
||||
* If this is the first request in the process for an observer for this
|
||||
* origin, this will trigger a RequestHelper-mediated synchronous LSRequest
|
||||
* to prepare a new observer in the parent process and also construction of
|
||||
* corresponding actors, which will result in the observer being fully
|
||||
* registered in the parent process.
|
||||
*/
|
||||
nsresult
|
||||
EnsureObserver();
|
||||
|
||||
/**
|
||||
* Invoked by nsGlobalWindowInner whenever its last "storage" event listener
|
||||
* is removed.
|
||||
*/
|
||||
void
|
||||
DropObserver();
|
||||
|
||||
/**
|
||||
* Internal helper method used by mutation methods that wraps the call to
|
||||
* Storage::NotifyChange to generate same-process "storage" events.
|
||||
*/
|
||||
void
|
||||
OnChange(const nsAString& aKey,
|
||||
const nsAString& aOldValue,
|
||||
const nsAString& aNewValue);
|
||||
|
||||
nsresult
|
||||
EndExplicitSnapshotInternal();
|
||||
|
||||
// Storage overrides.
|
||||
void
|
||||
LastRelease() override;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_localstorage_LSObject_h
|
76
dom/localstorage/LSObserver.cpp
Normal file
76
dom/localstorage/LSObserver.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "LSObserver.h"
|
||||
|
||||
#include "mozilla/ipc/BackgroundChild.h"
|
||||
#include "mozilla/ipc/BackgroundUtils.h"
|
||||
#include "mozilla/ipc/PBackgroundChild.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIScriptObjectPrincipal.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
namespace {
|
||||
|
||||
typedef nsDataHashtable<nsCStringHashKey, LSObserver*> LSObserverHashtable;
|
||||
|
||||
StaticAutoPtr<LSObserverHashtable> gLSObservers;
|
||||
|
||||
} // namespace
|
||||
|
||||
LSObserver::LSObserver(const nsACString& aOrigin)
|
||||
: mActor(nullptr)
|
||||
, mOrigin(aOrigin)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (!gLSObservers) {
|
||||
gLSObservers = new LSObserverHashtable();
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!gLSObservers->Get(mOrigin));
|
||||
gLSObservers->Put(mOrigin, this);
|
||||
}
|
||||
|
||||
LSObserver::~LSObserver()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (mActor) {
|
||||
mActor->SendDeleteMeInternal();
|
||||
MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
|
||||
}
|
||||
|
||||
MOZ_ASSERT(gLSObservers);
|
||||
MOZ_ASSERT(gLSObservers->Get(mOrigin));
|
||||
gLSObservers->Remove(mOrigin);
|
||||
|
||||
if (!gLSObservers->Count()) {
|
||||
gLSObservers = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
LSObserver*
|
||||
LSObserver::Get(const nsACString& aOrigin)
|
||||
{
|
||||
return gLSObservers ? gLSObservers->Get(aOrigin) : nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
LSObserver::SetActor(LSObserverChild* aActor)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aActor);
|
||||
MOZ_ASSERT(!mActor);
|
||||
|
||||
mActor = aActor;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
74
dom/localstorage/LSObserver.h
Normal file
74
dom/localstorage/LSObserver.h
Normal file
@ -0,0 +1,74 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_localstorage_LSObserver_h
|
||||
#define mozilla_dom_localstorage_LSObserver_h
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class LSObserverChild;
|
||||
|
||||
/**
|
||||
* Effectively just a refcounted life-cycle management wrapper around
|
||||
* LSObserverChild which exists to receive "storage" event information from
|
||||
* other processes. (Same-process events are handled within the process, see
|
||||
* `LSObject::OnChange`.)
|
||||
*
|
||||
* ## Lifecycle ##
|
||||
* - Created by LSObject::EnsureObserver via synchronous LSRequest idiom
|
||||
* whenever the first window's origin adds a "storage" event. Placed in the
|
||||
* gLSObservers LSObserverHashtable for subsequent LSObject's via
|
||||
* LSObserver::Get lookup.
|
||||
* - The LSObserverChild directly handles "Observe" messages, shunting them
|
||||
* directly to Storage::NotifyChange which does all the legwork of notifying
|
||||
* windows about "storage" events.
|
||||
* - Destroyed when refcount goes to zero due to all owning LSObjects being
|
||||
* destroyed or having their `LSObject::DropObserver` methods invoked due to
|
||||
* the last "storage" event listener being removed from the owning window.
|
||||
*/
|
||||
class LSObserver final
|
||||
{
|
||||
friend class LSObject;
|
||||
|
||||
LSObserverChild* mActor;
|
||||
|
||||
const nsCString mOrigin;
|
||||
|
||||
public:
|
||||
explicit LSObserver(const nsACString& aOrigin);
|
||||
|
||||
static LSObserver*
|
||||
Get(const nsACString& aOrigin);
|
||||
|
||||
NS_INLINE_DECL_REFCOUNTING(LSObserver)
|
||||
|
||||
void
|
||||
AssertIsOnOwningThread() const
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(LSDatabase);
|
||||
}
|
||||
|
||||
void
|
||||
SetActor(LSObserverChild* aActor);
|
||||
|
||||
void
|
||||
ClearActor()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mActor);
|
||||
|
||||
mActor = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
~LSObserver();
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_localstorage_LSObserver_h
|
785
dom/localstorage/LSSnapshot.cpp
Normal file
785
dom/localstorage/LSSnapshot.cpp
Normal file
@ -0,0 +1,785 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "LSSnapshot.h"
|
||||
|
||||
#include "nsContentUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
namespace {
|
||||
|
||||
const uint32_t kSnapshotTimeoutMs = 20000;
|
||||
|
||||
} // namespace
|
||||
|
||||
LSSnapshot::LSSnapshot(LSDatabase* aDatabase)
|
||||
: mDatabase(aDatabase)
|
||||
, mActor(nullptr)
|
||||
, mInitLength(0)
|
||||
, mLength(0)
|
||||
, mExactUsage(0)
|
||||
, mPeakUsage(0)
|
||||
, mLoadState(LoadState::Initial)
|
||||
, mExplicit(false)
|
||||
, mHasPendingStableStateCallback(false)
|
||||
, mHasPendingTimerCallback(false)
|
||||
, mDirty(false)
|
||||
#ifdef DEBUG
|
||||
, mInitialized(false)
|
||||
, mSentFinish(false)
|
||||
#endif
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
}
|
||||
|
||||
LSSnapshot::~LSSnapshot()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mDatabase);
|
||||
MOZ_ASSERT(!mHasPendingStableStateCallback);
|
||||
MOZ_ASSERT(!mHasPendingTimerCallback);
|
||||
MOZ_ASSERT_IF(mInitialized, mSentFinish);
|
||||
|
||||
if (mActor) {
|
||||
mActor->SendDeleteMeInternal();
|
||||
MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LSSnapshot::SetActor(LSSnapshotChild* aActor)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(aActor);
|
||||
MOZ_ASSERT(!mActor);
|
||||
|
||||
mActor = aActor;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSSnapshot::Init(const LSSnapshotInitInfo& aInitInfo,
|
||||
bool aExplicit)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(!mSelfRef);
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(mLoadState == LoadState::Initial);
|
||||
MOZ_ASSERT(!mInitialized);
|
||||
MOZ_ASSERT(!mSentFinish);
|
||||
|
||||
mSelfRef = this;
|
||||
|
||||
LoadState loadState = aInitInfo.loadState();
|
||||
|
||||
const nsTArray<LSItemInfo>& itemInfos = aInitInfo.itemInfos();
|
||||
for (uint32_t i = 0; i < itemInfos.Length(); i++) {
|
||||
const LSItemInfo& itemInfo = itemInfos[i];
|
||||
|
||||
const nsString& value = itemInfo.value();
|
||||
|
||||
if (loadState != LoadState::AllOrderedItems && !value.IsVoid()) {
|
||||
mLoadedItems.PutEntry(itemInfo.key());
|
||||
}
|
||||
|
||||
mValues.Put(itemInfo.key(), value);
|
||||
}
|
||||
|
||||
if (loadState == LoadState::Partial) {
|
||||
mInitLength = aInitInfo.totalLength();
|
||||
mLength = mInitLength;
|
||||
} else if (loadState == LoadState::AllOrderedKeys) {
|
||||
mInitLength = aInitInfo.totalLength();
|
||||
} else {
|
||||
MOZ_ASSERT(loadState == LoadState::AllOrderedItems);
|
||||
}
|
||||
|
||||
mExactUsage = aInitInfo.initialUsage();
|
||||
mPeakUsage = aInitInfo.peakUsage();
|
||||
|
||||
mLoadState = aInitInfo.loadState();
|
||||
|
||||
mExplicit = aExplicit;
|
||||
|
||||
#ifdef DEBUG
|
||||
mInitialized = true;
|
||||
#endif
|
||||
|
||||
if (!mExplicit) {
|
||||
mTimer = NS_NewTimer();
|
||||
MOZ_ASSERT(mTimer);
|
||||
|
||||
ScheduleStableStateCallback();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSSnapshot::GetLength(uint32_t* aResult)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(mInitialized);
|
||||
MOZ_ASSERT(!mSentFinish);
|
||||
|
||||
MaybeScheduleStableStateCallback();
|
||||
|
||||
if (mLoadState == LoadState::Partial) {
|
||||
*aResult = mLength;
|
||||
} else {
|
||||
*aResult = mValues.Count();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSSnapshot::GetKey(uint32_t aIndex,
|
||||
nsAString& aResult)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(mInitialized);
|
||||
MOZ_ASSERT(!mSentFinish);
|
||||
|
||||
MaybeScheduleStableStateCallback();
|
||||
|
||||
nsresult rv = EnsureAllKeys();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
aResult.SetIsVoid(true);
|
||||
for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
if (aIndex == 0) {
|
||||
aResult = iter.Key();
|
||||
return NS_OK;
|
||||
}
|
||||
aIndex--;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSSnapshot::GetItem(const nsAString& aKey,
|
||||
nsAString& aResult)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(mInitialized);
|
||||
MOZ_ASSERT(!mSentFinish);
|
||||
|
||||
MaybeScheduleStableStateCallback();
|
||||
|
||||
nsString result;
|
||||
nsresult rv = GetItemInternal(aKey, Optional<nsString>(), result);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
aResult = result;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSSnapshot::GetKeys(nsTArray<nsString>& aKeys)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(mInitialized);
|
||||
MOZ_ASSERT(!mSentFinish);
|
||||
|
||||
MaybeScheduleStableStateCallback();
|
||||
|
||||
nsresult rv = EnsureAllKeys();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
aKeys.AppendElement(iter.Key());
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSSnapshot::SetItem(const nsAString& aKey,
|
||||
const nsAString& aValue,
|
||||
LSNotifyInfo& aNotifyInfo)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(mInitialized);
|
||||
MOZ_ASSERT(!mSentFinish);
|
||||
|
||||
MaybeScheduleStableStateCallback();
|
||||
|
||||
nsString oldValue;
|
||||
nsresult rv =
|
||||
GetItemInternal(aKey, Optional<nsString>(nsString(aValue)), oldValue);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool changed;
|
||||
if (oldValue == aValue && oldValue.IsVoid() == aValue.IsVoid()) {
|
||||
changed = false;
|
||||
} else {
|
||||
changed = true;
|
||||
|
||||
int64_t delta = static_cast<int64_t>(aValue.Length()) -
|
||||
static_cast<int64_t>(oldValue.Length());
|
||||
|
||||
if (oldValue.IsVoid()) {
|
||||
delta += static_cast<int64_t>(aKey.Length());
|
||||
}
|
||||
|
||||
rv = UpdateUsage(delta);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
if (oldValue.IsVoid()) {
|
||||
mValues.Remove(aKey);
|
||||
} else {
|
||||
mValues.Put(aKey, oldValue);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (oldValue.IsVoid() && mLoadState == LoadState::Partial) {
|
||||
mLength++;
|
||||
}
|
||||
|
||||
LSSetItemInfo setItemInfo;
|
||||
setItemInfo.key() = aKey;
|
||||
setItemInfo.oldValue() = oldValue;
|
||||
setItemInfo.value() = aValue;
|
||||
|
||||
mWriteInfos.AppendElement(std::move(setItemInfo));
|
||||
}
|
||||
|
||||
aNotifyInfo.changed() = changed;
|
||||
aNotifyInfo.oldValue() = oldValue;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSSnapshot::RemoveItem(const nsAString& aKey,
|
||||
LSNotifyInfo& aNotifyInfo)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(mInitialized);
|
||||
MOZ_ASSERT(!mSentFinish);
|
||||
|
||||
MaybeScheduleStableStateCallback();
|
||||
|
||||
nsString oldValue;
|
||||
nsresult rv =
|
||||
GetItemInternal(aKey, Optional<nsString>(VoidString()), oldValue);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool changed;
|
||||
if (oldValue.IsVoid()) {
|
||||
changed = false;
|
||||
} else {
|
||||
changed = true;
|
||||
|
||||
int64_t delta = -(static_cast<int64_t>(aKey.Length()) +
|
||||
static_cast<int64_t>(oldValue.Length()));
|
||||
|
||||
DebugOnly<nsresult> rv = UpdateUsage(delta);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
|
||||
if (mLoadState == LoadState::Partial) {
|
||||
mLength--;
|
||||
}
|
||||
|
||||
LSRemoveItemInfo removeItemInfo;
|
||||
removeItemInfo.key() = aKey;
|
||||
removeItemInfo.oldValue() = oldValue;
|
||||
|
||||
mWriteInfos.AppendElement(std::move(removeItemInfo));
|
||||
}
|
||||
|
||||
aNotifyInfo.changed() = changed;
|
||||
aNotifyInfo.oldValue() = oldValue;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSSnapshot::Clear(LSNotifyInfo& aNotifyInfo)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(mInitialized);
|
||||
MOZ_ASSERT(!mSentFinish);
|
||||
|
||||
MaybeScheduleStableStateCallback();
|
||||
|
||||
uint32_t length;
|
||||
if (mLoadState == LoadState::Partial) {
|
||||
length = mLength;
|
||||
MOZ_ASSERT(length);
|
||||
|
||||
MOZ_ALWAYS_TRUE(mActor->SendLoaded());
|
||||
|
||||
mLoadedItems.Clear();
|
||||
mUnknownItems.Clear();
|
||||
mLength = 0;
|
||||
mLoadState = LoadState::AllOrderedItems;
|
||||
} else {
|
||||
length = mValues.Count();
|
||||
}
|
||||
|
||||
bool changed;
|
||||
if (!length) {
|
||||
changed = false;
|
||||
} else {
|
||||
changed = true;
|
||||
|
||||
DebugOnly<nsresult> rv = UpdateUsage(-mExactUsage);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
|
||||
mValues.Clear();
|
||||
|
||||
LSClearInfo clearInfo;
|
||||
|
||||
mWriteInfos.AppendElement(std::move(clearInfo));
|
||||
}
|
||||
|
||||
aNotifyInfo.changed() = changed;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
LSSnapshot::MarkDirty()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(mInitialized);
|
||||
MOZ_ASSERT(!mSentFinish);
|
||||
|
||||
if (mDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
mDirty = true;
|
||||
|
||||
if (!mExplicit && !mHasPendingStableStateCallback) {
|
||||
CancelTimer();
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(Checkpoint());
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(Finish());
|
||||
} else {
|
||||
MOZ_ASSERT(!mHasPendingTimerCallback);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSSnapshot::End()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(mExplicit);
|
||||
MOZ_ASSERT(!mHasPendingStableStateCallback);
|
||||
MOZ_ASSERT(!mHasPendingTimerCallback);
|
||||
MOZ_ASSERT(mInitialized);
|
||||
MOZ_ASSERT(!mSentFinish);
|
||||
|
||||
nsresult rv = Checkpoint();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
RefPtr<LSSnapshot> kungFuDeathGrip = this;
|
||||
|
||||
rv = Finish();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(!mActor->SendPing())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
LSSnapshot::ScheduleStableStateCallback()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mTimer);
|
||||
MOZ_ASSERT(!mExplicit);
|
||||
MOZ_ASSERT(!mHasPendingStableStateCallback);
|
||||
|
||||
CancelTimer();
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable = this;
|
||||
nsContentUtils::RunInStableState(runnable.forget());
|
||||
|
||||
mHasPendingStableStateCallback = true;
|
||||
}
|
||||
|
||||
void
|
||||
LSSnapshot::MaybeScheduleStableStateCallback()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
|
||||
if (!mExplicit && !mHasPendingStableStateCallback) {
|
||||
ScheduleStableStateCallback();
|
||||
} else {
|
||||
MOZ_ASSERT(!mHasPendingTimerCallback);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSSnapshot::GetItemInternal(const nsAString& aKey,
|
||||
const Optional<nsString>& aValue,
|
||||
nsAString& aResult)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(mInitialized);
|
||||
MOZ_ASSERT(!mSentFinish);
|
||||
|
||||
nsString result;
|
||||
|
||||
switch (mLoadState) {
|
||||
case LoadState::Partial: {
|
||||
if (mValues.Get(aKey, &result)) {
|
||||
MOZ_ASSERT(!result.IsVoid());
|
||||
} else if (mLoadedItems.GetEntry(aKey) || mUnknownItems.GetEntry(aKey)) {
|
||||
result.SetIsVoid(true);
|
||||
} else {
|
||||
if (NS_WARN_IF(!mActor->SendLoadItem(nsString(aKey), &result))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (result.IsVoid()) {
|
||||
mUnknownItems.PutEntry(aKey);
|
||||
} else {
|
||||
mLoadedItems.PutEntry(aKey);
|
||||
mValues.Put(aKey, result);
|
||||
|
||||
if (mLoadedItems.Count() == mInitLength) {
|
||||
mLoadedItems.Clear();
|
||||
mUnknownItems.Clear();
|
||||
mLength = 0;
|
||||
mLoadState = LoadState::AllUnorderedItems;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (aValue.WasPassed()) {
|
||||
const nsString& value = aValue.Value();
|
||||
if (!value.IsVoid()) {
|
||||
mValues.Put(aKey, value);
|
||||
} else if (!result.IsVoid()) {
|
||||
mValues.Remove(aKey);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case LoadState::AllOrderedKeys: {
|
||||
if (mValues.Get(aKey, &result)) {
|
||||
if (result.IsVoid()) {
|
||||
if (NS_WARN_IF(!mActor->SendLoadItem(nsString(aKey), &result))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!result.IsVoid());
|
||||
|
||||
mLoadedItems.PutEntry(aKey);
|
||||
mValues.Put(aKey, result);
|
||||
|
||||
if (mLoadedItems.Count() == mInitLength) {
|
||||
mLoadedItems.Clear();
|
||||
MOZ_ASSERT(mLength == 0);
|
||||
mLoadState = LoadState::AllOrderedItems;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.SetIsVoid(true);
|
||||
}
|
||||
|
||||
if (aValue.WasPassed()) {
|
||||
const nsString& value = aValue.Value();
|
||||
if (!value.IsVoid()) {
|
||||
mValues.Put(aKey, value);
|
||||
} else if (!result.IsVoid()) {
|
||||
mValues.Remove(aKey);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case LoadState::AllUnorderedItems:
|
||||
case LoadState::AllOrderedItems: {
|
||||
if (aValue.WasPassed()) {
|
||||
const nsString& value = aValue.Value();
|
||||
if (!value.IsVoid()) {
|
||||
auto entry = mValues.LookupForAdd(aKey);
|
||||
if (entry) {
|
||||
result = entry.Data();
|
||||
entry.Data() = value;
|
||||
} else {
|
||||
result.SetIsVoid(true);
|
||||
entry.OrInsert([value]() { return value; });
|
||||
}
|
||||
} else {
|
||||
if (auto entry = mValues.Lookup(aKey)) {
|
||||
result = entry.Data();
|
||||
MOZ_ASSERT(!result.IsVoid());
|
||||
entry.Remove();
|
||||
} else {
|
||||
result.SetIsVoid(true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mValues.Get(aKey, &result)) {
|
||||
MOZ_ASSERT(!result.IsVoid());
|
||||
} else {
|
||||
result.SetIsVoid(true);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
MOZ_CRASH("Bad state!");
|
||||
}
|
||||
|
||||
aResult = result;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSSnapshot::EnsureAllKeys()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(mInitialized);
|
||||
MOZ_ASSERT(!mSentFinish);
|
||||
MOZ_ASSERT(mLoadState != LoadState::Initial);
|
||||
|
||||
if (mLoadState == LoadState::AllOrderedKeys ||
|
||||
mLoadState == LoadState::AllOrderedItems) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsTArray<nsString> keys;
|
||||
if (NS_WARN_IF(!mActor->SendLoadKeys(&keys))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsDataHashtable<nsStringHashKey, nsString> newValues;
|
||||
|
||||
for (auto key : keys) {
|
||||
newValues.Put(key, VoidString());
|
||||
}
|
||||
|
||||
for (uint32_t index = 0; index < mWriteInfos.Length(); index++) {
|
||||
const LSWriteInfo& writeInfo = mWriteInfos[index];
|
||||
|
||||
switch (writeInfo.type()) {
|
||||
case LSWriteInfo::TLSSetItemInfo: {
|
||||
newValues.Put(writeInfo.get_LSSetItemInfo().key(), VoidString());
|
||||
break;
|
||||
}
|
||||
case LSWriteInfo::TLSRemoveItemInfo: {
|
||||
newValues.Remove(writeInfo.get_LSRemoveItemInfo().key());
|
||||
break;
|
||||
}
|
||||
case LSWriteInfo::TLSClearInfo: {
|
||||
newValues.Clear();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
MOZ_CRASH("Should never get here!");
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT_IF(mLoadState == LoadState::AllUnorderedItems,
|
||||
newValues.Count() == mValues.Count());
|
||||
|
||||
for (auto iter = newValues.Iter(); !iter.Done(); iter.Next()) {
|
||||
nsString value;
|
||||
if (mValues.Get(iter.Key(), &value)) {
|
||||
iter.Data() = value;
|
||||
}
|
||||
}
|
||||
|
||||
mValues.SwapElements(newValues);
|
||||
|
||||
if (mLoadState == LoadState::Partial) {
|
||||
mUnknownItems.Clear();
|
||||
mLength = 0;
|
||||
mLoadState = LoadState::AllOrderedKeys;
|
||||
} else {
|
||||
MOZ_ASSERT(mLoadState == LoadState::AllUnorderedItems);
|
||||
|
||||
MOZ_ASSERT(mUnknownItems.Count() == 0);
|
||||
MOZ_ASSERT(mLength == 0);
|
||||
mLoadState = LoadState::AllOrderedItems;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSSnapshot::UpdateUsage(int64_t aDelta)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mDatabase);
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(mPeakUsage >= mExactUsage);
|
||||
MOZ_ASSERT(mInitialized);
|
||||
MOZ_ASSERT(!mSentFinish);
|
||||
|
||||
int64_t newExactUsage = mExactUsage + aDelta;
|
||||
if (newExactUsage > mPeakUsage) {
|
||||
int64_t minSize = newExactUsage - mPeakUsage;
|
||||
int64_t requestedSize = minSize + 4096;
|
||||
int64_t size;
|
||||
if (NS_WARN_IF(!mActor->SendIncreasePeakUsage(requestedSize,
|
||||
minSize,
|
||||
&size))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(size >= 0);
|
||||
|
||||
if (size == 0) {
|
||||
return NS_ERROR_FILE_NO_DEVICE_SPACE;
|
||||
}
|
||||
|
||||
mPeakUsage += size;
|
||||
}
|
||||
|
||||
mExactUsage = newExactUsage;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSSnapshot::Checkpoint()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(mInitialized);
|
||||
MOZ_ASSERT(!mSentFinish);
|
||||
|
||||
if (!mWriteInfos.IsEmpty()) {
|
||||
MOZ_ALWAYS_TRUE(mActor->SendCheckpoint(mWriteInfos));
|
||||
|
||||
mWriteInfos.Clear();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LSSnapshot::Finish()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mDatabase);
|
||||
MOZ_ASSERT(mActor);
|
||||
MOZ_ASSERT(mInitialized);
|
||||
MOZ_ASSERT(!mSentFinish);
|
||||
|
||||
MOZ_ALWAYS_TRUE(mActor->SendFinish());
|
||||
|
||||
mDatabase->NoteFinishedSnapshot(this);
|
||||
|
||||
#ifdef DEBUG
|
||||
mSentFinish = true;
|
||||
#endif
|
||||
|
||||
// Clear the self reference added in Init method.
|
||||
MOZ_ASSERT(mSelfRef);
|
||||
mSelfRef = nullptr;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
LSSnapshot::CancelTimer()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mTimer);
|
||||
|
||||
if (mHasPendingTimerCallback) {
|
||||
MOZ_ALWAYS_SUCCEEDS(mTimer->Cancel());
|
||||
mHasPendingTimerCallback = false;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void
|
||||
LSSnapshot::TimerCallback(nsITimer* aTimer, void* aClosure)
|
||||
{
|
||||
MOZ_ASSERT(aTimer);
|
||||
|
||||
auto* self = static_cast<LSSnapshot*>(aClosure);
|
||||
MOZ_ASSERT(self);
|
||||
MOZ_ASSERT(self->mTimer);
|
||||
MOZ_ASSERT(SameCOMIdentity(self->mTimer, aTimer));
|
||||
MOZ_ASSERT(!self->mHasPendingStableStateCallback);
|
||||
MOZ_ASSERT(self->mHasPendingTimerCallback);
|
||||
|
||||
self->mHasPendingTimerCallback = false;
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(self->Finish());
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(LSSnapshot, nsIRunnable)
|
||||
|
||||
NS_IMETHODIMP
|
||||
LSSnapshot::Run()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(!mExplicit);
|
||||
MOZ_ASSERT(mHasPendingStableStateCallback);
|
||||
MOZ_ASSERT(!mHasPendingTimerCallback);
|
||||
|
||||
mHasPendingStableStateCallback = false;
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(Checkpoint());
|
||||
|
||||
if (mDirty || !Preferences::GetBool("dom.storage.snapshot_reusing")) {
|
||||
MOZ_ALWAYS_SUCCEEDS(Finish());
|
||||
} else if (!mExplicit) {
|
||||
MOZ_ASSERT(mTimer);
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(
|
||||
mTimer->InitWithNamedFuncCallback(TimerCallback,
|
||||
this,
|
||||
kSnapshotTimeoutMs,
|
||||
nsITimer::TYPE_ONE_SHOT,
|
||||
"LSSnapshot::TimerCallback"));
|
||||
|
||||
mHasPendingTimerCallback = true;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
204
dom/localstorage/LSSnapshot.h
Normal file
204
dom/localstorage/LSSnapshot.h
Normal file
@ -0,0 +1,204 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_localstorage_LSSnapshot_h
|
||||
#define mozilla_dom_localstorage_LSSnapshot_h
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class LSDatabase;
|
||||
class LSNotifyInfo;
|
||||
class LSSnapshotChild;
|
||||
class LSSnapshotInitInfo;
|
||||
class LSWriteInfo;
|
||||
|
||||
class LSSnapshot final
|
||||
: public nsIRunnable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* The LoadState expresses what subset of information a snapshot has from the
|
||||
* authoritative Datastore in the parent process. The initial snapshot is
|
||||
* populated heuristically based on the size of the keys and size of the items
|
||||
* (inclusive of the key value; item is key+value, not just value) of the
|
||||
* entire datastore relative to the configured prefill limit (via pref
|
||||
* "dom.storage.snapshot_prefill" exposed as gSnapshotPrefill in bytes).
|
||||
*
|
||||
* If there's less data than the limit, we send both keys and values and end
|
||||
* up as AllOrderedItems. If there's enough room for all the keys but not
|
||||
* all the values, we end up as AllOrderedKeys with as many values present as
|
||||
* would fit. If there's not enough room for all the keys, then we end up as
|
||||
* Partial with as many key-value pairs as will fit.
|
||||
*
|
||||
* The state AllUnorderedItems can only be reached by code getting items one
|
||||
* by one.
|
||||
*/
|
||||
enum class LoadState
|
||||
{
|
||||
/**
|
||||
* Class constructed, Init(LSSnapshotInitInfo) has not been invoked yet.
|
||||
*/
|
||||
Initial,
|
||||
/**
|
||||
* Some keys and their values are known.
|
||||
*/
|
||||
Partial,
|
||||
/**
|
||||
* All the keys are known in order, but some values are unknown.
|
||||
*/
|
||||
AllOrderedKeys,
|
||||
/**
|
||||
* All keys and their values are known, but in an arbitrary order.
|
||||
*/
|
||||
AllUnorderedItems,
|
||||
/**
|
||||
* All keys and their values are known and are present in their canonical
|
||||
* order. This is everything, and is the preferred case. The initial
|
||||
* population will send this info when the size of all items is less than
|
||||
* the prefill threshold.
|
||||
*
|
||||
* mValues will contain all keys and values, mLoadedItems and mUnknownItems
|
||||
* are unused.
|
||||
*/
|
||||
AllOrderedItems,
|
||||
EndGuard
|
||||
};
|
||||
|
||||
private:
|
||||
RefPtr<LSSnapshot> mSelfRef;
|
||||
|
||||
RefPtr<LSDatabase> mDatabase;
|
||||
|
||||
nsCOMPtr<nsITimer> mTimer;
|
||||
|
||||
LSSnapshotChild* mActor;
|
||||
|
||||
nsTHashtable<nsStringHashKey> mLoadedItems;
|
||||
nsTHashtable<nsStringHashKey> mUnknownItems;
|
||||
nsDataHashtable<nsStringHashKey, nsString> mValues;
|
||||
nsTArray<LSWriteInfo> mWriteInfos;
|
||||
|
||||
uint32_t mInitLength;
|
||||
uint32_t mLength;
|
||||
int64_t mExactUsage;
|
||||
int64_t mPeakUsage;
|
||||
|
||||
LoadState mLoadState;
|
||||
|
||||
bool mExplicit;
|
||||
bool mHasPendingStableStateCallback;
|
||||
bool mHasPendingTimerCallback;
|
||||
bool mDirty;
|
||||
|
||||
#ifdef DEBUG
|
||||
bool mInitialized;
|
||||
bool mSentFinish;
|
||||
#endif
|
||||
|
||||
public:
|
||||
explicit LSSnapshot(LSDatabase* aDatabase);
|
||||
|
||||
void
|
||||
AssertIsOnOwningThread() const
|
||||
{
|
||||
NS_ASSERT_OWNINGTHREAD(LSSnapshot);
|
||||
}
|
||||
|
||||
void
|
||||
SetActor(LSSnapshotChild* aActor);
|
||||
|
||||
void
|
||||
ClearActor()
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(mActor);
|
||||
|
||||
mActor = nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
Explicit() const
|
||||
{
|
||||
return mExplicit;
|
||||
}
|
||||
|
||||
nsresult
|
||||
Init(const LSSnapshotInitInfo& aInitInfo,
|
||||
bool aExplicit);
|
||||
|
||||
nsresult
|
||||
GetLength(uint32_t* aResult);
|
||||
|
||||
nsresult
|
||||
GetKey(uint32_t aIndex,
|
||||
nsAString& aResult);
|
||||
|
||||
nsresult
|
||||
GetItem(const nsAString& aKey,
|
||||
nsAString& aResult);
|
||||
|
||||
nsresult
|
||||
GetKeys(nsTArray<nsString>& aKeys);
|
||||
|
||||
nsresult
|
||||
SetItem(const nsAString& aKey,
|
||||
const nsAString& aValue,
|
||||
LSNotifyInfo& aNotifyInfo);
|
||||
|
||||
nsresult
|
||||
RemoveItem(const nsAString& aKey,
|
||||
LSNotifyInfo& aNotifyInfo);
|
||||
|
||||
nsresult
|
||||
Clear(LSNotifyInfo& aNotifyInfo);
|
||||
|
||||
void
|
||||
MarkDirty();
|
||||
|
||||
nsresult
|
||||
End();
|
||||
|
||||
private:
|
||||
~LSSnapshot();
|
||||
|
||||
void
|
||||
ScheduleStableStateCallback();
|
||||
|
||||
void
|
||||
MaybeScheduleStableStateCallback();
|
||||
|
||||
nsresult
|
||||
GetItemInternal(const nsAString& aKey,
|
||||
const Optional<nsString>& aValue,
|
||||
nsAString& aResult);
|
||||
|
||||
nsresult
|
||||
EnsureAllKeys();
|
||||
|
||||
nsresult
|
||||
UpdateUsage(int64_t aDelta);
|
||||
|
||||
nsresult
|
||||
Checkpoint();
|
||||
|
||||
nsresult
|
||||
Finish();
|
||||
|
||||
void
|
||||
CancelTimer();
|
||||
|
||||
static void
|
||||
TimerCallback(nsITimer* aTimer, void* aClosure);
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIRUNNABLE
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_localstorage_LSSnapshot_h
|
42
dom/localstorage/LocalStorageCommon.cpp
Normal file
42
dom/localstorage/LocalStorageCommon.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "LocalStorageCommon.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
namespace {
|
||||
|
||||
Atomic<int32_t> gNextGenLocalStorageEnabled(-1);
|
||||
|
||||
} // namespace
|
||||
|
||||
const char16_t* kLocalStorageType = u"localStorage";
|
||||
|
||||
bool
|
||||
NextGenLocalStorageEnabled()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (gNextGenLocalStorageEnabled == -1) {
|
||||
bool enabled = Preferences::GetBool("dom.storage.next_gen", false);
|
||||
gNextGenLocalStorageEnabled = enabled ? 1 : 0;
|
||||
}
|
||||
|
||||
return !!gNextGenLocalStorageEnabled;
|
||||
}
|
||||
|
||||
bool
|
||||
CachedNextGenLocalStorageEnabled()
|
||||
{
|
||||
MOZ_ASSERT(gNextGenLocalStorageEnabled != -1);
|
||||
|
||||
return !!gNextGenLocalStorageEnabled;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
247
dom/localstorage/LocalStorageCommon.h
Normal file
247
dom/localstorage/LocalStorageCommon.h
Normal file
@ -0,0 +1,247 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_localstorage_LocalStorageCommon_h
|
||||
#define mozilla_dom_localstorage_LocalStorageCommon_h
|
||||
|
||||
/*
|
||||
* Local storage
|
||||
* ~~~~~~~~~~~~~
|
||||
*
|
||||
* Implementation overview
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* The implementation is based on a per principal/origin cache (datastore)
|
||||
* living in the main process and synchronous calls initiated from content
|
||||
* processes.
|
||||
* The IPC communication is managed by database actors which link to the
|
||||
* datastore.
|
||||
* The synchronous blocking of the main thread is done by using a special
|
||||
* technique or by using standard synchronous IPC calls.
|
||||
*
|
||||
* General architecture
|
||||
* ~~~~~~~~~~~~~~~~~~~~
|
||||
* The current browser architecture consists of one main process and multiple
|
||||
* content processes (there are other processes but for simplicity's sake, they
|
||||
* are not mentioned here). The processes use the IPC communication to talk to
|
||||
* each other. Local storage implementation uses the client-server model, so
|
||||
* the main process manages all the data and content processes then request
|
||||
* particular data from the main process. The main process is also called the
|
||||
* parent or the parent side, the content process is then called the child or
|
||||
* the child side.
|
||||
*
|
||||
* Datastores
|
||||
* ~~~~~~~~~~
|
||||
*
|
||||
* A datastore provides a convenient way to access data for given origin. The
|
||||
* data is always preloaded into memory and indexed using a hash table. This
|
||||
* enables very fast access to particular stored items. There can be only one
|
||||
* datastore per origin and exists solely on the parent side. It is represented
|
||||
* by the "Datastore" class. A datastore instance is a ref counted object and
|
||||
* lives on the PBackground thread, it is kept alive by database objects. When
|
||||
* the last database object for given origin is destroyed, the associated
|
||||
* datastore object is destroyed too.
|
||||
*
|
||||
* Databases
|
||||
* ~~~~~~~~~
|
||||
*
|
||||
* A database allows direct access to a datastore from a content process. There
|
||||
* can be multiple databases for the same origin, but they all share the same
|
||||
* datastore.
|
||||
* Databases use the PBackgroundLSDatabase IPDL protocol for IPC communication.
|
||||
* Given the nature of local storage, most of PBackgroundLSDatabase messages
|
||||
* are synchronous.
|
||||
*
|
||||
* On the parent side, the database is represented by the "Database" class that
|
||||
* is a parent actor as well (implements the "PBackgroundLSDatabaseParent"
|
||||
* interface). A database instance is a ref counted object and lives on the
|
||||
* PBackground thread.
|
||||
* All live database actors are tracked in an array.
|
||||
*
|
||||
* On the child side, the database is represented by the "LSDatabase" class
|
||||
* that provides indirect access to a child actor. An LSDatabase instance is a
|
||||
* ref counted object and lives on the main thread.
|
||||
* The actual child actor is represented by the "LSDatabaseChild" class that
|
||||
* implements the "PBackgroundLSDatabaseChild" interface. An "LSDatabaseChild"
|
||||
* instance is not ref counted and lives on the main thread too.
|
||||
*
|
||||
* Synchronous blocking
|
||||
* ~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* Local storage is synchronous in nature which means the execution can't move
|
||||
* forward until there's a reply for given method call.
|
||||
* Since we have to use IPC anyway, we could just always use synchronous IPC
|
||||
* messages for all local storage method calls. Well, there's a problem with
|
||||
* that approach.
|
||||
* If the main process needs to do some off PBackground thread stuff like
|
||||
* getting info from principals on the main thread or some asynchronous stuff
|
||||
* like directory locking before sending a reply to a synchronous message, then
|
||||
* we would have to block the thread or spin the event loop which is usually a
|
||||
* bad idea, especially in the main process.
|
||||
* Instead, we can use a special thread in the content process called DOM File
|
||||
* thread for communication with the main process using asynchronous messages
|
||||
* and synchronously block the main thread until the DOM File thread is done
|
||||
* (the main thread blocking is a bit more complicated, see the comment in
|
||||
* RequestHelper::StartAndReturnResponse for more details).
|
||||
* Anyway, the extra hop to the DOM File thread brings another overhead and
|
||||
* latency. The final solution is to use a combination of the special thread
|
||||
* for complex stuff like datastore preparation and synchronous IPC messages
|
||||
* sent directly from the main thread for database access when data is already
|
||||
* loaded from disk into memory.
|
||||
*
|
||||
* Requests
|
||||
* ~~~~~~~~
|
||||
*
|
||||
* Requests are used to handle asynchronous high level datastore operations
|
||||
* which are initiated in a content process and then processed in the parent
|
||||
* process (for example, preparation of a datastore).
|
||||
* Requests use the "PBackgroundLSRequest" IPDL protocol for IPC communication.
|
||||
*
|
||||
* On the parent side, the request is represented by the "LSRequestBase" class
|
||||
* that is a parent actor as well (implements the "PBackgroundLSRequestParent"
|
||||
* interface). It's an abstract class (contains pure virtual functions) so it
|
||||
* can't be used to create instances.
|
||||
* It also inherits from the "DatastoreOperationBase" class which is a generic
|
||||
* base class for all datastore operations. The "DatastoreOperationsBase" class
|
||||
* inherits from the "Runnable" class, so derived class instances are ref
|
||||
* counted, can be dispatched to multiple threads and thus they are used on
|
||||
* multiple threads. However, derived class instances can be created on the
|
||||
* PBackground thread only.
|
||||
*
|
||||
* On the child side, the request is represented by the "RequestHelper" class
|
||||
* that covers all the complexity needed to start a new request, handle
|
||||
* responses and do safe main thread blocking at the same time.
|
||||
* It inherits from the "Runnable" class, so instances are ref counted and
|
||||
* they are internally used on multiple threads (specifically on the main
|
||||
* thread and on the DOM File thread). Anyway, users should create and use
|
||||
* instances of this class only on the main thread (apart from a special case
|
||||
* when we need to cancel the request from an internal chromium IPC thread to
|
||||
* prevent a dead lock involving CPOWs).
|
||||
* The actual child actor is represented by the "LSRequestChild" class that
|
||||
* implements the "PBackgroundLSRequestChild" interface. An "LSRequestChild"
|
||||
* instance is not ref counted and lives on the DOM File thread.
|
||||
* Request responses are passed using the "LSRequestChildCallback" interface.
|
||||
*
|
||||
* Preparation of a datastore
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* The datastore preparation is needed to make sure a datastore is fully loaded
|
||||
* into memory. Every datastore preparation produces a unique id (even if the
|
||||
* datastore for given origin already exists).
|
||||
* On the parent side, the preparation is handled by the "PrepareDatastoreOp"
|
||||
* class which inherits from the "LSRequestBase" class. The preparation process
|
||||
* on the parent side is quite complicated, it happens sequentially on multiple
|
||||
* threads and is managed by a state machine.
|
||||
* On the child side, the preparation is done in the LSObject::EnsureDatabase
|
||||
* method using the "RequestHelper" class. The method starts a new preparation
|
||||
* request and obtains a unique id produced by the parent (or an error code if
|
||||
* the requested failed to complete).
|
||||
*
|
||||
* Linking databases to a datastore
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* A datastore exists only on the parent side, but it can be accessed from the
|
||||
* content via database actors. Database actors are initiated on the child side
|
||||
* and they need to be linked to a datastore on the parent side via an id. The
|
||||
* datastore preparation process gives us the required id.
|
||||
* The linking is initiated on the child side in the LSObject::EnsureDatabase
|
||||
* method by calling SendPBackgroundLSDatabaseConstructor and finished in
|
||||
* RecvPBackgroundLSDatabaseConstructor on the parent side.
|
||||
*
|
||||
* Actor migration
|
||||
* ~~~~~~~~~~~~~~~
|
||||
*
|
||||
* In theory, the datastore preparation request could return a database actor
|
||||
* directly (instead of returning an id intended for database linking to a
|
||||
* datastore). However, as it was explained above, the preparation must be done
|
||||
* on the DOM File thread and database objects are used on the main thread. The
|
||||
* returned actor would have to be migrated from the DOM File thread to the
|
||||
* main thread and that's something which our IPDL doesn't support yet.
|
||||
*
|
||||
* Exposing local storage
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* The implementation is exposed to the DOM via window.localStorage attribute.
|
||||
* Local storage's sibling, session storage shares the same WebIDL interface
|
||||
* for exposing it to web content, therefore there's an abstract class called
|
||||
* "Storage" that handles some of the common DOM bindings stuff. Local storage
|
||||
* specific functionality is defined in the "LSObject" derived class.
|
||||
* The "LSObject" class is also a starting point for the datastore preparation
|
||||
* and database linking.
|
||||
*
|
||||
* Local storage manager
|
||||
* ~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* The local storage manager exposes some of the features that need to be
|
||||
* available only in the chrome code or tests. The manager is represented by
|
||||
* the "LocalStorageManager2" class that implements the "nsIDOMStorageManager"
|
||||
* interface.
|
||||
*/
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
extern const char16_t* kLocalStorageType;
|
||||
|
||||
/**
|
||||
* Convenience data-structure to make it easier to track whether a value has
|
||||
* changed and what its previous value was for notification purposes. Instances
|
||||
* are created on the stack by LSObject and passed to LSDatabase which in turn
|
||||
* passes them onto LSSnapshot for final updating/population. LSObject then
|
||||
* generates an event, if appropriate.
|
||||
*/
|
||||
class MOZ_STACK_CLASS LSNotifyInfo
|
||||
{
|
||||
bool mChanged;
|
||||
nsString mOldValue;
|
||||
|
||||
public:
|
||||
LSNotifyInfo()
|
||||
: mChanged(false)
|
||||
{ }
|
||||
|
||||
bool
|
||||
changed() const
|
||||
{
|
||||
return mChanged;
|
||||
}
|
||||
|
||||
bool&
|
||||
changed()
|
||||
{
|
||||
return mChanged;
|
||||
}
|
||||
|
||||
const nsString&
|
||||
oldValue() const
|
||||
{
|
||||
return mOldValue;
|
||||
}
|
||||
|
||||
nsString&
|
||||
oldValue()
|
||||
{
|
||||
return mOldValue;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Main-thread-only check of LSNG being enabled, the value is latched once
|
||||
* initialized so changing the preference during runtime has no effect.
|
||||
*/
|
||||
bool
|
||||
NextGenLocalStorageEnabled();
|
||||
|
||||
/**
|
||||
* Cached any-thread version of NextGenLocalStorageEnabled().
|
||||
*/
|
||||
bool
|
||||
CachedNextGenLocalStorageEnabled();
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_localstorage_LocalStorageCommon_h
|
430
dom/localstorage/LocalStorageManager2.cpp
Normal file
430
dom/localstorage/LocalStorageManager2.cpp
Normal file
@ -0,0 +1,430 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "LocalStorageManager2.h"
|
||||
|
||||
#include "LSObject.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
namespace {
|
||||
|
||||
class RequestResolver final
|
||||
: public LSRequestChildCallback
|
||||
{
|
||||
RefPtr<Promise> mPromise;
|
||||
|
||||
public:
|
||||
explicit RequestResolver(Promise* aPromise)
|
||||
: mPromise(aPromise)
|
||||
{ }
|
||||
|
||||
NS_INLINE_DECL_REFCOUNTING(mozilla::dom::RequestResolver, override);
|
||||
|
||||
private:
|
||||
~RequestResolver() = default;
|
||||
|
||||
void
|
||||
HandleResponse(nsresult aResponse);
|
||||
|
||||
void
|
||||
HandleResponse(const NullableDatastoreId& aDatastoreId);
|
||||
|
||||
// LSRequestChildCallback
|
||||
void
|
||||
OnResponse(const LSRequestResponse& aResponse) override;
|
||||
};
|
||||
|
||||
class SimpleRequestResolver final
|
||||
: public LSSimpleRequestChildCallback
|
||||
{
|
||||
RefPtr<Promise> mPromise;
|
||||
|
||||
public:
|
||||
explicit SimpleRequestResolver(Promise* aPromise)
|
||||
: mPromise(aPromise)
|
||||
{ }
|
||||
|
||||
NS_INLINE_DECL_REFCOUNTING(SimpleRequestResolver, override);
|
||||
|
||||
private:
|
||||
~SimpleRequestResolver() = default;
|
||||
|
||||
void
|
||||
HandleResponse(nsresult aResponse);
|
||||
|
||||
void
|
||||
HandleResponse(bool aResponse);
|
||||
|
||||
// LSRequestChildCallback
|
||||
void
|
||||
OnResponse(const LSSimpleRequestResponse& aResponse) override;
|
||||
};
|
||||
|
||||
nsresult
|
||||
CreatePromise(JSContext* aContext, Promise** aPromise)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aContext);
|
||||
|
||||
nsIGlobalObject* global =
|
||||
xpc::NativeGlobal(JS::CurrentGlobalOrNull(aContext));
|
||||
if (NS_WARN_IF(!global)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
ErrorResult result;
|
||||
RefPtr<Promise> promise = Promise::Create(global, result);
|
||||
if (result.Failed()) {
|
||||
return result.StealNSResult();
|
||||
}
|
||||
|
||||
promise.forget(aPromise);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
CheckedPrincipalToPrincipalInfo(nsIPrincipal* aPrincipal,
|
||||
PrincipalInfo& aPrincipalInfo)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aPrincipal);
|
||||
|
||||
nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &aPrincipalInfo);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (aPrincipalInfo.type() != PrincipalInfo::TContentPrincipalInfo &&
|
||||
aPrincipalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LocalStorageManager2::LocalStorageManager2()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(NextGenLocalStorageEnabled());
|
||||
}
|
||||
|
||||
LocalStorageManager2::~LocalStorageManager2()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(LocalStorageManager2,
|
||||
nsIDOMStorageManager,
|
||||
nsILocalStorageManager)
|
||||
|
||||
NS_IMETHODIMP
|
||||
LocalStorageManager2::PrecacheStorage(nsIPrincipal* aPrincipal,
|
||||
Storage** _retval)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aPrincipal);
|
||||
MOZ_ASSERT(_retval);
|
||||
|
||||
// This method was created as part of the e10s-ification of the old LS
|
||||
// implementation to perform a preload in the content/current process. That's
|
||||
// not how things work in LSNG. Instead everything happens in the parent
|
||||
// process, triggered by the official preloading spot,
|
||||
// ContentParent::AboutToLoadHttpFtpWyciwygDocumentForChild.
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LocalStorageManager2::CreateStorage(mozIDOMWindow* aWindow,
|
||||
nsIPrincipal* aPrincipal,
|
||||
const nsAString& aDocumentURI,
|
||||
bool aPrivate,
|
||||
Storage** _retval)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aPrincipal);
|
||||
MOZ_ASSERT(_retval);
|
||||
|
||||
nsCOMPtr<nsPIDOMWindowInner> inner = nsPIDOMWindowInner::From(aWindow);
|
||||
|
||||
RefPtr<LSObject> object;
|
||||
nsresult rv = LSObject::CreateForPrincipal(inner,
|
||||
aPrincipal,
|
||||
aDocumentURI,
|
||||
aPrivate,
|
||||
getter_AddRefs(object));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
object.forget(_retval);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LocalStorageManager2::GetStorage(mozIDOMWindow* aWindow,
|
||||
nsIPrincipal* aPrincipal,
|
||||
bool aPrivate,
|
||||
Storage** _retval)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aPrincipal);
|
||||
MOZ_ASSERT(_retval);
|
||||
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LocalStorageManager2::CloneStorage(Storage* aStorageToCloneFrom)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aStorageToCloneFrom);
|
||||
|
||||
// Cloning is specific to sessionStorage; state is forked when a new tab is
|
||||
// opened from an existing tab.
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LocalStorageManager2::CheckStorage(nsIPrincipal* aPrincipal,
|
||||
Storage *aStorage,
|
||||
bool* _retval)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aPrincipal);
|
||||
MOZ_ASSERT(aStorage);
|
||||
MOZ_ASSERT(_retval);
|
||||
|
||||
// Only used by sessionStorage.
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LocalStorageManager2::GetNextGenLocalStorageEnabled(bool* aResult)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aResult);
|
||||
|
||||
*aResult = NextGenLocalStorageEnabled();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LocalStorageManager2::Preload(nsIPrincipal* aPrincipal,
|
||||
JSContext* aContext,
|
||||
nsISupports** _retval)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aPrincipal);
|
||||
MOZ_ASSERT(_retval);
|
||||
|
||||
nsresult rv;
|
||||
|
||||
RefPtr<Promise> promise;
|
||||
|
||||
if (aContext) {
|
||||
rv = CreatePromise(aContext, getter_AddRefs(promise));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
LSRequestPrepareDatastoreParams params;
|
||||
params.createIfNotExists() = false;
|
||||
|
||||
rv = CheckedPrincipalToPrincipalInfo(aPrincipal,
|
||||
params.principalInfo());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = StartRequest(promise, params);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
promise.forget(_retval);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LocalStorageManager2::IsPreloaded(nsIPrincipal* aPrincipal,
|
||||
JSContext* aContext,
|
||||
nsISupports** _retval)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aPrincipal);
|
||||
MOZ_ASSERT(_retval);
|
||||
|
||||
RefPtr<Promise> promise;
|
||||
nsresult rv = CreatePromise(aContext, getter_AddRefs(promise));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
LSSimpleRequestPreloadedParams params;
|
||||
|
||||
rv = CheckedPrincipalToPrincipalInfo(aPrincipal,
|
||||
params.principalInfo());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = StartSimpleRequest(promise, params);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
promise.forget(_retval);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LocalStorageManager2::StartRequest(Promise* aPromise,
|
||||
const LSRequestParams& aParams)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
PBackgroundChild* backgroundActor =
|
||||
BackgroundChild::GetOrCreateForCurrentThread();
|
||||
if (NS_WARN_IF(!backgroundActor)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
RefPtr<RequestResolver> resolver = new RequestResolver(aPromise);
|
||||
|
||||
auto actor = new LSRequestChild(resolver);
|
||||
|
||||
if (!backgroundActor->SendPBackgroundLSRequestConstructor(actor, aParams)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
LocalStorageManager2::StartSimpleRequest(Promise* aPromise,
|
||||
const LSSimpleRequestParams& aParams)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aPromise);
|
||||
|
||||
PBackgroundChild* backgroundActor =
|
||||
BackgroundChild::GetOrCreateForCurrentThread();
|
||||
if (NS_WARN_IF(!backgroundActor)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
RefPtr<SimpleRequestResolver> resolver = new SimpleRequestResolver(aPromise);
|
||||
|
||||
auto actor = new LSSimpleRequestChild(resolver);
|
||||
|
||||
if (!backgroundActor->SendPBackgroundLSSimpleRequestConstructor(actor,
|
||||
aParams)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
RequestResolver::HandleResponse(nsresult aResponse)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!mPromise) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPromise->MaybeReject(aResponse);
|
||||
}
|
||||
|
||||
void
|
||||
RequestResolver::HandleResponse(const NullableDatastoreId& aDatastoreId)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!mPromise) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (aDatastoreId.type()) {
|
||||
case NullableDatastoreId::Tnull_t:
|
||||
mPromise->MaybeResolve(JS::NullHandleValue);
|
||||
break;
|
||||
|
||||
case NullableDatastoreId::Tuint64_t:
|
||||
mPromise->MaybeResolve(aDatastoreId.get_uint64_t());
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_CRASH("Unknown datastore id type!");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RequestResolver::OnResponse(const LSRequestResponse& aResponse)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
switch (aResponse.type()) {
|
||||
case LSRequestResponse::Tnsresult:
|
||||
HandleResponse(aResponse.get_nsresult());
|
||||
break;
|
||||
|
||||
case LSRequestResponse::TLSRequestPrepareDatastoreResponse:
|
||||
HandleResponse(
|
||||
aResponse.get_LSRequestPrepareDatastoreResponse().datastoreId());
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH("Unknown response type!");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SimpleRequestResolver::HandleResponse(nsresult aResponse)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mPromise);
|
||||
|
||||
mPromise->MaybeReject(aResponse);
|
||||
}
|
||||
|
||||
void
|
||||
SimpleRequestResolver::HandleResponse(bool aResponse)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mPromise);
|
||||
|
||||
mPromise->MaybeResolve(aResponse);
|
||||
}
|
||||
|
||||
void
|
||||
SimpleRequestResolver::OnResponse(const LSSimpleRequestResponse& aResponse)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
switch (aResponse.type()) {
|
||||
case LSSimpleRequestResponse::Tnsresult:
|
||||
HandleResponse(aResponse.get_nsresult());
|
||||
break;
|
||||
|
||||
case LSSimpleRequestResponse::TLSSimpleRequestPreloadedResponse:
|
||||
HandleResponse(
|
||||
aResponse.get_LSSimpleRequestPreloadedResponse().preloaded());
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_CRASH("Unknown response type!");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
69
dom/localstorage/LocalStorageManager2.h
Normal file
69
dom/localstorage/LocalStorageManager2.h
Normal file
@ -0,0 +1,69 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_localstorage_LocalStorageManager2_h
|
||||
#define mozilla_dom_localstorage_LocalStorageManager2_h
|
||||
|
||||
#include "nsIDOMStorageManager.h"
|
||||
#include "nsILocalStorageManager.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class LSRequestParams;
|
||||
class LSSimpleRequestParams;
|
||||
class Promise;
|
||||
|
||||
/**
|
||||
* Under LSNG this exposes nsILocalStorageManager::Preload to ContentParent to
|
||||
* trigger preloading. Otherwise, this is basically just a place for test logic
|
||||
* that doesn't make sense to put directly on the Storage WebIDL interface.
|
||||
*
|
||||
* Previously, the nsIDOMStorageManager XPCOM interface was also used by
|
||||
* nsGlobalWindowInner to interact with LocalStorage, but in these de-XPCOM
|
||||
* days, we've moved to just directly reference the relevant concrete classes
|
||||
* (ex: LSObject) directly.
|
||||
*
|
||||
* Note that testing methods are now also directly exposed on the Storage WebIDL
|
||||
* interface for simplicity/sanity.
|
||||
*/
|
||||
class LocalStorageManager2 final
|
||||
: public nsIDOMStorageManager
|
||||
, public nsILocalStorageManager
|
||||
{
|
||||
public:
|
||||
LocalStorageManager2();
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIDOMSTORAGEMANAGER
|
||||
NS_DECL_NSILOCALSTORAGEMANAGER
|
||||
|
||||
private:
|
||||
~LocalStorageManager2();
|
||||
|
||||
/**
|
||||
* Helper to trigger an LSRequest and resolve/reject the provided promise when
|
||||
* the result comes in. This routine is notable because the LSRequest
|
||||
* mechanism is normally used synchronously from content, but here it's
|
||||
* exposed asynchronously.
|
||||
*/
|
||||
nsresult
|
||||
StartRequest(Promise* aPromise,
|
||||
const LSRequestParams& aParams);
|
||||
|
||||
/**
|
||||
* Helper to trigger an LSSimpleRequst and resolve/reject the provided promise
|
||||
* when the result comes in.
|
||||
*/
|
||||
nsresult
|
||||
StartSimpleRequest(Promise* aPromise,
|
||||
const LSSimpleRequestParams& aParams);
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_localstorage_LocalStorageManager2_h
|
148
dom/localstorage/PBackgroundLSDatabase.ipdl
Normal file
148
dom/localstorage/PBackgroundLSDatabase.ipdl
Normal file
@ -0,0 +1,148 @@
|
||||
/* 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/. */
|
||||
|
||||
include protocol PBackground;
|
||||
include protocol PBackgroundLSSnapshot;
|
||||
|
||||
include "mozilla/dom/localstorage/SerializationHelpers.h";
|
||||
|
||||
using mozilla::dom::LSSnapshot::LoadState
|
||||
from "mozilla/dom/LSSnapshot.h";
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
/**
|
||||
* LocalStorage key/value pair wire representations. `value` may be void in
|
||||
* cases where there is a value but it is not being sent for memory/bandwidth
|
||||
* conservation purposes. (It's not possible to have a null/undefined `value`
|
||||
* as Storage is defined explicitly as a String store.)
|
||||
*/
|
||||
struct LSItemInfo
|
||||
{
|
||||
nsString key;
|
||||
nsString value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initial LSSnapshot state as produced by Datastore::GetSnapshotInitInfo. See
|
||||
* `LSSnapshot::LoadState` for more details about the possible states and a
|
||||
* high level overview.
|
||||
*/
|
||||
struct LSSnapshotInitInfo
|
||||
{
|
||||
/**
|
||||
* As many key/value or key/void pairs as the snapshot prefill byte budget
|
||||
* allowed.
|
||||
*/
|
||||
LSItemInfo[] itemInfos;
|
||||
/**
|
||||
* The total number of key/value pairs in LocalStorage for this origin at the
|
||||
* time the snapshot was created. (And the point of the snapshot is to
|
||||
* conceptually freeze the state of the Datastore in time, so this value does
|
||||
* not change despite what other LSDatabase objects get up to in other
|
||||
* processes.)
|
||||
*/
|
||||
uint32_t totalLength;
|
||||
/**
|
||||
* The current amount of LocalStorage usage as measured by the summing the
|
||||
* nsString Length() of both the key and the value over all stored pairs.
|
||||
*/
|
||||
int64_t initialUsage;
|
||||
/**
|
||||
* The amount of storage allowed to be used by the Snapshot without requesting
|
||||
* more storage space via IncreasePeakUsage. This is the `initialUsage` plus
|
||||
* 0 or more bytes of space. If space was available, the increase will be the
|
||||
* `requestedSize` from the PBackgroundLSSnapshot constructor. If the
|
||||
* LocalStorage usage was already close to the limit, then the fallback is the
|
||||
* `minSize` requested, or 0 if there wasn't space for that.
|
||||
*/
|
||||
int64_t peakUsage;
|
||||
// See `LSSnapshot::LoadState` in `LSSnapshot.h`
|
||||
LoadState loadState;
|
||||
};
|
||||
|
||||
/**
|
||||
* This protocol is asynchronously created via constructor on PBackground but
|
||||
* has synchronous semantics from the perspective of content on the main thread.
|
||||
* The construction potentially involves waiting for disk I/O to load the
|
||||
* LocalStorage data from disk as well as related QuotaManager checks, so async
|
||||
* calls to PBackground are the only viable mechanism because blocking
|
||||
* PBackground is not acceptable. (Note that an attempt is made to minimize any
|
||||
* I/O latency by triggering preloading from
|
||||
* ContentParent::AboutToLoadHttpFtpWyciwygDocumentForChild, the central place
|
||||
* for pre-loading.)
|
||||
*/
|
||||
sync protocol PBackgroundLSDatabase
|
||||
{
|
||||
manager PBackground;
|
||||
manages PBackgroundLSSnapshot;
|
||||
|
||||
parent:
|
||||
// The DeleteMe message is used to avoid a race condition between the parent
|
||||
// actor and the child actor. The PBackgroundLSDatabase protocol could be
|
||||
// simply destroyed by sending the __delete__ message from the child side.
|
||||
// However, that would destroy the child actor immediatelly and the parent
|
||||
// could be sending a message to the child at the same time resulting in a
|
||||
// routing error since the child actor wouldn't exist anymore. A routing
|
||||
// error typically causes a crash. The race can be prevented by doing the
|
||||
// teardown in two steps. First, we send the DeleteMe message to the parent
|
||||
// and the parent then sends the __delete__ message to the child.
|
||||
async DeleteMe();
|
||||
|
||||
/**
|
||||
* Sent in response to a `RequestAllowToClose` message once the snapshot
|
||||
* cleanup has happened OR from LSDatabase's destructor if AllowToClose has
|
||||
* not already been reported.
|
||||
*/
|
||||
async AllowToClose();
|
||||
|
||||
/**
|
||||
* Invoked to create an LSSnapshot backed by a Snapshot in PBackground that
|
||||
* presents an atomic and consistent view of the state of the authoritative
|
||||
* Datastore state in the parent.
|
||||
*
|
||||
* This needs to be synchronous because LocalStorage's semantics are
|
||||
* synchronous. Note that the Datastore in the PBackground parent already
|
||||
* has the answers to this request immediately available without needing to
|
||||
* consult any other threads or perform any I/O. Additionally, the response
|
||||
* is explicitly bounded in size by the tunable snapshot prefill byte limit.
|
||||
*
|
||||
* @param increasePeakUsage
|
||||
* Whether the parent should attempt to pre-allocate some amount of quota
|
||||
* usage to the Snapshot.
|
||||
*/
|
||||
sync PBackgroundLSSnapshot(nsString documentURI,
|
||||
bool increasePeakUsage,
|
||||
int64_t requestedSize,
|
||||
int64_t minSize)
|
||||
returns (LSSnapshotInitInfo initInfo);
|
||||
|
||||
child:
|
||||
/**
|
||||
* Only sent by the parent in response to the child's DeleteMe request.
|
||||
*/
|
||||
async __delete__();
|
||||
|
||||
/**
|
||||
* Request to close the LSDatabase, checkpointing and finishing any
|
||||
* outstanding snapshots so no state is lost. This request is issued when
|
||||
* QuotaManager is shutting down or is aborting operations for an origin or
|
||||
* process. Once the snapshot has cleaned up, AllowToClose will be sent to
|
||||
* the parent.
|
||||
*
|
||||
* Note that the QuotaManager shutdown process is more likely to happen in
|
||||
* unit tests where we explicitly reset the QuotaManager. At runtime, we
|
||||
* expect windows to be closed and content processes terminated well before
|
||||
* QuotaManager shutdown would actually occur.
|
||||
*
|
||||
* Also, Operations are usually aborted for an origin due to privacy API's
|
||||
* clearing data for an origin. Operations are aborted for a process by
|
||||
* ContentParent::ShutDownProcess.
|
||||
*/
|
||||
async RequestAllowToClose();
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
57
dom/localstorage/PBackgroundLSObserver.ipdl
Normal file
57
dom/localstorage/PBackgroundLSObserver.ipdl
Normal file
@ -0,0 +1,57 @@
|
||||
/* 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/. */
|
||||
|
||||
include protocol PBackground;
|
||||
|
||||
include PBackgroundSharedTypes;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
/**
|
||||
* The observer protocol sends "storage" event notifications for changes to
|
||||
* LocalStorage that take place in other processes as their Snapshots are
|
||||
* Checkpointed to the canonical Datastore in the parent process. Same-process
|
||||
* notifications are generated as mutations happen.
|
||||
*
|
||||
* Note that mutations are never generated for redundant mutations. Setting the
|
||||
* key "foo" to have value "bar" when it already has value "bar" will never
|
||||
* result in a "storage" event.
|
||||
*/
|
||||
async protocol PBackgroundLSObserver
|
||||
{
|
||||
manager PBackground;
|
||||
|
||||
parent:
|
||||
/**
|
||||
* Sent by the LSObserver's destructor when it's going away. Any Observe
|
||||
* messages received after this is sent will be ignored. Which is fine,
|
||||
* because there should be nothing around left to hear. In the event a new
|
||||
* page came into existence, its Observer creation will happen (effectively)
|
||||
* synchronously.
|
||||
*/
|
||||
async DeleteMe();
|
||||
|
||||
child:
|
||||
/**
|
||||
* Only sent by the parent in response to a deletion request.
|
||||
*/
|
||||
async __delete__();
|
||||
|
||||
/**
|
||||
* Sent by the parent process as Snapshots from other processes are
|
||||
* Checkpointed, applying their mutations. The child actor currently directly
|
||||
* shunts these to Storage::NotifyChange to generate "storage" events for
|
||||
* immediate dispatch.
|
||||
*/
|
||||
async Observe(PrincipalInfo principalInfo,
|
||||
uint32_t privateBrowsingId,
|
||||
nsString documentURI,
|
||||
nsString key,
|
||||
nsString oldValue,
|
||||
nsString newValue);
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
103
dom/localstorage/PBackgroundLSRequest.ipdl
Normal file
103
dom/localstorage/PBackgroundLSRequest.ipdl
Normal file
@ -0,0 +1,103 @@
|
||||
/* 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/. */
|
||||
|
||||
include protocol PBackground;
|
||||
|
||||
using struct mozilla::null_t
|
||||
from "ipc/IPCMessageUtils.h";
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
union NullableDatastoreId
|
||||
{
|
||||
null_t;
|
||||
uint64_t;
|
||||
};
|
||||
|
||||
struct LSRequestPrepareDatastoreResponse
|
||||
{
|
||||
NullableDatastoreId datastoreId;
|
||||
};
|
||||
|
||||
struct LSRequestPrepareObserverResponse
|
||||
{
|
||||
uint64_t observerId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Discriminated union which can contain an error code (`nsresult`) or
|
||||
* particular request response.
|
||||
*/
|
||||
union LSRequestResponse
|
||||
{
|
||||
nsresult;
|
||||
LSRequestPrepareDatastoreResponse;
|
||||
LSRequestPrepareObserverResponse;
|
||||
};
|
||||
|
||||
/**
|
||||
* An asynchronous protocol for issuing requests that are used in a synchronous
|
||||
* fashion by LocalStorage via LSObject's RequestHelper mechanism. This differs
|
||||
* from LSSimpleRequest which is implemented and used asynchronously.
|
||||
*
|
||||
* See `PBackgroundLSSharedTypes.ipdlh` for more on the request types, the
|
||||
* response types above for their corresponding responses, and `RequestHelper`
|
||||
* for more on the usage and lifecycle of this mechanism.
|
||||
*/
|
||||
protocol PBackgroundLSRequest
|
||||
{
|
||||
manager PBackground;
|
||||
|
||||
parent:
|
||||
// The Cancel message is used to avoid a possible dead lock caused by a CPOW
|
||||
// sending a synchronous message from the main thread in the chrome process
|
||||
// to the main thread in the content process at the time we are blocking
|
||||
// the main thread in the content process to handle a request.
|
||||
// We use the PBackground thread on the parent side to handle requests, but
|
||||
// sometimes we need to get information from principals and that's currently
|
||||
// only possible on the main thread. So if the main thread in the chrome
|
||||
// process is blocked by a CPOW operation, our request must wait for the CPOW
|
||||
// operation to complete. However the CPOW operation can't complete either
|
||||
// because we are blocking the main thread in the content process.
|
||||
// The dead lock is prevented by canceling our nested event loop in the
|
||||
// content process when we receive a synchronous IPC message from the parent.
|
||||
//
|
||||
// Note that cancellation isn't instantaneous. It's just an asynchronous flow
|
||||
// that definitely doesn't involve the main thread in the parent process, so
|
||||
// we're guaranteed to unblock the main-thread in the content process and
|
||||
// allow the sync IPC to make progress. When Cancel() is received by the
|
||||
// parent, it will Send__delete__. The child will either send Cancel or
|
||||
// Finish, but not both.
|
||||
async Cancel();
|
||||
|
||||
/**
|
||||
* Sent by the child in response to Ready, requesting that __delete__ be sent
|
||||
* with the result. The child will either send Finish or Cancel, but not
|
||||
* both. No further message will be sent from the child after invoking one.
|
||||
*/
|
||||
async Finish();
|
||||
|
||||
child:
|
||||
/**
|
||||
* The deletion is sent with the result of the request directly in response to
|
||||
* either Cancel or Finish.
|
||||
*/
|
||||
async __delete__(LSRequestResponse response);
|
||||
|
||||
/**
|
||||
* Sent by the parent when it has completed whatever async stuff it needs to
|
||||
* do and is ready to send the results. It then awaits the Finish() call to
|
||||
* send the results. This may seem redundant, but it's not. If the
|
||||
* __delete__ was sent directly, it's possible there could be a race where
|
||||
* Cancel() would be received by the parent after it had already sent
|
||||
* __delete__. (Which may no longer be fatal thanks to improvements to the
|
||||
* IPC layer, but it would still lead to warnings, etc. And we don't
|
||||
* expect PBackground to be highly contended nor the DOM File thread.)
|
||||
*/
|
||||
async Ready();
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
38
dom/localstorage/PBackgroundLSSharedTypes.ipdlh
Normal file
38
dom/localstorage/PBackgroundLSSharedTypes.ipdlh
Normal file
@ -0,0 +1,38 @@
|
||||
/* 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/. */
|
||||
|
||||
include PBackgroundSharedTypes;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
struct LSRequestPrepareDatastoreParams
|
||||
{
|
||||
PrincipalInfo principalInfo;
|
||||
bool createIfNotExists;
|
||||
};
|
||||
|
||||
struct LSRequestPrepareObserverParams
|
||||
{
|
||||
PrincipalInfo principalInfo;
|
||||
};
|
||||
|
||||
union LSRequestParams
|
||||
{
|
||||
LSRequestPrepareDatastoreParams;
|
||||
LSRequestPrepareObserverParams;
|
||||
};
|
||||
|
||||
struct LSSimpleRequestPreloadedParams
|
||||
{
|
||||
PrincipalInfo principalInfo;
|
||||
};
|
||||
|
||||
union LSSimpleRequestParams
|
||||
{
|
||||
LSSimpleRequestPreloadedParams;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
50
dom/localstorage/PBackgroundLSSimpleRequest.ipdl
Normal file
50
dom/localstorage/PBackgroundLSSimpleRequest.ipdl
Normal file
@ -0,0 +1,50 @@
|
||||
/* 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/. */
|
||||
|
||||
include protocol PBackground;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
/**
|
||||
* Response to a `LSSimpleRequestPreloadedParams` request indicating whether the
|
||||
* origin was preloaded.
|
||||
*/
|
||||
struct LSSimpleRequestPreloadedResponse
|
||||
{
|
||||
bool preloaded;
|
||||
};
|
||||
|
||||
/**
|
||||
* Discriminated union which can contain an error code (`nsresult`) or
|
||||
* particular simple request response.
|
||||
*/
|
||||
union LSSimpleRequestResponse
|
||||
{
|
||||
nsresult;
|
||||
LSSimpleRequestPreloadedResponse;
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple requests are async-only from both a protocol perspective and the
|
||||
* manner in which they're used. In comparison, PBackgroundLSRequests are
|
||||
* async only from a protocol perspective; they are used synchronously from the
|
||||
* main thread via LSObject's RequestHelper mechanism. (With the caveat that
|
||||
* nsILocalStorageManager does expose LSRequests asynchronously.)
|
||||
*
|
||||
* These requests use the common idiom where the arguments to the request are
|
||||
* sent in the constructor and the result is sent in the __delete__ response.
|
||||
* Request types are indicated by the Params variant used and those live in
|
||||
* `PBackgroundLSSharedTypes.ipdlh`.
|
||||
*/
|
||||
protocol PBackgroundLSSimpleRequest
|
||||
{
|
||||
manager PBackground;
|
||||
|
||||
child:
|
||||
async __delete__(LSSimpleRequestResponse response);
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
114
dom/localstorage/PBackgroundLSSnapshot.ipdl
Normal file
114
dom/localstorage/PBackgroundLSSnapshot.ipdl
Normal file
@ -0,0 +1,114 @@
|
||||
/* 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/. */
|
||||
|
||||
include protocol PBackground;
|
||||
include protocol PBackgroundLSDatabase;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
struct LSSetItemInfo
|
||||
{
|
||||
nsString key;
|
||||
nsString oldValue;
|
||||
nsString value;
|
||||
};
|
||||
|
||||
struct LSRemoveItemInfo
|
||||
{
|
||||
nsString key;
|
||||
nsString oldValue;
|
||||
};
|
||||
|
||||
struct LSClearInfo
|
||||
{
|
||||
};
|
||||
|
||||
/**
|
||||
* Union of LocalStorage mutation types.
|
||||
*/
|
||||
union LSWriteInfo
|
||||
{
|
||||
LSSetItemInfo;
|
||||
LSRemoveItemInfo;
|
||||
LSClearInfo;
|
||||
};
|
||||
|
||||
sync protocol PBackgroundLSSnapshot
|
||||
{
|
||||
manager PBackgroundLSDatabase;
|
||||
|
||||
parent:
|
||||
async DeleteMe();
|
||||
|
||||
async Checkpoint(LSWriteInfo[] writeInfos);
|
||||
|
||||
async Finish();
|
||||
|
||||
async Loaded();
|
||||
|
||||
/**
|
||||
* Invoked on demand to load an item that didn't fit into the initial
|
||||
* snapshot prefill.
|
||||
*
|
||||
* This needs to be synchronous because LocalStorage's semantics are
|
||||
* synchronous. Note that the Snapshot in the PBackground parent already
|
||||
* has the answers to this request immediately available without needing to
|
||||
* consult any other threads or perform any I/O.
|
||||
*/
|
||||
sync LoadItem(nsString key)
|
||||
returns (nsString value);
|
||||
|
||||
/**
|
||||
* Invoked on demand to load all keys in in their canonical order if they
|
||||
* didn't fit into the initial snapshot prefill.
|
||||
*
|
||||
* This needs to be synchronous because LocalStorage's semantics are
|
||||
* synchronous. Note that the Snapshot in the PBackground parent already
|
||||
* has the answers to this request immediately available without needing to
|
||||
* consult any other threads or perform any I/O.
|
||||
*/
|
||||
sync LoadKeys()
|
||||
returns (nsString[] keys);
|
||||
|
||||
/**
|
||||
* This needs to be synchronous because LocalStorage's semantics are
|
||||
* synchronous. Note that the Snapshot in the PBackground parent typically
|
||||
* doesn't need to consult any other threads or perform any I/O to handle
|
||||
* this request. However, it has to call a quota manager method that can
|
||||
* potentially do I/O directly on the PBackground thread. It can only happen
|
||||
* rarely in a storage pressure (low storage space) situation. Specifically,
|
||||
* after we get a list of origin directories for eviction, we will delete
|
||||
* them directly on the PBackground thread. This doesn't cause any
|
||||
* performance problems, but avoiding I/O completely might need to be done as
|
||||
* a futher optimization.
|
||||
*/
|
||||
sync IncreasePeakUsage(int64_t requestedSize, int64_t minSize)
|
||||
returns (int64_t size);
|
||||
|
||||
// A synchronous ping to the parent actor to confirm that the parent actor
|
||||
// has received previous async message. This should only be used by the
|
||||
// snapshotting code to end an explicit snapshot.
|
||||
sync Ping();
|
||||
|
||||
child:
|
||||
/**
|
||||
* Compels the child LSSnapshot to Checkpoint() and Finish(), effectively
|
||||
* compelling the snapshot to flush any issued mutations and close itself.
|
||||
* The child LSSnapshot does that either immediately if it's just waiting
|
||||
* to be reused or when it gets into a stable state.
|
||||
*
|
||||
* This message is expected to be sent in the following two cases only:
|
||||
* 1. The state of the underlying Datastore starts to differ from the state
|
||||
* captured at the time of snapshot creation.
|
||||
* 2. The last private browsing context exits. And in that case we expect
|
||||
* all private browsing globals to already have been destroyed.
|
||||
*/
|
||||
async MarkDirty();
|
||||
|
||||
async __delete__();
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
36
dom/localstorage/ReportInternalError.cpp
Normal file
36
dom/localstorage/ReportInternalError.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "ReportInternalError.h"
|
||||
|
||||
#include "mozilla/IntegerPrintfMacros.h"
|
||||
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsPrintfCString.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
namespace localstorage {
|
||||
|
||||
void
|
||||
ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr)
|
||||
{
|
||||
// Get leaf of file path
|
||||
for (const char* p = aFile; *p; ++p) {
|
||||
if (*p == '/' && *(p + 1)) {
|
||||
aFile = p + 1;
|
||||
}
|
||||
}
|
||||
|
||||
nsContentUtils::LogSimpleConsoleError(
|
||||
NS_ConvertUTF8toUTF16(nsPrintfCString(
|
||||
"LocalStorage %s: %s:%" PRIu32, aStr, aFile, aLine)),
|
||||
"localstorage", false);
|
||||
}
|
||||
|
||||
} // namespace localstorage
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
32
dom/localstorage/ReportInternalError.h
Normal file
32
dom/localstorage/ReportInternalError.h
Normal file
@ -0,0 +1,32 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_localstorage_ReportInternalError_h
|
||||
#define mozilla_dom_localstorage_ReportInternalError_h
|
||||
|
||||
#include "nsDebug.h"
|
||||
|
||||
#define LS_WARNING(...) \
|
||||
do { \
|
||||
nsPrintfCString s(__VA_ARGS__); \
|
||||
mozilla::dom::localstorage::ReportInternalError(__FILE__, \
|
||||
__LINE__, \
|
||||
s.get()); \
|
||||
NS_WARNING(s.get()); \
|
||||
} while (0)
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
namespace localstorage {
|
||||
|
||||
void
|
||||
ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr);
|
||||
|
||||
} // namespace localstorage
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_localstorage_ReportInternalError_h
|
25
dom/localstorage/SerializationHelpers.h
Normal file
25
dom/localstorage/SerializationHelpers.h
Normal file
@ -0,0 +1,25 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_localstorage_SerializationHelpers_h
|
||||
#define mozilla_dom_localstorage_SerializationHelpers_h
|
||||
|
||||
#include "ipc/IPCMessageUtils.h"
|
||||
|
||||
#include "mozilla/dom/LSSnapshot.h"
|
||||
|
||||
namespace IPC {
|
||||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::dom::LSSnapshot::LoadState> :
|
||||
public ContiguousEnumSerializer<mozilla::dom::LSSnapshot::LoadState,
|
||||
mozilla::dom::LSSnapshot::LoadState::Initial,
|
||||
mozilla::dom::LSSnapshot::LoadState::EndGuard>
|
||||
{ };
|
||||
|
||||
} // namespace IPC
|
||||
|
||||
#endif // mozilla_dom_localstorage_SerializationHelpers_h
|
64
dom/localstorage/moz.build
Normal file
64
dom/localstorage/moz.build
Normal file
@ -0,0 +1,64 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += [
|
||||
'test/unit/xpcshell.ini'
|
||||
]
|
||||
|
||||
TEST_HARNESS_FILES.xpcshell.dom.localstorage.test.unit += [
|
||||
'test/unit/databaseShadowing-shared.js',
|
||||
]
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsILocalStorageManager.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'dom_localstorage'
|
||||
|
||||
EXPORTS.mozilla.dom.localstorage += [
|
||||
'ActorsParent.h',
|
||||
'SerializationHelpers.h',
|
||||
]
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
'LocalStorageCommon.h',
|
||||
'LocalStorageManager2.h',
|
||||
'LSObject.h',
|
||||
'LSObserver.h',
|
||||
'LSSnapshot.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'ActorsChild.cpp',
|
||||
'ActorsParent.cpp',
|
||||
'LocalStorageCommon.cpp',
|
||||
'LocalStorageManager2.cpp',
|
||||
'LSDatabase.cpp',
|
||||
'LSObject.cpp',
|
||||
'LSObserver.cpp',
|
||||
'LSSnapshot.cpp',
|
||||
'ReportInternalError.cpp',
|
||||
]
|
||||
|
||||
IPDL_SOURCES += [
|
||||
'PBackgroundLSDatabase.ipdl',
|
||||
'PBackgroundLSObserver.ipdl',
|
||||
'PBackgroundLSRequest.ipdl',
|
||||
'PBackgroundLSSharedTypes.ipdlh',
|
||||
'PBackgroundLSSimpleRequest.ipdl',
|
||||
'PBackgroundLSSnapshot.ipdl',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
if CONFIG['GNU_CXX']:
|
||||
CXXFLAGS += ['-Wno-error=shadow']
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/dom/file/ipc',
|
||||
]
|
35
dom/localstorage/nsILocalStorageManager.idl
Normal file
35
dom/localstorage/nsILocalStorageManager.idl
Normal file
@ -0,0 +1,35 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIPrincipal;
|
||||
|
||||
/**
|
||||
* Methods specific to LocalStorage, see nsIDOMStorageManager for methods shared
|
||||
* with SessionStorage. Methods may migrate there as SessionStorage is
|
||||
* overhauled.
|
||||
*/
|
||||
[scriptable, builtinclass, uuid(d4f534da-2744-4db3-8774-8b187c64ade9)]
|
||||
interface nsILocalStorageManager : nsISupports
|
||||
{
|
||||
readonly attribute boolean nextGenLocalStorageEnabled;
|
||||
|
||||
/**
|
||||
* Trigger preload of LocalStorage for the given principal. For use by
|
||||
* ContentParent::AboutToLoadHttpFtpWyciwygDocumentForChild to maximize the
|
||||
* amount of time we have to load the data off disk before the page might
|
||||
* attempt to touch LocalStorage.
|
||||
*
|
||||
* This method will not create a QuotaManager-managed directory on disk if
|
||||
* one does not already exist for the principal.
|
||||
*/
|
||||
[implicit_jscontext] nsISupports
|
||||
preload(in nsIPrincipal aPrincipal);
|
||||
|
||||
[implicit_jscontext] nsISupports
|
||||
isPreloaded(in nsIPrincipal aPrincipal);
|
||||
};
|
BIN
dom/localstorage/test/unit/archive_profile.zip
Normal file
BIN
dom/localstorage/test/unit/archive_profile.zip
Normal file
Binary file not shown.
114
dom/localstorage/test/unit/databaseShadowing-shared.js
Normal file
114
dom/localstorage/test/unit/databaseShadowing-shared.js
Normal file
@ -0,0 +1,114 @@
|
||||
const principalInfos = [
|
||||
{ url: "http://example.com", attrs: {} },
|
||||
|
||||
{ url: "http://origin.test", attrs: {} },
|
||||
|
||||
{ url: "http://prefix.test", attrs: {} },
|
||||
{ url: "http://prefix.test", attrs: { userContextId: 10 } },
|
||||
|
||||
{ url: "http://pattern.test", attrs: { userContextId: 15 } },
|
||||
{ url: "http://pattern.test:8080", attrs: { userContextId: 15 } },
|
||||
{ url: "https://pattern.test", attrs: { userContextId: 15 } },
|
||||
];
|
||||
|
||||
function enableNextGenLocalStorage()
|
||||
{
|
||||
info("Setting pref");
|
||||
|
||||
Services.prefs.setBoolPref("dom.storage.next_gen", true);
|
||||
}
|
||||
|
||||
function disableNextGenLocalStorage()
|
||||
{
|
||||
info("Setting pref");
|
||||
|
||||
Services.prefs.setBoolPref("dom.storage.next_gen", false);
|
||||
}
|
||||
|
||||
function storeData()
|
||||
{
|
||||
for (let i = 0; i < principalInfos.length; i++) {
|
||||
let principalInfo = principalInfos[i];
|
||||
let principal = getPrincipal(principalInfo.url, principalInfo.attrs);
|
||||
|
||||
info("Getting storage");
|
||||
|
||||
let storage = getLocalStorage(principal);
|
||||
|
||||
info("Adding data");
|
||||
|
||||
storage.setItem("key0", "value0");
|
||||
storage.clear();
|
||||
storage.setItem("key1", "value1");
|
||||
storage.removeItem("key1");
|
||||
storage.setItem("key2", "value2");
|
||||
|
||||
info("Closing storage");
|
||||
|
||||
storage.close();
|
||||
}
|
||||
}
|
||||
|
||||
function exportShadowDatabase(name)
|
||||
{
|
||||
info("Verifying shadow database");
|
||||
|
||||
let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
||||
let shadowDatabase = profileDir.clone();
|
||||
shadowDatabase.append("webappsstore.sqlite");
|
||||
|
||||
let exists = shadowDatabase.exists();
|
||||
ok(exists, "Shadow database does exist");
|
||||
|
||||
info("Copying shadow database");
|
||||
|
||||
let currentDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
|
||||
shadowDatabase.copyTo(currentDir, name);
|
||||
}
|
||||
|
||||
function importShadowDatabase(name)
|
||||
{
|
||||
info("Verifying shadow database");
|
||||
|
||||
let currentDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
|
||||
let shadowDatabase = currentDir.clone();
|
||||
shadowDatabase.append(name);
|
||||
|
||||
let exists = shadowDatabase.exists();
|
||||
if (!exists) {
|
||||
return false;
|
||||
}
|
||||
|
||||
info("Copying shadow database");
|
||||
|
||||
let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
||||
shadowDatabase.copyTo(profileDir, "webappsstore.sqlite");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function verifyData(clearedOrigins)
|
||||
{
|
||||
for (let i = 0; i < principalInfos.length; i++) {
|
||||
let principalInfo = principalInfos[i];
|
||||
let principal = getPrincipal(principalInfo.url, principalInfo.attrs);
|
||||
|
||||
info("Getting storage");
|
||||
|
||||
let storage = getLocalStorage(principal);
|
||||
|
||||
info("Verifying data");
|
||||
|
||||
if (clearedOrigins.includes(i)) {
|
||||
ok(storage.getItem("key2") == null, "Correct value");
|
||||
} else {
|
||||
ok(storage.getItem("key0") == null, "Correct value");
|
||||
ok(storage.getItem("key1") == null, "Correct value");
|
||||
ok(storage.getItem("key2") == "value2", "Correct value");
|
||||
}
|
||||
|
||||
info("Closing storage");
|
||||
|
||||
storage.close();
|
||||
}
|
||||
}
|
271
dom/localstorage/test/unit/head.js
Normal file
271
dom/localstorage/test/unit/head.js
Normal file
@ -0,0 +1,271 @@
|
||||
/**
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
const NS_ERROR_DOM_QUOTA_EXCEEDED_ERR = 22;
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function is(a, b, msg)
|
||||
{
|
||||
Assert.equal(a, b, msg);
|
||||
}
|
||||
|
||||
function ok(cond, msg)
|
||||
{
|
||||
Assert.ok(!!cond, msg);
|
||||
}
|
||||
|
||||
function run_test()
|
||||
{
|
||||
runTest();
|
||||
};
|
||||
|
||||
if (!this.runTest) {
|
||||
this.runTest = function()
|
||||
{
|
||||
do_get_profile();
|
||||
|
||||
enableTesting();
|
||||
|
||||
Assert.ok(typeof testSteps === "function",
|
||||
"There should be a testSteps function");
|
||||
Assert.ok(testSteps.constructor.name === "AsyncFunction",
|
||||
"testSteps should be an async function");
|
||||
|
||||
registerCleanupFunction(resetTesting);
|
||||
|
||||
add_task(testSteps);
|
||||
|
||||
// Since we defined run_test, we must invoke run_next_test() to start the
|
||||
// async test.
|
||||
run_next_test();
|
||||
}
|
||||
}
|
||||
|
||||
function returnToEventLoop()
|
||||
{
|
||||
return new Promise(function(resolve) {
|
||||
executeSoon(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
function enableTesting()
|
||||
{
|
||||
Services.prefs.setBoolPref("dom.storage.testing", true);
|
||||
Services.prefs.setBoolPref("dom.quotaManager.testing", true);
|
||||
}
|
||||
|
||||
function resetTesting()
|
||||
{
|
||||
Services.prefs.clearUserPref("dom.quotaManager.testing");
|
||||
Services.prefs.clearUserPref("dom.storage.testing");
|
||||
}
|
||||
|
||||
function setGlobalLimit(globalLimit)
|
||||
{
|
||||
Services.prefs.setIntPref("dom.quotaManager.temporaryStorage.fixedLimit",
|
||||
globalLimit);
|
||||
}
|
||||
|
||||
function resetGlobalLimit()
|
||||
{
|
||||
Services.prefs.clearUserPref("dom.quotaManager.temporaryStorage.fixedLimit");
|
||||
}
|
||||
|
||||
function setOriginLimit(originLimit)
|
||||
{
|
||||
Services.prefs.setIntPref("dom.storage.default_quota", originLimit);
|
||||
}
|
||||
|
||||
function resetOriginLimit()
|
||||
{
|
||||
Services.prefs.clearUserPref("dom.storage.default_quota");
|
||||
}
|
||||
|
||||
function getOriginUsage(principal)
|
||||
{
|
||||
let request = Services.qms.getUsageForPrincipal(principal, function() { });
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
function clear()
|
||||
{
|
||||
let request = Services.qms.clear();
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
function clearOriginsByPattern(pattern)
|
||||
{
|
||||
let request = Services.qms.clearStoragesForOriginAttributesPattern(pattern);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
function clearOriginsByPrefix(principal, persistence)
|
||||
{
|
||||
let request =
|
||||
Services.qms.clearStoragesForPrincipal(principal, persistence, null, true);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
function clearOrigin(principal, persistence)
|
||||
{
|
||||
let request = Services.qms.clearStoragesForPrincipal(principal, persistence);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
function reset(callback)
|
||||
{
|
||||
let request = Services.qms.reset();
|
||||
request.callback = callback;
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
function resetOrigin(principal)
|
||||
{
|
||||
let request =
|
||||
Services.qms.resetStoragesForPrincipal(principal, "default", "ls");
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
function installPackage(packageName)
|
||||
{
|
||||
let directoryService = Cc["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Ci.nsIProperties);
|
||||
|
||||
let currentDir = directoryService.get("CurWorkD", Ci.nsIFile);
|
||||
|
||||
let packageFile = currentDir.clone();
|
||||
packageFile.append(packageName + ".zip");
|
||||
|
||||
let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
|
||||
.createInstance(Ci.nsIZipReader);
|
||||
zipReader.open(packageFile);
|
||||
|
||||
let entryNames = [];
|
||||
let entries = zipReader.findEntries(null);
|
||||
while (entries.hasMore()) {
|
||||
let entry = entries.getNext();
|
||||
entryNames.push(entry);
|
||||
}
|
||||
entryNames.sort();
|
||||
|
||||
for (let entryName of entryNames) {
|
||||
let zipentry = zipReader.getEntry(entryName);
|
||||
|
||||
let file = getRelativeFile(entryName);
|
||||
|
||||
if (zipentry.isDirectory) {
|
||||
file.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
|
||||
} else {
|
||||
let istream = zipReader.getInputStream(entryName);
|
||||
|
||||
var ostream = Cc["@mozilla.org/network/file-output-stream;1"]
|
||||
.createInstance(Ci.nsIFileOutputStream);
|
||||
ostream.init(file, -1, parseInt("0644", 8), 0);
|
||||
|
||||
let bostream = Cc['@mozilla.org/network/buffered-output-stream;1']
|
||||
.createInstance(Ci.nsIBufferedOutputStream);
|
||||
bostream.init(ostream, 32768);
|
||||
|
||||
bostream.writeFrom(istream, istream.available());
|
||||
|
||||
istream.close();
|
||||
bostream.close();
|
||||
}
|
||||
}
|
||||
|
||||
zipReader.close();
|
||||
}
|
||||
|
||||
function getProfileDir()
|
||||
{
|
||||
let directoryService =
|
||||
Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
|
||||
|
||||
return directoryService.get("ProfD", Ci.nsIFile);
|
||||
}
|
||||
|
||||
// Given a "/"-delimited path relative to the profile directory,
|
||||
// return an nsIFile representing the path. This does not test
|
||||
// for the existence of the file or parent directories.
|
||||
// It is safe even on Windows where the directory separator is not "/",
|
||||
// but make sure you're not passing in a "\"-delimited path.
|
||||
function getRelativeFile(relativePath)
|
||||
{
|
||||
let profileDir = getProfileDir();
|
||||
|
||||
let file = profileDir.clone();
|
||||
relativePath.split('/').forEach(function(component) {
|
||||
file.append(component);
|
||||
});
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
function repeatChar(count, ch) {
|
||||
if (count == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let result = ch;
|
||||
let count2 = count / 2;
|
||||
|
||||
// Double the input until it is long enough.
|
||||
while (result.length <= count2) {
|
||||
result += result;
|
||||
}
|
||||
|
||||
// Use substring to hit the precise length target without using extra memory.
|
||||
return result + result.substring(0, count - result.length);
|
||||
}
|
||||
|
||||
function getPrincipal(url, attrs)
|
||||
{
|
||||
let uri = Services.io.newURI(url);
|
||||
if (!attrs) {
|
||||
attrs = {};
|
||||
}
|
||||
return Services.scriptSecurityManager.createCodebasePrincipal(uri, attrs);
|
||||
}
|
||||
|
||||
function getCurrentPrincipal()
|
||||
{
|
||||
return Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
|
||||
}
|
||||
|
||||
function getLocalStorage(principal)
|
||||
{
|
||||
if (!principal) {
|
||||
principal = getCurrentPrincipal();
|
||||
}
|
||||
|
||||
return Services.domStorageManager.createStorage(null, principal, "");
|
||||
}
|
||||
|
||||
function requestFinished(request) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
request.callback = function(request) {
|
||||
if (request.resultCode == Cr.NS_OK) {
|
||||
resolve(request.result);
|
||||
} else {
|
||||
reject(request.resultCode);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadSubscript(path)
|
||||
{
|
||||
let file = do_get_file(path, false);
|
||||
let uri = Services.io.newFileURI(file);
|
||||
Services.scriptloader.loadSubScript(uri.spec);
|
||||
}
|
BIN
dom/localstorage/test/unit/migration_profile.zip
Normal file
BIN
dom/localstorage/test/unit/migration_profile.zip
Normal file
Binary file not shown.
76
dom/localstorage/test/unit/test_archive.js
Normal file
76
dom/localstorage/test/unit/test_archive.js
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
async function testSteps()
|
||||
{
|
||||
const lsArchiveFile = "storage/ls-archive.sqlite";
|
||||
|
||||
const principalInfo = {
|
||||
url: "http://example.com",
|
||||
attrs: {}
|
||||
};
|
||||
|
||||
function checkStorage()
|
||||
{
|
||||
let principal = getPrincipal(principalInfo.url, principalInfo.attrs);
|
||||
let storage = getLocalStorage(principal);
|
||||
try {
|
||||
storage.open();
|
||||
ok(true, "Did not throw");
|
||||
} catch(ex) {
|
||||
ok(false, "Should not have thrown");
|
||||
}
|
||||
}
|
||||
|
||||
info("Setting pref");
|
||||
|
||||
Services.prefs.setBoolPref("dom.storage.next_gen", true);
|
||||
|
||||
// Profile 1 - Archive file is a directory.
|
||||
info("Clearing");
|
||||
|
||||
let request = clear();
|
||||
await requestFinished(request);
|
||||
|
||||
let archiveFile = getRelativeFile(lsArchiveFile);
|
||||
|
||||
archiveFile.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
|
||||
|
||||
checkStorage();
|
||||
|
||||
// Profile 2 - Corrupted archive file.
|
||||
info("Clearing");
|
||||
|
||||
request = clear();
|
||||
await requestFinished(request);
|
||||
|
||||
let ostream = Cc["@mozilla.org/network/file-output-stream;1"]
|
||||
.createInstance(Ci.nsIFileOutputStream);
|
||||
ostream.init(archiveFile, -1, parseInt("0644", 8), 0);
|
||||
ostream.write("foobar", 6);
|
||||
ostream.close();
|
||||
|
||||
checkStorage();
|
||||
|
||||
// Profile 3 - Nonupdateable archive file.
|
||||
info("Clearing");
|
||||
|
||||
request = clear();
|
||||
await requestFinished(request);
|
||||
|
||||
info("Installing package");
|
||||
|
||||
// The profile contains storage.sqlite and storage/ls-archive.sqlite
|
||||
// storage/ls-archive.sqlite was taken from FF 54 to force an upgrade.
|
||||
// There's just one record in the webappsstore2 table. The record was
|
||||
// modified by renaming the origin attribute userContextId to userContextKey.
|
||||
// This triggers an error during the upgrade.
|
||||
installPackage("archive_profile");
|
||||
|
||||
let fileSize = archiveFile.fileSize;
|
||||
ok(fileSize > 0, "archive file size is greater than zero");
|
||||
|
||||
checkStorage();
|
||||
}
|
23
dom/localstorage/test/unit/test_databaseShadowing1.js
Normal file
23
dom/localstorage/test/unit/test_databaseShadowing1.js
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
loadSubscript("databaseShadowing-shared.js");
|
||||
|
||||
async function testSteps()
|
||||
{
|
||||
enableNextGenLocalStorage();
|
||||
|
||||
storeData();
|
||||
|
||||
verifyData([]);
|
||||
|
||||
// Wait for all database connections to close.
|
||||
let request = reset();
|
||||
await requestFinished(request);
|
||||
|
||||
exportShadowDatabase("shadowdb.sqlite");
|
||||
|
||||
// The shadow database is now prepared for test_databaseShadowing2.js
|
||||
}
|
19
dom/localstorage/test/unit/test_databaseShadowing2.js
Normal file
19
dom/localstorage/test/unit/test_databaseShadowing2.js
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
loadSubscript("databaseShadowing-shared.js");
|
||||
|
||||
async function testSteps()
|
||||
{
|
||||
// The shadow database was prepared in test_databaseShadowing1.js
|
||||
|
||||
disableNextGenLocalStorage();
|
||||
|
||||
if (!importShadowDatabase("shadowdb.sqlite")) {
|
||||
return;
|
||||
}
|
||||
|
||||
verifyData([]);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
loadSubscript("databaseShadowing-shared.js");
|
||||
|
||||
async function testSteps()
|
||||
{
|
||||
enableNextGenLocalStorage();
|
||||
|
||||
storeData();
|
||||
|
||||
verifyData([]);
|
||||
|
||||
let principal = getPrincipal("http://origin.test", {});
|
||||
let request = clearOrigin(principal, "default");
|
||||
await requestFinished(request);
|
||||
|
||||
verifyData([1]);
|
||||
|
||||
// Wait for all database connections to close.
|
||||
request = reset();
|
||||
await requestFinished(request);
|
||||
|
||||
exportShadowDatabase("shadowdb_clearedOrigin.sqlite");
|
||||
|
||||
// The shadow database is now prepared for
|
||||
// test_databaseShadowing_clearOrigin2.js
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
loadSubscript("databaseShadowing-shared.js");
|
||||
|
||||
async function testSteps()
|
||||
{
|
||||
// The shadow database was prepared in test_databaseShadowing_clearOrigin1.js
|
||||
|
||||
disableNextGenLocalStorage();
|
||||
|
||||
if (!importShadowDatabase("shadowdb-clearedOrigin.sqlite")) {
|
||||
return;
|
||||
}
|
||||
|
||||
verifyData([1]);
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
loadSubscript("databaseShadowing-shared.js");
|
||||
|
||||
async function testSteps()
|
||||
{
|
||||
enableNextGenLocalStorage();
|
||||
|
||||
storeData();
|
||||
|
||||
verifyData([]);
|
||||
|
||||
let request = clearOriginsByPattern(JSON.stringify({ userContextId: 15 }));
|
||||
await requestFinished(request);
|
||||
|
||||
verifyData([4,5,6]);
|
||||
|
||||
// Wait for all database connections to close.
|
||||
request = reset();
|
||||
await requestFinished(request);
|
||||
|
||||
exportShadowDatabase("shadowdb-clearedOriginsByPattern.sqlite");
|
||||
|
||||
// The shadow database is now prepared for
|
||||
// test_databaseShadowing_clearOriginsByPattern2.js
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
loadSubscript("databaseShadowing-shared.js");
|
||||
|
||||
async function testSteps()
|
||||
{
|
||||
// The shadow database was prepared in
|
||||
// test_databaseShadowing_clearOriginsByPattern1.js
|
||||
|
||||
disableNextGenLocalStorage();
|
||||
|
||||
if (!importShadowDatabase("shadowdb-clearedOriginsByPattern.sqlite")) {
|
||||
return;
|
||||
}
|
||||
|
||||
verifyData([4,5,6]);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user