Merge mozilla-inbound to mozilla-central a=merge

This commit is contained in:
Razvan Maries 2018-11-30 05:05:29 +02:00
commit 530163105d
257 changed files with 19672 additions and 4809 deletions

View File

@ -190,6 +190,7 @@ dom/grid/**
dom/html/**
dom/ipc/**
dom/jsurl/**
dom/localstorage/**
dom/manifest/**
dom/media/test/**
dom/media/tests/**

View File

@ -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(); };
});

View File

@ -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

View File

@ -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`,

View File

@ -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",

View File

@ -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;
}

View File

@ -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;
}

View File

@ -5,3 +5,5 @@
skip-if = debug # Bug 1507747
[browser_search.js]
skip-if = debug # Bug 1507747
[browser_locked.js]

View File

@ -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);
});
});
});

View File

@ -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.

View File

@ -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);

View File

@ -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);

View File

@ -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");

View File

@ -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]

View File

@ -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;
});

View File

@ -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;
}));
}

View File

@ -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) {

View File

@ -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

View File

@ -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]

View File

@ -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";

View File

@ -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);

View File

@ -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");

View File

@ -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";

View File

@ -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));
}

View File

@ -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]

View File

@ -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");
}

View File

@ -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";

View File

@ -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]

View File

@ -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");
}

View File

@ -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]

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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";

View File

@ -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();
}

View File

@ -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]

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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";

View File

@ -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)) {

View File

@ -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,

View File

@ -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]

View File

@ -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

View File

@ -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]

View 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>

View File

@ -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;

View File

@ -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

View File

@ -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();
}
}
}

View File

@ -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"

View File

@ -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

View File

@ -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)
{

View File

@ -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,

View File

@ -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]

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -54,6 +54,12 @@ private:
nsTArray<RefPtr<IPCBlobInputStreamChild>> mPendingActors;
};
bool
IsOnDOMFileThread();
void
AssertIsOnDOMFileThread();
} // dom namespace
} // mozilla namespace

View File

@ -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;

View File

@ -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

View File

@ -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)
{

View File

@ -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;

View File

@ -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;
}

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

262
dom/localstorage/LSObject.h Normal file
View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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',
]

View 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);
};

Binary file not shown.

View 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();
}
}

View 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);
}

Binary file not shown.

View 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();
}

View 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
}

View 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([]);
}

View File

@ -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
}

View 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_databaseShadowing_clearOrigin1.js
disableNextGenLocalStorage();
if (!importShadowDatabase("shadowdb-clearedOrigin.sqlite")) {
return;
}
verifyData([1]);
}

View File

@ -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
}

View File

@ -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