Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-04-18 15:05:36 +02:00
Родитель 5f8eefe278 3689b59fae
Коммит 26b5712f9f
58 изменённых файлов: 1194 добавлений и 308 удалений

Просмотреть файл

@ -212,12 +212,15 @@ class BasePopup {
break;
case "DOMWindowCreated":
let winUtils = this.browser.contentWindow
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
for (let stylesheet of global.stylesheets) {
winUtils.addSheet(stylesheet, winUtils.AGENT_SHEET);
if (event.target === this.browser.contentDocument) {
let winUtils = this.browser.contentWindow
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
for (let stylesheet of global.stylesheets) {
winUtils.addSheet(stylesheet, winUtils.AGENT_SHEET);
}
}
break;
case "DOMWindowClose":
if (event.target === this.browser.contentWindow) {
event.preventDefault();

Просмотреть файл

@ -1188,11 +1188,13 @@ html|span.ac-tag {
height: 16px;
}
.ac-type-icon[type=switchtab] {
.ac-type-icon[type=switchtab],
.ac-type-icon[type=remotetab] {
list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab");
}
.ac-type-icon[type=switchtab][selected] {
.ac-type-icon[type=switchtab][selected],
.ac-type-icon[type=remotetab][selected] {
list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab-inverted");
}

Просмотреть файл

@ -1835,11 +1835,13 @@ html|span.ac-emphasize-text-url {
list-style-image: url("chrome://browser/skin/places/tag.png");
}
.ac-type-icon[type=switchtab] {
.ac-type-icon[type=switchtab],
.ac-type-icon[type=remotetab] {
list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab");
}
.ac-type-icon[type=switchtab][selected] {
.ac-type-icon[type=switchtab][selected],
.ac-type-icon[type=remotetab][selected] {
list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab-inverted");
}

Просмотреть файл

@ -1573,11 +1573,13 @@ html|span.ac-emphasize-text-url {
height: 16px;
}
.ac-type-icon[type=switchtab] {
.ac-type-icon[type=switchtab],
.ac-type-icon[type=remotetab] {
list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab");
}
.ac-type-icon[type=switchtab][selected] {
.ac-type-icon[type=switchtab][selected],
.ac-type-icon[type=remotetab][selected] {
list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab-inverted");
}

Просмотреть файл

@ -25,6 +25,19 @@ module.exports = createClass({
BrowserToolboxProcess.init({ addonID: target.addonID });
},
reload() {
let { client, target } = this.props;
// This function sometimes returns a partial promise that only
// implements then().
client.request({
to: target.addonActor,
type: "reload"
}).then(() => {}, error => {
throw new Error(
"Error reloading addon " + target.addonID + ": " + error);
});
},
render() {
let { target, debugDisabled } = this.props;
@ -41,7 +54,11 @@ module.exports = createClass({
className: "debug-button",
onClick: this.debug,
disabled: debugDisabled,
}, Strings.GetStringFromName("debug"))
}, Strings.GetStringFromName("debug")),
dom.button({
className: "reload-button",
onClick: this.reload
}, Strings.GetStringFromName("reload"))
);
}
});

Просмотреть файл

@ -60,17 +60,21 @@ module.exports = createClass({
},
updateAddonsList() {
AddonManager.getAllAddons(addons => {
let extensions = addons.filter(addon => addon.isDebuggable).map(addon => {
return {
name: addon.name,
icon: addon.iconURL || ExtensionIcon,
addonID: addon.id
};
});
this.props.client.listAddons()
.then(({addons}) => {
let extensions = addons.filter(addon => addon.debuggable).map(addon => {
return {
name: addon.name,
icon: addon.iconURL || ExtensionIcon,
addonID: addon.id,
addonActor: addon.actor
};
});
this.setState({ extensions });
});
this.setState({ extensions });
}, error => {
throw new Error("Client error while listing addons: " + error);
});
},
/**

Просмотреть файл

@ -14,6 +14,7 @@ support-files =
[browser_addons_debug_bootstrapped.js]
[browser_addons_debugging_initial_state.js]
[browser_addons_install.js]
[browser_addons_reload.js]
[browser_addons_toggle_debug.js]
[browser_service_workers.js]
[browser_service_workers_push.js]

Просмотреть файл

@ -22,6 +22,7 @@ add_task(function* () {
});
let { tab, document } = yield openAboutDebugging("addons");
yield waitForInitialAddonList(document);
yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,
"test-devtools");

Просмотреть файл

@ -47,6 +47,7 @@ function* testCheckboxState(testData) {
});
let { tab, document } = yield openAboutDebugging("addons");
yield waitForInitialAddonList(document);
info("Install a test addon.");
yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,

Просмотреть файл

@ -7,6 +7,7 @@ const ADDON_NAME = "test-devtools";
add_task(function* () {
let { tab, document } = yield openAboutDebugging("addons");
yield waitForInitialAddonList(document);
// Install this add-on, and verify that it appears in the about:debugging UI
yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,
@ -20,6 +21,7 @@ add_task(function* () {
add_task(function* () {
let { tab, document } = yield openAboutDebugging("addons");
yield waitForInitialAddonList(document);
// Start an observer that looks for the install error before
// actually doing the install

Просмотреть файл

@ -0,0 +1,65 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const ADDON_ID = "test-devtools@mozilla.org";
const ADDON_NAME = "test-devtools";
/**
* Returns a promise that resolves when the given add-on event is fired. The
* resolved value is an array of arguments passed for the event.
*/
function promiseAddonEvent(event) {
return new Promise(resolve => {
let listener = {
[event]: function(...args) {
AddonManager.removeAddonListener(listener);
resolve(args);
}
};
AddonManager.addAddonListener(listener);
});
}
add_task(function* () {
const { tab, document } = yield openAboutDebugging("addons");
yield waitForInitialAddonList(document);
yield installAddon(document, "addons/unpacked/install.rdf",
ADDON_NAME, ADDON_NAME);
// Retrieve the Reload button.
const names = [...document.querySelectorAll("#addons .target-name")];
const name = names.filter(element => element.textContent === ADDON_NAME)[0];
ok(name, "Found " + ADDON_NAME + " add-on in the list");
const targetElement = name.parentNode.parentNode;
const reloadButton = targetElement.querySelector(".reload-button");
ok(reloadButton, "Found its reload button");
const onDisabled = promiseAddonEvent("onDisabled");
const onEnabled = promiseAddonEvent("onEnabled");
const onBootstrapInstallCalled = new Promise(done => {
Services.obs.addObserver(function listener() {
Services.obs.removeObserver(listener, ADDON_NAME, false);
ok(true, "Add-on was installed: " + ADDON_NAME);
done();
}, ADDON_NAME, false);
});
reloadButton.click();
const [disabledAddon] = yield onDisabled;
ok(disabledAddon.name === ADDON_NAME,
"Add-on was disabled: " + disabledAddon.name);
const [enabledAddon] = yield onEnabled;
ok(enabledAddon.name === ADDON_NAME,
"Add-on was re-enabled: " + enabledAddon.name);
yield onBootstrapInstallCalled;
info("Uninstall addon installed earlier.");
yield uninstallAddon(document, ADDON_ID, ADDON_NAME);
yield closeAboutDebugging(tab);
});

Просмотреть файл

@ -20,6 +20,7 @@ add_task(function* () {
});
let { tab, document } = yield openAboutDebugging("addons");
yield waitForInitialAddonList(document);
info("Install a test addon.");
yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,

Просмотреть файл

@ -5,7 +5,8 @@
/* eslint-disable mozilla/no-cpows-in-tests */
/* exported openAboutDebugging, closeAboutDebugging, installAddon,
uninstallAddon, waitForMutation, assertHasTarget,
waitForServiceWorkerRegistered, unregisterServiceWorker */
waitForInitialAddonList, waitForServiceWorkerRegistered,
unregisterServiceWorker */
/* global sendAsyncMessage */
"use strict";
@ -155,6 +156,30 @@ function* uninstallAddon(document, addonId, addonName) {
+ names);
}
/**
* Returns a promise that will resolve when the add-on list has been updated.
*
* @param {Node} document
* @return {Promise}
*/
function waitForInitialAddonList(document) {
const addonListContainer = document.querySelector("#addons .targets");
let addonCount = addonListContainer.querySelectorAll(".target");
addonCount = addonCount ? [...addonCount].length : -1;
info("Waiting for add-ons to load. Current add-on count: " + addonCount);
// This relies on the network speed of the actor responding to the
// listAddons() request and also the speed of openAboutDebugging().
let result;
if (addonCount > 0) {
info("Actually, the add-ons have already loaded");
result = Promise.resolve();
} else {
result = waitForMutation(addonListContainer, { childList: true });
}
return result;
}
/**
* Returns a promise that will resolve after receiving a mutation matching the
* provided mutation options on the provided target.

Просмотреть файл

@ -127,9 +127,10 @@ HTMLBreadcrumbs.prototype = {
* @param {Error} err
*/
selectionGuardEnd: function(err) {
if (err === "selection-changed") {
console.warn("Asynchronous operation was aborted as selection changed.");
} else {
// If the error is selection-changed, this is expected, the selection
// changed while we were waiting for a promise to resolve, so there's no
// need to proceed with the current update, and we should be silent.
if (err !== "selection-changed") {
console.error(err);
}
},
@ -618,7 +619,7 @@ HTMLBreadcrumbs.prototype = {
return;
}
moreChildren();
}).then(null, this.selectionGuardEnd);
}).catch(this.selectionGuardEnd);
};
moreChildren();
@ -816,7 +817,7 @@ HTMLBreadcrumbs.prototype = {
this.inspector.emit("breadcrumbs-updated", this.selection.nodeFront);
doneUpdating();
});
}).then(null, err => {
}).catch(err => {
doneUpdating(this.selection.nodeFront);
this.selectionGuardEnd(err);
});

Просмотреть файл

@ -1066,7 +1066,7 @@ InspectorPanel.prototype = {
yield onMutations;
// Select the new node (this will auto-expand its parent).
this.selection.setNodeFront(nodes[0]);
this.selection.setNodeFront(nodes[0], "node-inserted");
}),
/**

Просмотреть файл

@ -550,19 +550,25 @@ MarkupView.prototype = {
},
/**
* Focus the current node selection's MarkupContainer if the selection
* happened because the user picked an element using the element picker or
* browser context menu.
* Maybe focus the current node selection's MarkupContainer depending on why
* the current node got selected.
*/
maybeFocusNewSelection: function() {
let {reason, nodeFront} = this._inspector.selection;
if (reason !== "browser-context-menu" &&
reason !== "picker-node-picked") {
return;
}
// The list of reasons that should lead to focusing the node.
let reasonsToFocus = [
// If the user picked an element with the element picker.
"picker-node-picked",
// If the user selected an element with the browser context menu.
"browser-context-menu",
// If the user added a new node by clicking in the inspector toolbar.
"node-inserted"
];
this.getContainer(nodeFront).focus();
if (reasonsToFocus.includes(reason)) {
this.getContainer(nodeFront).focus();
}
},
/**

Просмотреть файл

@ -436,6 +436,12 @@ Rule.prototype = {
let props = parseDeclarations(this.style.authoredText, true);
for (let prop of props) {
let name = prop.name;
// If the authored text has an invalid property, it will show up
// as nameless. Skip these as we don't currently have a good
// way to display them.
if (!name) {
continue;
}
// In an inherited rule, we only show inherited properties.
// However, we must keep all properties in order for rule
// rewriting to work properly. So, compute the "invisible"

Просмотреть файл

@ -121,6 +121,7 @@ skip-if = os == "mac" # Bug 1245996 : click on scrollbar not working on OSX
[browser_rules_edit-selector_05.js]
[browser_rules_edit-selector_06.js]
[browser_rules_edit-selector_07.js]
[browser_rules_edit-selector_08.js]
[browser_rules_editable-field-focus_01.js]
[browser_rules_editable-field-focus_02.js]
[browser_rules_eyedropper.js]
@ -133,6 +134,7 @@ skip-if = (os == "win" && debug) # bug 963492: win.
[browser_rules_inherited-properties_02.js]
[browser_rules_inherited-properties_03.js]
[browser_rules_inline-source-map.js]
[browser_rules_invalid.js]
[browser_rules_invalid-source-map.js]
[browser_rules_keybindings.js]
[browser_rules_keyframes-rule_01.js]

Просмотреть файл

@ -0,0 +1,71 @@
/* 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 reverting a selector edit does the right thing.
// Bug 1241046.
const TEST_URI = `
<style type="text/css">
span {
color: chartreuse;
}
</style>
<span>
<div id="testid" class="testclass">Styled Node</div>
</span>
`;
add_task(function* () {
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
let {inspector, view} = yield openRuleView();
info("Selecting the test element");
yield selectNode("#testid", inspector);
let idRuleEditor = getRuleViewRuleEditor(view, 2);
info("Focusing an existing selector name in the rule-view");
let editor = yield focusEditableField(view, idRuleEditor.selectorText);
is(inplaceEditor(idRuleEditor.selectorText), editor,
"The selector editor got focused");
info("Entering a new selector name and committing");
editor.input.value = "pre";
info("Waiting for rule view to update");
let onRuleViewChanged = once(view, "ruleview-changed");
info("Entering the commit key");
EventUtils.synthesizeKey("VK_RETURN", {});
yield onRuleViewChanged;
info("Re-focusing the selector name in the rule-view");
idRuleEditor = getRuleViewRuleEditor(view, 2);
editor = yield focusEditableField(view, idRuleEditor.selectorText);
is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
ok(getRuleViewRule(view, "pre"), "Rule with pre selector exists.");
is(getRuleViewRuleEditor(view, 2).element.getAttribute("unmatched"),
"true",
"Rule with pre does not match the current element.");
// Now change it back.
info("Re-entering original selector name and committing");
editor.input.value = "span";
info("Waiting for rule view to update");
onRuleViewChanged = once(view, "ruleview-changed");
info("Entering the commit key");
EventUtils.synthesizeKey("VK_RETURN", {});
yield onRuleViewChanged;
is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
ok(getRuleViewRule(view, "span"), "Rule with span selector exists.");
is(getRuleViewRuleEditor(view, 2).element.getAttribute("unmatched"),
"false", "Rule with span matches the current element.");
});

Просмотреть файл

@ -0,0 +1,33 @@
/* 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 an invalid property still lets us display the rule view
// Bug 1235603.
const TEST_URI = `
<style>
div {
background: #fff;
font-family: sans-serif;
url(display-table.min.htc);
}
</style>
<body>
<div id="testid" class="testclass">Styled Node</div>
</body>
`;
add_task(function* () {
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
let {inspector, view} = yield openRuleView();
yield selectNode("#testid", inspector);
is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
// Have to actually get the rule in order to ensure that the
// elements were created.
ok(getRuleViewRule(view, "div"), "Rule with div selector exists");
});

Просмотреть файл

@ -4,12 +4,12 @@
"use strict";
// Test that adding nodes does work as expected: the parent gets expanded and
// the new node gets selected.
// Test that adding nodes does work as expected: the parent gets expanded, the
// new node gets selected and the corresponding markup-container focused.
const TEST_URL = URL_ROOT + "doc_inspector_add_node.html";
add_task(function*() {
add_task(function* () {
let {inspector} = yield openInspectorForURL(TEST_URL);
info("Adding in element that has no children and is collapsed");
@ -37,13 +37,14 @@ add_task(function*() {
function* testAddNode(parentNode, inspector) {
let btn = inspector.panelDoc.querySelector("#inspector-element-add-button");
let markupWindow = inspector.markup.win;
info("Clicking on the 'add node' button and expecting a markup mutation");
info("Clicking 'add node' and expecting a markup mutation and focus event");
let onMutation = inspector.once("markupmutation");
btn.click();
let mutations = yield onMutation;
info("Expecting an inspector-updated event right after the mutation event "+
info("Expecting an inspector-updated event right after the mutation event " +
"to wait for the new node selection");
yield inspector.once("inspector-updated");
@ -60,6 +61,13 @@ function* testAddNode(parentNode, inspector) {
is(inspector.selection.nodeFront.parentNode(), parentNode,
"The new node is inside the right parent");
let focusedElement = markupWindow.document.activeElement;
let focusedContainer = focusedElement.closest(".child").container;
is(focusedContainer.node, inspector.selection.nodeFront,
"The right container is focused in the markup-view");
ok(focusedElement.classList.contains("tag"),
"The tagName part of the container is focused");
}
function collapseNode(node, inspector) {

Просмотреть файл

@ -16,6 +16,7 @@ addonDebugging.moreInfo = more info
loadTemporaryAddon = Load Temporary Add-on
extensions = Extensions
selectAddonFromFile2 = Select Manifest File or Package (.xpi)
reload = Reload
workers = Workers
serviceWorkers = Service Workers

Просмотреть файл

@ -27,6 +27,10 @@ memory.tooltip=Memory
# snapshot to disk.
snapshot.io.save=Save
# LOCALIZATION NOTE (snapshot.io.delete): The label for the link that deletes
# a snapshot
snapshot.io.delete=Delete
# LOCALIZATION NOTE (snapshot.io.save.window): The title for the window
# displayed when saving a snapshot to disk.
snapshot.io.save.window=Save Heap Snapshot

Просмотреть файл

@ -482,6 +482,9 @@ const refreshSelectedCensus = exports.refreshSelectedCensus = function (heapWork
const refreshSelectedTreeMap = exports.refreshSelectedTreeMap = function (heapWorker) {
return function*(dispatch, getState) {
let snapshot = getState().snapshots.find(s => s.selected);
if (!snapshot || snapshot.state !== states.READ) {
return;
}
// Intermediate snapshot states will get handled by the task action that is
// orchestrating them. For example, if the snapshot census's state is
@ -489,8 +492,7 @@ const refreshSelectedTreeMap = exports.refreshSelectedTreeMap = function (heapWo
// the inverted property matches the inverted state. If the snapshot is
// still in the process of being saved or read, the takeSnapshotAndCensus
// task action will follow through and ensure that a census is taken.
if (snapshot &&
(snapshot.treeMap && snapshot.treeMap.state === treeMapState.SAVED) ||
if ((snapshot.treeMap && snapshot.treeMap.state === treeMapState.SAVED) ||
!snapshot.treeMap) {
yield dispatch(takeTreeMap(heapWorker, snapshot.id));
}
@ -761,6 +763,27 @@ const clearSnapshots = exports.clearSnapshots = function (heapWorker) {
};
};
/**
* Delete a snapshot
*
* @param {HeapAnalysesClient} heapWorker
* @param {snapshotModel} snapshot
*/
const deleteSnapshot = exports.deleteSnapshot = function (heapWorker, snapshot) {
return function*(dispatch, getState) {
dispatch({ type: actions.DELETE_SNAPSHOTS_START, ids: [snapshot.id] });
try {
yield heapWorker.deleteHeapSnapshot(snapshot.path);
} catch (error) {
reportException("deleteSnapshot", error);
dispatch({ type: actions.SNAPSHOT_ERROR, id: snapshot.id, error });
}
dispatch({ type: actions.DELETE_SNAPSHOTS_END, ids: [snapshot.id] });
};
};
/**
* Expand the given node in the snapshot's census report.
*

Просмотреть файл

@ -30,6 +30,7 @@ const {
selectSnapshotAndRefresh,
takeSnapshotAndCensus,
clearSnapshots,
deleteSnapshot,
fetchImmediatelyDominated,
expandCensusNode,
collapseCensusNode,
@ -210,6 +211,7 @@ const MemoryApp = createClass({
itemComponent: SnapshotListItem,
items: snapshots,
onSave: snapshot => dispatch(pickFileAndExportSnapshot(snapshot)),
onDelete: snapshot => dispatch(deleteSnapshot(heapWorker, snapshot)),
onClick: onClickSnapshotListItem,
diffing,
}),

Просмотреть файл

@ -26,12 +26,13 @@ const SnapshotListItem = module.exports = createClass({
propTypes: {
onClick: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
item: snapshotModel.isRequired,
index: PropTypes.number.isRequired,
},
render() {
let { index, item: snapshot, onClick, onSave, diffing } = this.props;
let { index, item: snapshot, onClick, onSave, onDelete, diffing } = this.props;
let className = `snapshot-list-item ${snapshot.selected ? " selected" : ""}`;
let statusText = getStatusText(snapshot.state);
let wantThrobber = !!statusText;
@ -88,13 +89,20 @@ const SnapshotListItem = module.exports = createClass({
let saveLink = !snapshot.path ? void 0 : dom.a({
onClick: () => onSave(snapshot),
className: "save",
}, L10N.getFormatStr("snapshot.io.save"));
}, L10N.getStr("snapshot.io.save"));
let deleteButton = !snapshot.path ? void 0 : dom.button({
onClick: () => onDelete(snapshot),
className: "devtools-button delete",
title: L10N.getStr("snapshot.io.delete")
});
return (
dom.li({ className, onClick },
dom.span({ className: `snapshot-title ${wantThrobber ? " devtools-throbber" : ""}` },
checkbox,
title
title,
deleteButton
),
dom.span({ className: "snapshot-info" },
details,

Просмотреть файл

@ -12,7 +12,9 @@ support-files =
[test_Heap_03.html]
[test_Heap_04.html]
[test_Heap_05.html]
[test_List_01.html]
[test_ShortestPaths_01.html]
[test_ShortestPaths_02.html]
[test_SnapshotListItem_01.html]
[test_Toolbar_01.html]
[test_TreeMap_01.html]

Просмотреть файл

@ -53,6 +53,8 @@ var DominatorTreeComponent = React.createFactory(require("devtools/client/memory
var DominatorTreeItem = React.createFactory(require("devtools/client/memory/components/dominator-tree-item"));
var ShortestPaths = React.createFactory(require("devtools/client/memory/components/shortest-paths"));
var TreeMap = React.createFactory(require("devtools/client/memory/components/tree-map"));
var SnapshotListItem = React.createFactory(require("devtools/client/memory/components/snapshot-list-item"));
var List = React.createFactory(require("devtools/client/memory/components/list"));
var Toolbar = React.createFactory(require("devtools/client/memory/components/toolbar"));
// All tests are asynchronous.
@ -163,6 +165,43 @@ var TEST_SHORTEST_PATHS_PROPS = Object.freeze({
}),
});
var TEST_SNAPSHOT = Object.freeze({
id: 1337,
selected: true,
path: "/fake/path/to/snapshot",
census: Object.freeze({
report: Object.freeze({
objects: Object.freeze({ count: 4, bytes: 400 }),
scripts: Object.freeze({ count: 3, bytes: 300 }),
strings: Object.freeze({ count: 2, bytes: 200 }),
other: Object.freeze({ count: 1, bytes: 100 }),
}),
display: Object.freeze({
displayName: "Test Display",
tooltip: "Test display tooltup",
inverted: false,
breakdown: Object.freeze({
by: "coarseType",
objects: Object.freeze({ by: "count", count: true, bytes: true }),
scripts: Object.freeze({ by: "count", count: true, bytes: true }),
strings: Object.freeze({ by: "count", count: true, bytes: true }),
other: Object.freeze({ by: "count", count: true, bytes: true }),
}),
}),
state: censusState.SAVED,
inverted: false,
filter: null,
expanded: new Set(),
focused: null,
parentMap: Object.freeze(Object.create(null))
}),
dominatorTree: TEST_DOMINATOR_TREE,
error: null,
imported: false,
creationTime: 0,
state: snapshotState.READ,
});
var TEST_HEAP_PROPS = Object.freeze({
onSnapshotClick: noop,
onLoadMoreSiblings: noop,
@ -175,42 +214,7 @@ var TEST_HEAP_PROPS = Object.freeze({
onViewSourceInDebugger: noop,
diffing: null,
view: { state: viewState.CENSUS, },
snapshot: Object.freeze({
id: 1337,
selected: true,
path: "/fake/path/to/snapshot",
census: Object.freeze({
report: Object.freeze({
objects: Object.freeze({ count: 4, bytes: 400 }),
scripts: Object.freeze({ count: 3, bytes: 300 }),
strings: Object.freeze({ count: 2, bytes: 200 }),
other: Object.freeze({ count: 1, bytes: 100 }),
}),
display: Object.freeze({
displayName: "Test Display",
tooltip: "Test display tooltup",
inverted: false,
breakdown: Object.freeze({
by: "coarseType",
objects: Object.freeze({ by: "count", count: true, bytes: true }),
scripts: Object.freeze({ by: "count", count: true, bytes: true }),
strings: Object.freeze({ by: "count", count: true, bytes: true }),
other: Object.freeze({ by: "count", count: true, bytes: true }),
}),
}),
state: censusState.SAVED,
inverted: false,
filter: null,
expanded: new Set(),
focused: null,
parentMap: Object.freeze(Object.create(null))
}),
dominatorTree: TEST_DOMINATOR_TREE,
error: null,
imported: false,
creationTime: 0,
state: snapshotState.READ,
}),
snapshot: TEST_SNAPSHOT,
sizes: Object.freeze({ shortestPathsSize: .5 }),
onShortestPathsResize: noop,
});
@ -285,6 +289,14 @@ var TEST_TREE_MAP_PROPS = Object.freeze({
})
});
var TEST_SNAPSHOT_LIST_ITEM_PROPS = Object.freeze({
onClick: noop,
onSave: noop,
onDelete: noop,
item: TEST_SNAPSHOT,
index: 1234,
});
function onNextAnimationFrame(fn) {
return () =>
requestAnimationFrame(() =>

Просмотреть файл

@ -0,0 +1,74 @@
<!DOCTYPE HTML>
<html>
<!--
Test to verify the delete button calls the onDelete handler for an item
-->
<head>
<meta charset="utf-8">
<title>Tree component test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<div id="container"></div>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
const container = document.getElementById("container");
let deletedSnapshots = [];
let snapshots = [ TEST_SNAPSHOT, TEST_SNAPSHOT, TEST_SNAPSHOT ]
.map((snapshot, index) => immutableUpdate(snapshot, {
index: snapshot.index + index
}));
yield renderComponent(
List({
itemComponent: SnapshotListItem,
onClick: noop,
onDelete: (item) => deletedSnapshots.push(item),
items: snapshots
}),
container
);
let deleteButtons = container.querySelectorAll('.delete');
is(container.querySelectorAll('.snapshot-list-item').length, 3,
"There are 3 list items\n");
is(deletedSnapshots.length, 0,
"Not snapshots have been deleted\n");
deleteButtons[1].click();
is(deletedSnapshots.length, 1, "One snapshot was deleted\n");
is(deletedSnapshots[0], snapshots[1],
"Deleted snapshot was added to the deleted list\n");
deleteButtons[0].click();
is(deletedSnapshots.length, 2, "Two snapshots were deleted\n");
is(deletedSnapshots[1], snapshots[0],
"Deleted snapshot was added to the deleted list\n");
deleteButtons[2].click();
is(deletedSnapshots.length, 3, "Three snapshots were deleted\n");
is(deletedSnapshots[2], snapshots[2],
"Deleted snapshot was added to the deleted list\n");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,53 @@
<!DOCTYPE HTML>
<html>
<!--
Test to verify that the delete button only shows up for a snapshot when it has a
path.
-->
<head>
<meta charset="utf-8">
<title>Tree component test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<div id="container"></div>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
const container = document.getElementById("container");
yield renderComponent(
SnapshotListItem(TEST_SNAPSHOT_LIST_ITEM_PROPS),
container
);
ok(container.querySelector('.delete'),
"Should have delete button when there is a path");
const pathlessProps = immutableUpdate(
TEST_SNAPSHOT_LIST_ITEM_PROPS,
{item: immutableUpdate(TEST_SNAPSHOT, {path: null})}
);
yield renderComponent(
SnapshotListItem(pathlessProps),
container
);
ok(!container.querySelector('.delete'),
"No delete button should be found if there is no path\n");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -5,6 +5,7 @@ support-files =
storage-cache-error.html
storage-complex-values.html
storage-cookies.html
storage-empty-objectstores.html
storage-listings.html
storage-localstorage.html
storage-overflow.html
@ -21,11 +22,12 @@ support-files =
[browser_storage_cookies_edit.js]
[browser_storage_cookies_edit_keyboard.js]
[browser_storage_cookies_tab_navigation.js]
[browser_storage_dynamic_updates.js]
[browser_storage_localstorage_edit.js]
[browser_storage_delete.js]
[browser_storage_delete_all.js]
[browser_storage_delete_tree.js]
[browser_storage_dynamic_updates.js]
[browser_storage_empty_objectstores.js]
[browser_storage_localstorage_edit.js]
[browser_storage_overflow.js]
[browser_storage_search.js]
skip-if = os == "linux" && e10s # Bug 1240804 - unhandled promise rejections

Просмотреть файл

@ -0,0 +1,77 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Basic test to assert that the storage tree and table corresponding to each
// item in the storage tree is correctly displayed.
"use strict";
// Entries that should be present in the tree for this test
// Format for each entry in the array:
// [
// ["path", "to", "tree", "item"],
// - The path to the tree item to click formed by id of each item
// ["key_value1", "key_value2", ...]
// - The value of the first (unique) column for each row in the table
// corresponding to the tree item selected.
// ]
// These entries are formed by the cookies, local storage, session storage and
// indexedDB entries created in storage-listings.html,
// storage-secured-iframe.html and storage-unsecured-iframe.html
const storeItems = [
[["indexedDB", "http://test1.example.org"],
["idb1", "idb2"]],
[["indexedDB", "http://test1.example.org", "idb1"],
["obj1", "obj2"]],
[["indexedDB", "http://test1.example.org", "idb2"],
[]],
[["indexedDB", "http://test1.example.org", "idb1", "obj1"],
[1, 2, 3]],
[["indexedDB", "http://test1.example.org", "idb1", "obj2"],
[1]]
];
/**
* Test that the desired number of tree items are present
*/
function testTree() {
let doc = gPanelWindow.document;
for (let [item] of storeItems) {
ok(doc.querySelector(`[data-id='${JSON.stringify(item)}']`),
`Tree item ${item} should be present in the storage tree`);
}
}
/**
* Test that correct table entries are shown for each of the tree item
*/
let testTables = function* () {
let doc = gPanelWindow.document;
// Expand all nodes so that the synthesized click event actually works
gUI.tree.expandAll();
// Click the tree items and wait for the table to be updated
for (let [item, ids] of storeItems) {
yield selectTreeItem(item);
// Check whether correct number of items are present in the table
is(doc.querySelectorAll(
".table-widget-wrapper:first-of-type .table-widget-cell"
).length, ids.length, "Number of items in table is correct");
// Check if all the desired items are present in the table
for (let id of ids) {
ok(doc.querySelector(".table-widget-cell[data-id='" + id + "']"),
`Table item ${id} should be present`);
}
}
};
add_task(function* () {
yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-empty-objectstores.html");
testTree();
yield testTables();
yield finishTests();
});

Просмотреть файл

@ -0,0 +1,62 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for proper listing indexedDB databases with no object stores</title>
</head>
<body>
<script type="application/javascript;version=1.7">
window.setup = function* () {
let request = indexedDB.open("idb1", 1);
let db = yield new Promise((resolve, reject) => {
request.onerror = e => reject(Error("error opening db connection"));
request.onupgradeneeded = event => {
let db = event.target.result;
let store1 = db.createObjectStore("obj1", { keyPath: "id" });
store1.createIndex("name", "name", { unique: false });
store1.createIndex("email", "email", { unique: true });
let store2 = db.createObjectStore("obj2", { keyPath: "id2" });
store1.transaction.oncomplete = () => resolve(db);
};
});
yield new Promise(resolve => request.onsuccess = resolve);
let transaction = db.transaction(["obj1", "obj2"], "readwrite");
let store1 = transaction.objectStore("obj1");
let store2 = transaction.objectStore("obj2");
store1.add({id: 1, name: "foo", email: "foo@bar.com"});
store1.add({id: 2, name: "foo2", email: "foo2@bar.com"});
store1.add({id: 3, name: "foo2", email: "foo3@bar.com"});
store2.add({id2: 1, name: "foo", email: "foo@bar.com", extra: "baz"});
yield new Promise(resolve => transaction.oncomplete = resolve);
db.close();
request = indexedDB.open("idb2", 1);
let db2 = yield new Promise((resolve, reject) => {
request.onerror = e => reject(Error("error opening db2 connection"));
request.onupgradeneeded = event => resolve(event.target.result);
});
yield new Promise(resolve => request.onsuccess = resolve);
db2.close();
dump("added indexedDB items from main page\n");
};
window.clear = function* () {
for (let dbName of ["idb1", "idb2"]) {
yield new Promise(resolve => {
indexedDB.deleteDatabase(dbName).onsuccess = resolve;
});
}
dump("removed indexedDB items from main page\n");
};
</script>
</body>
</html>

Просмотреть файл

@ -197,11 +197,32 @@ html, body, #app, #memory-tool {
font-size: 90%;
}
.snapshot-list-item .snapshot-title {
display: flex;
justify-content: space-between;
}
.snapshot-list-item .save {
text-decoration: underline;
cursor: pointer;
}
.snapshot-list-item .delete {
cursor: pointer;
position: relative;
min-height: 1em;
min-width: 1.3em;
}
.theme-light .snapshot-list-item.selected .delete {
filter: invert(100%);
}
.snapshot-list-item .delete::before {
background-image: url("chrome://devtools/skin/images/close.svg");
background-position: 0.2em 0;
}
.snapshot-list-item > .snapshot-title {
margin-bottom: 14px;
}

Просмотреть файл

@ -83,6 +83,7 @@ BrowserAddonActor.prototype = {
id: this.id,
name: this._addon.name,
url: this.url,
iconURL: this._addon.iconURL,
debuggable: this._addon.isDebuggable,
consoleActor: this._consoleActor.actorID,
@ -155,6 +156,13 @@ BrowserAddonActor.prototype = {
return { type: "detached" };
},
onReload: function BAA_onReload() {
return this._addon.reload()
.then(() => {
return {}; // send an empty response
});
},
preNest: function() {
let e = Services.wm.getEnumerator(null);
while (e.hasMoreElements()) {
@ -249,7 +257,8 @@ BrowserAddonActor.prototype = {
BrowserAddonActor.prototype.requestTypes = {
"attach": BrowserAddonActor.prototype.onAttach,
"detach": BrowserAddonActor.prototype.onDetach
"detach": BrowserAddonActor.prototype.onDetach,
"reload": BrowserAddonActor.prototype.onReload
};
/**

Просмотреть файл

@ -1536,9 +1536,13 @@ StorageActors.createActor({
getNamesForHost: function(host) {
let names = [];
for (let [dbName, metaData] of this.hostVsStores.get(host)) {
for (let objectStore of metaData.objectStores.keys()) {
names.push(JSON.stringify([dbName, objectStore]));
for (let [dbName, {objectStores}] of this.hostVsStores.get(host)) {
if (objectStores.size) {
for (let objectStore of objectStores.keys()) {
names.push(JSON.stringify([dbName, objectStore]));
}
} else {
names.push(JSON.stringify([dbName]));
}
}
return names;
@ -1634,7 +1638,7 @@ StorageActors.createActor({
return null;
}
if (item.indexes) {
if ("indexes" in item) {
// Object store meta data
return {
objectStore: item.name,
@ -1643,7 +1647,7 @@ StorageActors.createActor({
indexes: item.indexes
};
}
if (item.objectStores) {
if ("objectStores" in item) {
// DB meta data
return {
db: item.name,
@ -2240,7 +2244,8 @@ var StorageActor = exports.StorageActor = protocol.ActorClass({
let origin = win.document
.nodePrincipal
.originNoSuffix;
if (origin === host) {
let url = win.document.URL;
if (origin === host || url === host) {
return win;
}
}

Просмотреть файл

@ -742,6 +742,30 @@ var PageStyleActor = protocol.ActorClass({
return rules;
},
/**
* Given a node and a CSS rule, walk up the DOM looking for a
* matching element rule. Return an array of all found entries, in
* the form generated by _getAllElementRules. Note that this will
* always return an array of either zero or one element.
*
* @param {NodeActor} node the node
* @param {CSSStyleRule} filterRule the rule to filter for
* @return {Array} array of zero or one elements; if one, the element
* is the entry as returned by _getAllElementRules.
*/
findEntryMatchingRule: function(node, filterRule) {
const options = {matchedSelectors: true, inherited: true};
let entries = [];
let parent = this.walker.parentNode(node);
while (parent && parent.rawNode.nodeType != Ci.nsIDOMNode.DOCUMENT_NODE) {
entries = entries.concat(this._getAllElementRules(parent, parent,
options));
parent = this.walker.parentNode(parent);
}
return entries.filter(entry => entry.rule.rawRule === filterRule);
},
/**
* Helper function for getApplied that fetches a set of style properties that
* apply to the given node and associated rules
@ -752,7 +776,7 @@ var PageStyleActor = protocol.ActorClass({
* 'ua': Include properties from user and user-agent sheets.
* Default value is 'ua'
* `inherited`: Include styles inherited from parent nodes.
* `matchedSeletors`: Include an array of specific selectors that
* `matchedSelectors`: Include an array of specific selectors that
* caused this rule to match its node.
* @param array entries
* List of appliedstyle objects that lists the rules that apply to the
@ -970,7 +994,7 @@ var PageStyleActor = protocol.ActorClass({
* Helper function for adding a new rule and getting its applied style
* properties
* @param NodeActor node
* @param CSSStyleRUle rule
* @param CSSStyleRule rule
* @returns Object containing its applied style properties
*/
getNewAppliedProps: function(node, rule) {
@ -1626,11 +1650,9 @@ var StyleRuleActor = protocol.ActorClass({
* matches the current selected element
*/
modifySelector2: method(function(node, value, editAuthored = false) {
let ruleProps = null;
if (this.type === ELEMENT_STYLE ||
this.rawRule.selectorText === value) {
return { ruleProps, isMatching: true };
return { ruleProps: null, isMatching: true };
}
let selectorPromise = this._addNewSelector(value, editAuthored);
@ -1647,17 +1669,19 @@ var StyleRuleActor = protocol.ActorClass({
}
return selectorPromise.then((newCssRule) => {
if (newCssRule) {
ruleProps = this.pageStyle.getNewAppliedProps(node, newCssRule);
}
// Determine if the new selector value matches the current
// selected element
let ruleProps = null;
let isMatching = false;
try {
isMatching = node.rawNode.matches(value);
} catch (e) {
// This fails when value is an invalid selector.
if (newCssRule) {
let ruleEntry = this.pageStyle.findEntryMatchingRule(node, newCssRule);
if (ruleEntry.length === 1) {
isMatching = true;
ruleProps =
this.pageStyle.getAppliedProps(node, ruleEntry,
{ matchedSelectors: true });
} else {
ruleProps = this.pageStyle.getNewAppliedProps(node, newCssRule);
}
}
return { ruleProps, isMatching };

Просмотреть файл

@ -3459,8 +3459,15 @@ public class BrowserApp extends GeckoApp
if (itemId == R.id.bookmark) {
tab = Tabs.getInstance().getSelectedTab();
if (tab != null) {
final String extra;
if (AboutPages.isAboutReader(tab.getURL())) {
extra = "bookmark_reader";
} else {
extra = "bookmark";
}
if (item.isChecked()) {
Telemetry.sendUIEvent(TelemetryContract.Event.UNSAVE, TelemetryContract.Method.MENU, "bookmark");
Telemetry.sendUIEvent(TelemetryContract.Event.UNSAVE, TelemetryContract.Method.MENU, extra);
tab.removeBookmark();
item.setTitle(resolveBookmarkTitleID(false));
if (Versions.feature11Plus) {
@ -3468,7 +3475,7 @@ public class BrowserApp extends GeckoApp
item.setIcon(resolveBookmarkIconID(false));
}
} else {
Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.MENU, "bookmark");
Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.MENU, extra);
tab.addBookmark();
item.setTitle(resolveBookmarkTitleID(true));
if (Versions.feature11Plus) {

Просмотреть файл

@ -380,11 +380,20 @@ public abstract class HomeFragment extends Fragment {
switch (mType) {
case BOOKMARKS:
Telemetry.sendUIEvent(TelemetryContract.Event.UNSAVE, TelemetryContract.Method.CONTEXT_MENU, "bookmark");
SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(mContext);
final boolean isReaderViewPage = rch.isURLCached(mUrl);
final String extra;
if (isReaderViewPage) {
extra = "bookmark_reader";
} else {
extra = "bookmark";
}
Telemetry.sendUIEvent(TelemetryContract.Event.UNSAVE, TelemetryContract.Method.CONTEXT_MENU, extra);
mDB.removeBookmarksWithURL(cr, mUrl);
SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(mContext);
if (rch.isURLCached(mUrl)) {
if (isReaderViewPage) {
ReadingListHelper.removeCachedReaderItem(mUrl, mContext);
}

Просмотреть файл

@ -13,7 +13,6 @@
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/firstrun_min_height"
android:background="@color/about_page_header_grey"
android:gravity="center_horizontal"
android:orientation="vertical">

Просмотреть файл

@ -207,4 +207,14 @@ this.Async = {
query.executeAsync(storageCallback);
return Async.waitForSyncCallback(storageCallback.syncCb);
},
promiseSpinningly(promise) {
let cb = Async.makeSpinningCallback();
promise.then(result => {
cb(null, result);
}, err => {
cb(err || new Error("Promise rejected without explicit error"));
});
return cb.wait();
},
};

Просмотреть файл

@ -12,8 +12,11 @@ Cu.importGlobalProperties(['fetch']);
const PREF_KINTO_CHANGES_PATH = "services.kinto.changes.path";
const PREF_KINTO_BASE = "services.kinto.base";
const PREF_KINTO_BUCKET = "services.kinto.bucket";
const PREF_KINTO_LAST_UPDATE = "services.kinto.last_update_seconds";
const PREF_KINTO_LAST_ETAG = "services.kinto.last_etag";
const PREF_KINTO_CLOCK_SKEW_SECONDS = "services.kinto.clock_skew_seconds";
const PREF_KINTO_ONECRL_COLLECTION = "services.kinto.onecrl.collection";
const kintoClients = {
};
@ -21,6 +24,7 @@ const kintoClients = {
// This is called by the ping mechanism.
// returns a promise that rejects if something goes wrong
this.checkVersions = function() {
return Task.spawn(function *() {
// Fetch a versionInfo object that looks like:
// {"data":[
@ -33,25 +37,52 @@ this.checkVersions = function() {
// Right now, we only use the collection name and the last modified info
let kintoBase = Services.prefs.getCharPref(PREF_KINTO_BASE);
let changesEndpoint = kintoBase + Services.prefs.getCharPref(PREF_KINTO_CHANGES_PATH);
let blocklistsBucket = Services.prefs.getCharPref(PREF_KINTO_BUCKET);
let response = yield fetch(changesEndpoint);
// Use ETag to obtain a `304 Not modified` when no change occurred.
const headers = {};
if (Services.prefs.prefHasUserValue(PREF_KINTO_LAST_ETAG)) {
const lastEtag = Services.prefs.getCharPref(PREF_KINTO_LAST_ETAG);
if (lastEtag) {
headers["If-None-Match"] = lastEtag;
}
}
let response = yield fetch(changesEndpoint, {headers});
let versionInfo;
// No changes since last time. Go on with empty list of changes.
if (response.status == 304) {
versionInfo = {data: []};
} else {
versionInfo = yield response.json();
}
// If the server is failing, the JSON response might not contain the
// expected data (e.g. error response - Bug 1259145)
if (!versionInfo.hasOwnProperty("data")) {
throw new Error("Polling for changes failed.");
}
// Record new update time and the difference between local and server time
let serverTimeMillis = Date.parse(response.headers.get("Date"));
let clockDifference = Math.abs(Date.now() - serverTimeMillis) / 1000;
Services.prefs.setIntPref(PREF_KINTO_LAST_UPDATE, serverTimeMillis / 1000);
Services.prefs.setIntPref(PREF_KINTO_CLOCK_SKEW_SECONDS, clockDifference);
let versionInfo = yield response.json();
Services.prefs.setIntPref(PREF_KINTO_LAST_UPDATE, serverTimeMillis / 1000);
let firstError;
for (let collectionInfo of versionInfo.data) {
// Skip changes that don't concern configured blocklist bucket.
if (collectionInfo.bucket != blocklistsBucket) {
continue;
}
let collection = collectionInfo.collection;
let kintoClient = kintoClients[collection];
if (kintoClient && kintoClient.maybeSync) {
let lastModified = 0;
if (collectionInfo.last_modified) {
lastModified = collectionInfo.last_modified
lastModified = collectionInfo.last_modified;
}
try {
yield kintoClient.maybeSync(lastModified, serverTimeMillis);
@ -66,6 +97,12 @@ this.checkVersions = function() {
// cause the promise to reject by throwing the first observed error
throw firstError;
}
// Save current Etag for next poll.
if (response.headers.has("ETag")) {
const currentEtag = response.headers.get("ETag");
Services.prefs.setCharPref(PREF_KINTO_LAST_ETAG, currentEtag);
}
});
};
@ -75,6 +112,5 @@ this.addTestKintoClient = function(name, kintoClient) {
};
// Add the various things that we know want updates
kintoClients.certificates =
Cu.import("resource://services-common/KintoCertificateBlocklist.js", {})
.OneCRLClient;
const KintoBlocklist = Cu.import("resource://services-common/KintoCertificateBlocklist.js", {});
kintoClients[Services.prefs.getCharPref(PREF_KINTO_ONECRL_COLLECTION)] = KintoBlocklist.OneCRLClient;

Просмотреть файл

@ -8,6 +8,7 @@ var server;
const PREF_KINTO_BASE = "services.kinto.base";
const PREF_LAST_UPDATE = "services.kinto.last_update_seconds";
const PREF_LAST_ETAG = "services.kinto.last_etag";
const PREF_CLOCK_SKEW_SECONDS = "services.kinto.clock_skew_seconds";
// Check to ensure maybeSync is called with correct values when a changes
@ -31,7 +32,7 @@ add_task(function* test_check_maybeSync(){
response.setHeader(headerElements[0], headerElements[1].trimLeft());
}
// set the
// set the server date
response.setHeader("Date", (new Date(2000)).toUTCString());
response.write(sampled.responseBody);
@ -47,19 +48,20 @@ add_task(function* test_check_maybeSync(){
`http://localhost:${server.identity.primaryPort}/v1`);
// set some initial values so we can check these are updated appropriately
Services.prefs.setIntPref("services.kinto.last_update", 0);
Services.prefs.setIntPref("services.kinto.clock_difference", 0);
Services.prefs.setIntPref(PREF_LAST_UPDATE, 0);
Services.prefs.setIntPref(PREF_CLOCK_SKEW_SECONDS, 0);
Services.prefs.clearUserPref(PREF_LAST_ETAG);
let startTime = Date.now();
let updater = Cu.import("resource://services-common/kinto-updater.js");
let syncPromise = new Promise(function(resolve, reject) {
let updater = Cu.import("resource://services-common/kinto-updater.js");
// add a test kinto client that will respond to lastModified information
// for a collection called 'test-collection'
updater.addTestKintoClient("test-collection", {
"maybeSync": function(lastModified, serverTime){
// ensire the lastModified and serverTime values are as expected
maybeSync(lastModified, serverTime) {
do_check_eq(lastModified, 1000);
do_check_eq(serverTime, 2000);
resolve();
@ -80,6 +82,44 @@ add_task(function* test_check_maybeSync(){
// we previously set the serverTime to 2 (seconds past epoch)
do_check_eq(clockDifference <= endTime / 1000
&& clockDifference >= Math.floor(startTime / 1000) - 2, true);
// Last timestamp was saved. An ETag header value is a quoted string.
let lastEtag = Services.prefs.getCharPref(PREF_LAST_ETAG);
do_check_eq(lastEtag, "\"1100\"");
// Simulate a poll with up-to-date collection.
Services.prefs.setIntPref(PREF_LAST_UPDATE, 0);
// If server has no change, a 304 is received, maybeSync() is not called.
updater.addTestKintoClient("test-collection", {
maybeSync: () => {throw new Error("Should not be called");}
});
yield updater.checkVersions();
// Last update is overwritten
do_check_eq(Services.prefs.getIntPref(PREF_LAST_UPDATE), 2);
// Simulate a server error.
function simulateErrorResponse (request, response) {
response.setHeader("Date", (new Date(3000)).toUTCString());
response.setHeader("Content-Type", "application/json; charset=UTF-8");
response.write(JSON.stringify({
code: 503,
errno: 999,
error: "Service Unavailable",
}));
response.setStatusLine(null, 503, "Service Unavailable");
}
server.registerPathHandler(changesPath, simulateErrorResponse);
// checkVersions() fails with adequate error.
let error;
try {
yield updater.checkVersions();
} catch (e) {
error = e;
}
do_check_eq(error.message, "Polling for changes failed.");
// When an error occurs, last update was not overwritten (see Date header above).
do_check_eq(Services.prefs.getIntPref(PREF_LAST_UPDATE), 2);
});
function run_test() {
@ -99,12 +139,29 @@ function getSampleResponse(req, port) {
const responses = {
"GET:/v1/buckets/monitor/collections/changes/records?": {
"sampleHeaders": [
"Content-Type: application/json; charset=UTF-8"
"Content-Type: application/json; charset=UTF-8",
"ETag: \"1100\""
],
"status": {status: 200, statusText: "OK"},
"responseBody": JSON.stringify({"data":[{"host":"localhost","last_modified":1000,"bucket":"blocklists","id":"330a0c5f-fadf-ff0b-40c8-4eb0d924ff6a","collection":"test-collection"}]})
"responseBody": JSON.stringify({"data": [{
"host": "localhost",
"last_modified": 1100,
"bucket": "blocklists:aurora",
"id": "330a0c5f-fadf-ff0b-40c8-4eb0d924ff6a",
"collection": "test-collection"
}, {
"host": "localhost",
"last_modified": 1000,
"bucket": "blocklists",
"id": "254cbb9e-6888-4d9f-8e60-58b74faa8778",
"collection": "test-collection"
}]})
}
};
if (req.hasHeader("if-none-match") && req.getHeader("if-none-match", "") == "\"1100\"")
return {sampleHeaders: [], status: {status: 304, statusText: "Not Modified"}, responseBody: ""};
return responses[`${req.method}:${req.path}?${req.queryString}`] ||
responses[req.method];
}

Просмотреть файл

@ -272,69 +272,69 @@ BookmarksEngine.prototype = {
_guidMapFailed: false,
_buildGUIDMap: function _buildGUIDMap() {
let guidMap = {};
for (let guid in this._store.getAllIDs()) {
// Figure out with which key to store the mapping.
let key;
let id = this._store.idForGUID(guid);
let itemType;
try {
itemType = PlacesUtils.bookmarks.getItemType(id);
} catch (ex) {
this._log.warn("Deleting invalid bookmark record with id", id);
try {
PlacesUtils.bookmarks.removeItem(id);
} catch (ex) {
this._log.warn("Failed to delete invalid bookmark", ex);
let tree = Async.promiseSpinningly(PlacesUtils.promiseBookmarksTree("", {
includeItemIds: true
}));
function* walkBookmarksTree(tree, parent=null) {
if (tree) {
// Skip root node
if (parent) {
yield [tree, parent];
}
if (tree.children) {
for (let child of tree.children) {
yield* walkBookmarksTree(child, tree);
}
}
continue;
}
switch (itemType) {
case PlacesUtils.bookmarks.TYPE_BOOKMARK:
}
// Smart bookmarks map to their annotation value.
let queryId;
try {
queryId = PlacesUtils.annotations.getItemAnnotation(
id, SMART_BOOKMARKS_ANNO);
} catch(ex) {}
function* walkBookmarksRoots(tree, rootGUIDs) {
for (let guid of rootGUIDs) {
let id = kSpecialIds.specialIdForGUID(guid, false);
let bookmarkRoot = id === null ? null :
tree.children.find(child => child.id === id);
if (bookmarkRoot === null) {
continue;
}
yield* walkBookmarksTree(bookmarkRoot, tree);
}
}
if (queryId) {
key = "q" + queryId;
let rootsToWalk = kSpecialIds.guids.filter(guid =>
guid !== 'places' && guid !== 'tags');
for (let [node, parent] of walkBookmarksRoots(tree, rootsToWalk)) {
let {guid, id, type: placeType} = node;
guid = kSpecialIds.specialGUIDForId(id) || guid;
let key;
switch (placeType) {
case PlacesUtils.TYPE_X_MOZ_PLACE:
// Bookmark
let query = null;
if (node.annos && node.uri.startsWith("place:")) {
query = node.annos.find(({name}) => name === SMART_BOOKMARKS_ANNO);
}
if (query && query.value) {
key = "q" + query.value;
} else {
let uri;
try {
uri = PlacesUtils.bookmarks.getBookmarkURI(id);
} catch (ex) {
// Bug 1182366 - NS_ERROR_MALFORMED_URI here stops bookmarks sync.
// Try and get the string value of the URL for diagnostic purposes.
let url = this._getStringUrlForId(id);
this._log.warn(`Deleting bookmark with invalid URI. url="${url}", id=${id}`);
try {
PlacesUtils.bookmarks.removeItem(id);
} catch (ex) {
this._log.warn("Failed to delete invalid bookmark", ex);
}
continue;
}
key = "b" + uri.spec + ":" + PlacesUtils.bookmarks.getItemTitle(id);
key = "b" + node.uri + ":" + node.title;
}
break;
case PlacesUtils.bookmarks.TYPE_FOLDER:
key = "f" + PlacesUtils.bookmarks.getItemTitle(id);
case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER:
// Folder
key = "f" + node.title;
break;
case PlacesUtils.bookmarks.TYPE_SEPARATOR:
key = "s" + PlacesUtils.bookmarks.getItemIndex(id);
case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR:
// Separator
key = "s" + node.index;
break;
default:
this._log.error("Unknown place type: '"+placeType+"'");
continue;
}
// The mapping is on a per parent-folder-name basis.
let parent = PlacesUtils.bookmarks.getFolderIdForItem(id);
if (parent <= 0)
continue;
let parentName = PlacesUtils.bookmarks.getItemTitle(parent);
let parentName = parent.title;
if (guidMap[parentName] == null)
guidMap[parentName] = {};

Просмотреть файл

@ -396,7 +396,9 @@ add_test(function test_bookmark_guidMap_fail() {
engine.lastSync = 1; // So we don't back up.
// Make building the GUID map fail.
store.getAllIDs = function () { throw "Nooo"; };
let pbt = PlacesUtils.promiseBookmarksTree;
PlacesUtils.promiseBookmarksTree = function() { return Promise.reject("Nooo"); };
// Ensure that we throw when accessing _guidMap.
engine._syncStartup();
@ -422,6 +424,7 @@ add_test(function test_bookmark_guidMap_fail() {
}
do_check_eq(err, "Nooo");
PlacesUtils.promiseBookmarksTree = pbt;
server.stop(run_next_test);
});

Просмотреть файл

@ -12,46 +12,6 @@ var engine = Service.engineManager.get("bookmarks");
var store = engine._store;
var tracker = engine._tracker;
// Return a promise resolved when the specified message is written to the
// bookmarks engine log.
function promiseLogMessage(messagePortion) {
return new Promise(resolve => {
let appender;
let log = Log.repository.getLogger("Sync.Engine.Bookmarks");
function TestAppender() {
Log.Appender.call(this);
}
TestAppender.prototype = Object.create(Log.Appender.prototype);
TestAppender.prototype.doAppend = function(message) {
if (message.indexOf(messagePortion) >= 0) {
log.removeAppender(appender);
resolve();
}
};
TestAppender.prototype.level = Log.Level.Debug;
appender = new TestAppender();
log.addAppender(appender);
});
}
// Returns a promise that resolves if the specified ID does *not* exist and
// rejects if it does.
function promiseNoItem(itemId) {
return new Promise((resolve, reject) => {
try {
PlacesUtils.bookmarks.getFolderIdForItem(itemId);
reject("fetching the item didn't fail");
} catch (ex) {
if (ex.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
resolve("item doesn't exist");
} else {
reject("unexpected exception: " + ex);
}
}
});
}
add_task(function* test_ignore_invalid_uri() {
_("Ensure that we don't die with invalid bookmarks.");
@ -70,13 +30,9 @@ add_task(function* test_ignore_invalid_uri() {
{ id: bmid, url: "<invalid url>" });
}));
// DB is now "corrupt" - setup a log appender to capture what we log.
let promiseMessage = promiseLogMessage('Deleting bookmark with invalid URI. url="<invalid url>"');
// This should work and log our invalid id.
// Ensure that this doesn't throw even though the DB is now in a bad state (a
// bookmark has an illegal url).
engine._buildGUIDMap();
yield promiseMessage;
// And we should have deleted the item.
yield promiseNoItem(bmid);
});
add_task(function* test_ignore_missing_uri() {
@ -96,13 +52,9 @@ add_task(function* test_ignore_missing_uri() {
, { id: bmid });
}));
// DB is now "corrupt" - bookmarks will fail to locate a string url to log
// and use "<not found>" as a placeholder.
let promiseMessage = promiseLogMessage('Deleting bookmark with invalid URI. url="<not found>"');
// Ensure that this doesn't throw even though the DB is now in a bad state (a
// bookmark has an illegal url).
engine._buildGUIDMap();
yield promiseMessage;
// And we should have deleted the item.
yield promiseNoItem(bmid);
});
function run_test() {

Просмотреть файл

@ -240,7 +240,6 @@ ExtensionPage = class extends BaseContext {
this.contentWindow = contentWindow || null;
this.uri = uri || extension.baseURI;
this.incognito = params.incognito || false;
this.unloaded = false;
// This is the MessageSender property passed to extension.
// It can be augmented by the "page-open" hook.
@ -285,8 +284,6 @@ ExtensionPage = class extends BaseContext {
return;
}
this.unloaded = true;
super.unload();
Management.emit("page-unload", this);

Просмотреть файл

@ -54,6 +54,11 @@ function runSafeWithoutClone(f, ...args) {
// Run a function, cloning arguments into context.cloneScope, and
// report exceptions. |f| is expected to be in context.cloneScope.
function runSafeSync(context, f, ...args) {
if (context.unloaded) {
Cu.reportError("runSafeSync called after context unloaded");
return;
}
try {
args = Cu.cloneInto(args, context.cloneScope);
} catch (e) {
@ -72,6 +77,10 @@ function runSafe(context, f, ...args) {
Cu.reportError(e);
dump(`runSafe failure: cloning into ${context.cloneScope}: ${e}\n\n${filterStack(Error())}`);
}
if (context.unloaded) {
dump(`runSafe failure: context is already unloaded ${filterStack(new Error())}\n`);
return undefined;
}
return runSafeWithoutClone(f, ...args);
}
@ -135,6 +144,7 @@ class BaseContext {
this.checkedLastError = false;
this._lastError = null;
this.contextId = ++gContextId;
this.unloaded = false;
}
get cloneScope() {
@ -145,6 +155,22 @@ class BaseContext {
throw new Error("Not implemented");
}
runSafe(...args) {
if (this.unloaded) {
Cu.reportError("context.runSafe called after context unloaded");
} else {
return runSafeSync(this, ...args);
}
}
runSafeWithoutClone(...args) {
if (this.unloaded) {
Cu.reportError("context.runSafeWithoutClone called after context unloaded");
} else {
return runSafeSyncWithoutClone(...args);
}
}
checkLoadURL(url, options = {}) {
let ssm = Services.scriptSecurityManager;
@ -271,15 +297,17 @@ class BaseContext {
wrapPromise(promise, callback = null) {
// Note: `promise instanceof this.cloneScope.Promise` returns true
// here even for promises that do not belong to the content scope.
let runSafe = runSafeSync.bind(null, this);
let runSafe = this.runSafe.bind(this);
if (promise.constructor === this.cloneScope.Promise) {
runSafe = runSafeSyncWithoutClone;
runSafe = this.runSafeWithoutClone.bind(this);
}
if (callback) {
promise.then(
args => {
if (args instanceof SpreadArgs) {
if (this.unloaded) {
dump(`Promise resolved after context unloaded\n`);
} else if (args instanceof SpreadArgs) {
runSafe(callback, ...args);
} else {
runSafe(callback, args);
@ -287,21 +315,37 @@ class BaseContext {
},
error => {
this.withLastError(error, () => {
runSafeSyncWithoutClone(callback);
if (this.unloaded) {
dump(`Promise rejected after context unloaded\n`);
} else {
this.runSafeWithoutClone(callback);
}
});
});
} else {
return new this.cloneScope.Promise((resolve, reject) => {
promise.then(
value => { runSafe(resolve, value); },
value => {
runSafeSyncWithoutClone(reject, this.normalizeError(value));
if (this.unloaded) {
dump(`Promise resolved after context unloaded\n`);
} else {
runSafe(resolve, value);
}
},
value => {
if (this.unloaded) {
dump(`Promise rejected after context unloaded\n`);
} else {
this.runSafeWithoutClone(reject, this.normalizeError(value));
}
});
});
}
}
unload() {
this.unloaded = true;
MessageChannel.abortResponses({
extensionId: this.extension.id,
contextId: this.contextId,
@ -570,13 +614,19 @@ EventManager.prototype = {
fire(...args) {
for (let callback of this.callbacks) {
runSafe(this.context, callback, ...args);
Promise.resolve(callback).then(callback => {
if (this.context.unloaded) {
dump(`${this.name} event fired after context unloaded.`);
} else if (this.callbacks.has(callback)) {
this.context.runSafe(callback, ...args);
}
});
}
},
fireWithoutClone(...args) {
for (let callback of this.callbacks) {
runSafeSyncWithoutClone(callback, ...args);
this.context.runSafeWithoutClone(callback, ...args);
}
},
@ -609,7 +659,15 @@ function SingletonEventManager(context, name, register) {
SingletonEventManager.prototype = {
addListener(callback, ...args) {
let unregister = this.register(callback, ...args);
let wrappedCallback = (...args) => {
if (this.context.unloaded) {
dump(`${this.name} event fired after context unloaded.`);
} else if (this.unregister.has(callback)) {
return callback(...args);
}
};
let unregister = this.register(wrappedCallback, ...args);
this.unregister.set(callback, unregister);
},
@ -933,7 +991,7 @@ Messenger.prototype = {
this.delegate.getSender(this.context, target, sender);
}
let port = new Port(this.context, mm, name, portId, sender);
runSafeSyncWithoutClone(callback, port.api());
this.context.runSafeWithoutClone(callback, port.api());
return true;
},
};

Просмотреть файл

@ -150,9 +150,26 @@ add_task(function* webnav_ordering() {
ok(index1 < index2, `Action ${JSON.stringify(action1)} happened before ${JSON.stringify(action2)}`);
}
// As required in the webNavigation API documentation:
// If a navigating frame contains subframes, its onCommitted is fired before any
// of its children's onBeforeNavigate; while onCompleted is fired after
// all of its children's onCompleted.
checkBefore({url: URL, event: "onCommitted"}, {url: FRAME, event: "onBeforeNavigate"});
checkBefore({url: FRAME, event: "onCompleted"}, {url: URL, event: "onCompleted"});
// As required in the webNAvigation API documentation, check the event sequence:
// onBeforeNavigate -> onCommitted -> onDOMContentLoaded -> onCompleted
let expectedEventSequence = [
"onBeforeNavigate", "onCommitted", "onDOMContentLoaded", "onCompleted",
];
for (let i = 1; i < expectedEventSequence.length; i++) {
let after = expectedEventSequence[i];
let before = expectedEventSequence[i - 1];
checkBefore({url: URL, event: before}, {url: URL, event: after});
checkBefore({url: FRAME, event: before}, {url: FRAME, event: after});
}
yield loadAndWait(win, "onCompleted", FRAME2, () => { win.frames[0].location = FRAME2; });
checkRequired(FRAME2);

Просмотреть файл

@ -1740,7 +1740,7 @@ this.PlacesUtils = {
* - guid (string): the item's GUID (same as aItemGuid for the top item).
* - [deprecated] id (number): the item's id. This is only if
* aOptions.includeItemIds is set.
* - type (number): the item's type. @see PlacesUtils.TYPE_X_*
* - type (string): the item's type. @see PlacesUtils.TYPE_X_*
* - title (string): the item's title. If it has no title, this property
* isn't set.
* - dateAdded (number, microseconds from the epoch): the date-added value of
@ -1749,6 +1749,7 @@ this.PlacesUtils = {
* value of the item.
* - annos (see getAnnotationsForItem): the item's annotations. This is not
* set if there are no annotations set for the item).
* - index: the item's index under it's parent.
*
* The root object (i.e. the one for aItemGuid) also has the following
* properties set:

Просмотреть файл

@ -1256,7 +1256,7 @@ Search.prototype = {
// the URLBar.
value: makeActionURL("remotetab", { url, deviceName }),
comment: title || url,
style: "action",
style: "action remotetab",
// we want frecency > FRECENCY_DEFAULT so it doesn't get pushed out
// by "remote" matches.
frecency: FRECENCY_DEFAULT + 1,

Просмотреть файл

@ -53,7 +53,7 @@ function makeRemoteTabMatch(url, deviceName, extra = {}) {
return {
uri: makeActionURI("remotetab", {url, deviceName}),
title: extra.title || url,
style: [ "action" ],
style: [ "action", "remotetab" ],
icon: extra.icon,
}
}

Просмотреть файл

@ -35,6 +35,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "ProfileAge",
"resource://gre/modules/ProfileAge.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
"resource://gre/modules/UpdateUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
"resource://gre/modules/WindowsRegistry.jsm");
const CHANGE_THROTTLE_INTERVAL_MS = 5 * 60 * 1000;
@ -317,16 +319,17 @@ function getGfxAdapter(aSuffix = "") {
}
/**
* Gets the service pack information on Windows platforms. This was copied from
* nsUpdateService.js.
* Gets the service pack and build information on Windows platforms. The initial version
* was copied from nsUpdateService.js.
*
* @return An object containing the service pack major and minor versions.
* @return An object containing the service pack major and minor versions, along with the
* build number.
*/
function getServicePack() {
const UNKNOWN_SERVICE_PACK = {major: null, minor: null};
function getWindowsVersionInfo() {
const UNKNOWN_VERSION_INFO = {servicePackMajor: null, servicePackMinor: null, buildNumber: null};
if (AppConstants.platform !== "win") {
return UNKNOWN_SERVICE_PACK;
return UNKNOWN_VERSION_INFO;
}
const BYTE = ctypes.uint8_t;
@ -367,11 +370,12 @@ function getServicePack() {
}
return {
major: winVer.wServicePackMajor,
minor: winVer.wServicePackMinor,
servicePackMajor: winVer.wServicePackMajor,
servicePackMinor: winVer.wServicePackMinor,
buildNumber: winVer.dwBuildNumber,
};
} catch (e) {
return UNKNOWN_SERVICE_PACK;
return UNKNOWN_VERSION_INFO;
} finally {
kernel32.close();
}
@ -1243,9 +1247,23 @@ EnvironmentCache.prototype = {
if (["gonk", "android"].includes(AppConstants.platform)) {
data.kernelVersion = getSysinfoProperty("kernel_version", null);
} else if (AppConstants.platform === "win") {
let servicePack = getServicePack();
data.servicePackMajor = servicePack.major;
data.servicePackMinor = servicePack.minor;
// The path to the "UBR" key, queried to get additional version details on Windows.
const WINDOWS_UBR_KEY_PATH = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
let versionInfo = getWindowsVersionInfo();
data.servicePackMajor = versionInfo.servicePackMajor;
data.servicePackMinor = versionInfo.servicePackMinor;
// We only need the build number and UBR if we're at or above Windows 10.
if (typeof(data.version) === 'string' &&
Services.vc.compare(data.version, "10") >= 0) {
data.windowsBuildNumber = versionInfo.buildNumber;
// Query the UBR key and only add it to the environment if it's available.
// |readRegKey| doesn't throw, but rather returns 'undefined' on error.
let ubr = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
WINDOWS_UBR_KEY_PATH, "UBR",
Ci.nsIWindowsRegKey.WOW64_64);
data.windowsUBR = (ubr !== undefined) ? ubr : null;
}
data.installYear = getSysinfoProperty("installYear", null);
}

Просмотреть файл

@ -108,6 +108,9 @@ Structure::
kernelVersion: <string>, // android/b2g only or null on failure
servicePackMajor: <number>, // windows only or null on failure
servicePackMinor: <number>, // windows only or null on failure
windowsBuildNumber: <number>, // windows 10 only or null on failure
windowsUBR: <number>, // windows 10 only or null on failure
installYear: <number>, // windows only or null on failure
locale: <string>, // "en" or null on failure
},
hdd: {
@ -323,6 +326,24 @@ Important values for ``distributionId`` include:
- "canonical", for the `Ubuntu Firefox repack <http://bazaar.launchpad.net/~mozillateam/firefox/firefox.trusty/view/head:/debian/distribution.ini>`_.
- "yandex", for the Firefox Build by Yandex.
system
------
os
~~
This object contains operating system information.
- ``name``: the name of the OS.
- ``version``: a string representing the OS version.
- ``kernelVersion``: an Android/B2G only string representing the kernel version.
- ``servicePackMajor``: the Windows only major version number for the installed service pack.
- ``servicePackMinor``: the Windows only minor version number for the installed service pack.
- ``windowsBuildNumber``: the Windows build number, only available for Windows >= 10.
- ``windowsUBR``: the Windows UBR number, only available for Windows >= 10. This value is incremented by Windows cumulative updates patches.
- ``installYear``: the Windows only integer representing the year the OS was installed.
- ``locale``: the string representing the OS locale.
addons
------

Просмотреть файл

@ -511,6 +511,16 @@ function checkSystemSection(data) {
"ServicePackMajor must be a number.");
Assert.ok(Number.isFinite(osData["servicePackMinor"]),
"ServicePackMinor must be a number.");
if ("windowsBuildNumber" in osData) {
// This might not be available on all Windows platforms.
Assert.ok(Number.isFinite(osData["windowsBuildNumber"]),
"windowsBuildNumber must be a number.");
}
if ("windowsUBR" in osData) {
// This might not be available on all Windows platforms.
Assert.ok((osData["windowsUBR"] === null) || Number.isFinite(osData["windowsUBR"]),
"windowsUBR must be null or a number.");
}
} else if (gIsAndroid || gIsGonk) {
Assert.ok(checkNullOrString(osData.kernelVersion));
}

Просмотреть файл

@ -17,15 +17,19 @@ var WindowsRegistry = {
* The registry path to the key.
* @param aKey
* The key name.
* @param [aRegistryNode=0]
* Optionally set to nsIWindowsRegKey.WOW64_64 (or nsIWindowsRegKey.WOW64_32)
* to access a 64-bit (32-bit) key from either a 32-bit or 64-bit application.
* @return The key value or undefined if it doesn't exist. If the key is
* a REG_MULTI_SZ, an array is returned.
*/
readRegKey: function(aRoot, aPath, aKey) {
readRegKey: function(aRoot, aPath, aKey, aRegistryNode=0) {
const kRegMultiSz = 7;
const kMode = Ci.nsIWindowsRegKey.ACCESS_READ | aRegistryNode;
let registry = Cc["@mozilla.org/windows-registry-key;1"].
createInstance(Ci.nsIWindowsRegKey);
try {
registry.open(aRoot, aPath, Ci.nsIWindowsRegKey.ACCESS_READ);
registry.open(aRoot, aPath, kMode);
if (registry.hasValue(aKey)) {
let type = registry.getValueType(aKey);
switch (type) {
@ -57,13 +61,17 @@ var WindowsRegistry = {
* The registry path to the key.
* @param aKey
* The key name.
* @param [aRegistryNode=0]
* Optionally set to nsIWindowsRegKey.WOW64_64 (or nsIWindowsRegKey.WOW64_32)
* to access a 64-bit (32-bit) key from either a 32-bit or 64-bit application.
*/
removeRegKey: function(aRoot, aPath, aKey) {
removeRegKey: function(aRoot, aPath, aKey, aRegistryNode=0) {
let registry = Cc["@mozilla.org/windows-registry-key;1"].
createInstance(Ci.nsIWindowsRegKey);
try {
let mode = Ci.nsIWindowsRegKey.ACCESS_QUERY_VALUE |
Ci.nsIWindowsRegKey.ACCESS_SET_VALUE;
Ci.nsIWindowsRegKey.ACCESS_SET_VALUE |
aRegistryNode;
registry.open(aRoot, aPath, mode);
if (registry.hasValue(aKey)) {
registry.removeValue(aKey);

Просмотреть файл

@ -25,13 +25,15 @@ var Manager = {
init() {
Services.mm.addMessageListener("Extension:DOMContentLoaded", this);
Services.mm.addMessageListener("Extension:StateChange", this);
Services.mm.addMessageListener("Extension:LocationChange", this);
Services.mm.addMessageListener("Extension:DocumentChange", this);
Services.mm.addMessageListener("Extension:HistoryChange", this);
Services.mm.loadFrameScript("resource://gre/modules/WebNavigationContent.js", true);
},
uninit() {
Services.mm.removeMessageListener("Extension:StateChange", this);
Services.mm.removeMessageListener("Extension:LocationChange", this);
Services.mm.removeMessageListener("Extension:DocumentChange", this);
Services.mm.removeMessageListener("Extension:HistoryChange", this);
Services.mm.removeMessageListener("Extension:DOMContentLoaded", this);
Services.mm.removeDelayedFrameScript("resource://gre/modules/WebNavigationContent.js");
Services.mm.broadcastAsyncMessage("Extension:DisableWebNavigation");
@ -70,8 +72,12 @@ var Manager = {
this.onStateChange(target, data);
break;
case "Extension:LocationChange":
this.onLocationChange(target, data);
case "Extension:DocumentChange":
this.onDocumentChange(target, data);
break;
case "Extension:HistoryChange":
this.onHistoryChange(target, data);
break;
case "Extension:DOMContentLoaded":
@ -97,15 +103,19 @@ var Manager = {
}
},
onLocationChange(browser, data) {
onDocumentChange(browser, data) {
let url = data.location;
this.fire("onCommitted", browser, data, {url});
},
onHistoryChange(browser, data) {
let url = data.location;
if (data.isReferenceFragmentUpdated) {
this.fire("onReferenceFragmentUpdated", browser, data, {url});
} else if (data.isHistoryStateUpdated) {
this.fire("onHistoryStateUpdated", browser, data, {url});
} else {
this.fire("onCommitted", browser, data, {url});
}
},

Просмотреть файл

@ -54,29 +54,28 @@ var WebProgressListener = {
},
onStateChange: function onStateChange(webProgress, request, stateFlags, status) {
let data = {
requestURL: request.QueryInterface(Ci.nsIChannel).URI.spec,
windowId: webProgress.DOMWindowID,
parentWindowId: WebNavigationFrames.getParentWindowId(webProgress.DOMWindow),
status,
stateFlags,
};
let locationURI = request.QueryInterface(Ci.nsIChannel).URI;
sendAsyncMessage("Extension:StateChange", data);
this.sendStateChange({webProgress, locationURI, stateFlags, status});
if (webProgress.DOMWindow.top != webProgress.DOMWindow) {
let webNav = webProgress.QueryInterface(Ci.nsIWebNavigation);
if (!webNav.canGoBack) {
// For some reason we don't fire onLocationChange for the
// initial navigation of a sub-frame. So we need to simulate
// it here.
this.onLocationChange(webProgress, request, request.QueryInterface(Ci.nsIChannel).URI, 0);
}
// Based on the docs of the webNavigation.onCommitted event, it should be raised when:
// "The document might still be downloading, but at least part of
// the document has been received"
// and for some reason we don't fire onLocationChange for the
// initial navigation of a sub-frame.
// For the above two reasons, when the navigation event is related to
// a sub-frame we process the document change here and
// then send an "Extension:DocumentChange" message to the main process,
// where it will be turned into a webNavigation.onCommitted event.
// (see Bug 1264936 and Bug 125662 for rationale)
if ((webProgress.DOMWindow.top != webProgress.DOMWindow) &&
(stateFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT)) {
this.sendDocumentChange({webProgress, locationURI});
}
},
onLocationChange: function onLocationChange(webProgress, request, locationURI, flags) {
let {DOMWindow, loadType} = webProgress;
let {DOMWindow} = webProgress;
// Get the previous URI loaded in the DOMWindow.
let previousURI = this.previousURIMap.get(DOMWindow);
@ -85,37 +84,79 @@ var WebProgressListener = {
this.previousURIMap.set(DOMWindow, locationURI);
let isSameDocument = (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
let isHistoryStateUpdated = false;
let isReferenceFragmentUpdated = false;
// When a frame navigation doesn't change the current loaded document
// (which can be due to history.pushState/replaceState or to a changed hash in the url),
// it is reported only to the onLocationChange, for this reason
// we process the history change here and then we are going to send
// an "Extension:HistoryChange" to the main process, where it will be turned
// into a webNavigation.onHistoryStateUpdated/onReferenceFragmentUpdated event.
if (isSameDocument) {
let pathChanged = !(previousURI && locationURI.equalsExceptRef(previousURI));
let hashChanged = !(previousURI && previousURI.ref == locationURI.ref);
// When the location changes but the document is the same:
// - path not changed and hash changed -> |onReferenceFragmentUpdated|
// (even if it changed using |history.pushState|)
// - path not changed and hash not changed -> |onHistoryStateUpdated|
// (only if it changes using |history.pushState|)
// - path changed -> |onHistoryStateUpdated|
if (!pathChanged && hashChanged) {
isReferenceFragmentUpdated = true;
} else if (loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE) {
isHistoryStateUpdated = true;
} else if (loadType & Ci.nsIDocShell.LOAD_CMD_HISTORY) {
isHistoryStateUpdated = true;
}
this.sendHistoryChange({webProgress, previousURI, locationURI});
} else if (webProgress.DOMWindow.top == webProgress.DOMWindow) {
// We have to catch the document changes from top level frames here,
// where we can detect the "server redirect" transition.
// (see Bug 1264936 and Bug 125662 for rationale)
this.sendDocumentChange({webProgress, locationURI, request});
}
},
sendStateChange({webProgress, locationURI, stateFlags, status}) {
let data = {
requestURL: locationURI.spec,
windowId: webProgress.DOMWindowID,
parentWindowId: WebNavigationFrames.getParentWindowId(webProgress.DOMWindow),
status,
stateFlags,
};
sendAsyncMessage("Extension:StateChange", data);
},
sendDocumentChange({webProgress, locationURI}) {
let data = {
isHistoryStateUpdated, isReferenceFragmentUpdated,
location: locationURI ? locationURI.spec : "",
windowId: webProgress.DOMWindowID,
parentWindowId: WebNavigationFrames.getParentWindowId(webProgress.DOMWindow),
};
sendAsyncMessage("Extension:LocationChange", data);
sendAsyncMessage("Extension:DocumentChange", data);
},
sendHistoryChange({webProgress, previousURI, locationURI}) {
let {loadType} = webProgress;
let isHistoryStateUpdated = false;
let isReferenceFragmentUpdated = false;
let pathChanged = !(previousURI && locationURI.equalsExceptRef(previousURI));
let hashChanged = !(previousURI && previousURI.ref == locationURI.ref);
// When the location changes but the document is the same:
// - path not changed and hash changed -> |onReferenceFragmentUpdated|
// (even if it changed using |history.pushState|)
// - path not changed and hash not changed -> |onHistoryStateUpdated|
// (only if it changes using |history.pushState|)
// - path changed -> |onHistoryStateUpdated|
if (!pathChanged && hashChanged) {
isReferenceFragmentUpdated = true;
} else if (loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE) {
isHistoryStateUpdated = true;
} else if (loadType & Ci.nsIDocShell.LOAD_CMD_HISTORY) {
isHistoryStateUpdated = true;
}
if (isHistoryStateUpdated || isReferenceFragmentUpdated) {
let data = {
isHistoryStateUpdated, isReferenceFragmentUpdated,
location: locationURI ? locationURI.spec : "",
windowId: webProgress.DOMWindowID,
parentWindowId: WebNavigationFrames.getParentWindowId(webProgress.DOMWindow),
};
sendAsyncMessage("Extension:HistoryChange", data);
}
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]),