mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 13:51:41 +00:00
Merge fx-team to m-c a=merge
This commit is contained in:
commit
f7b636d799
@ -46,6 +46,12 @@ searchbar {
|
||||
-moz-binding: url("chrome://browser/content/search/search.xml#searchbar");
|
||||
}
|
||||
|
||||
/* Prevent shrinking the page content to 0 height and width */
|
||||
.browserStack > browser {
|
||||
min-height: 25px;
|
||||
min-width: 25px;
|
||||
}
|
||||
|
||||
.browserStack > browser {
|
||||
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-browser");
|
||||
}
|
||||
|
@ -32,6 +32,55 @@ function loadURI(tab, url) {
|
||||
return BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
}
|
||||
|
||||
// Creates a framescript which caches the current object value from the plugin
|
||||
// in the page. checkObjectValue below verifies that the framescript is still
|
||||
// active for the browser and that the cached value matches that from the plugin
|
||||
// in the page which tells us the plugin hasn't been reinitialized.
|
||||
function cacheObjectValue(browser) {
|
||||
let frame_script = function() {
|
||||
let plugin = content.document.wrappedJSObject.body.firstChild;
|
||||
let objectValue = plugin.getObjectValue();
|
||||
|
||||
addMessageListener("Test:CheckObjectValue", () => {
|
||||
try {
|
||||
let plugin = content.document.wrappedJSObject.body.firstChild;
|
||||
sendAsyncMessage("Test:CheckObjectValue", {
|
||||
result: plugin.checkObjectValue(objectValue)
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
sendAsyncMessage("Test:CheckObjectValue", {
|
||||
result: null,
|
||||
exception: e.toString()
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
browser.messageManager.loadFrameScript("data:,(" + frame_script.toString() + ")();", false)
|
||||
}
|
||||
|
||||
// See the notes for cacheObjectValue above.
|
||||
function checkObjectValue(browser) {
|
||||
let mm = browser.messageManager;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let listener = ({ data }) => {
|
||||
mm.removeMessageListener("Test:CheckObjectValue", listener);
|
||||
if (data.result === null) {
|
||||
ok(false, "checkObjectValue threw an exception: " + data.exception);
|
||||
reject(data.exception);
|
||||
}
|
||||
else {
|
||||
resolve(data.result);
|
||||
}
|
||||
};
|
||||
|
||||
mm.addMessageListener("Test:CheckObjectValue", listener);
|
||||
mm.sendAsyncMessage("Test:CheckObjectValue");
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function*() {
|
||||
let embed = '<embed type="application/x-test" allowscriptaccess="always" allowfullscreen="true" wmode="window" width="640" height="480"></embed>'
|
||||
setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
|
||||
@ -57,15 +106,12 @@ add_task(function*() {
|
||||
is(gBrowser.tabs[2], tabs[3], "tab3");
|
||||
is(gBrowser.tabs[3], tabs[4], "tab4");
|
||||
|
||||
let plugin = tabs[4].linkedBrowser.contentDocument.wrappedJSObject.body.firstChild;
|
||||
let tab4_plugin_object = plugin.getObjectValue();
|
||||
cacheObjectValue(tabs[4].linkedBrowser);
|
||||
|
||||
swapTabsAndCloseOther(3, 2); // now: 0 1 4
|
||||
gBrowser.selectedTab = gBrowser.tabs[2];
|
||||
|
||||
let doc = gBrowser.tabs[2].linkedBrowser.contentDocument.wrappedJSObject;
|
||||
plugin = doc.body.firstChild;
|
||||
ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
|
||||
ok((yield checkObjectValue(gBrowser.tabs[2].linkedBrowser)), "same plugin instance");
|
||||
|
||||
is(gBrowser.tabs[1], tabs[1], "tab1");
|
||||
is(gBrowser.tabs[2], tabs[3], "tab4");
|
||||
@ -77,9 +123,7 @@ add_task(function*() {
|
||||
swapTabsAndCloseOther(2, 1); // now: 0 4
|
||||
is(gBrowser.tabs[1], tabs[1], "tab1");
|
||||
|
||||
doc = gBrowser.tabs[1].linkedBrowser.contentDocument.wrappedJSObject;
|
||||
plugin = doc.body.firstChild;
|
||||
ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
|
||||
ok((yield checkObjectValue(gBrowser.tabs[1].linkedBrowser)), "same plugin instance");
|
||||
|
||||
yield clickTest(gBrowser.tabs[1]);
|
||||
|
||||
|
@ -25,12 +25,12 @@ add_task(function*() {
|
||||
is (nbox.clientWidth, nboxWidth, "Opening the toolbox hasn't changed the width of the nbox");
|
||||
|
||||
let iframe = document.getAnonymousElementByAttribute(nbox, "class", "devtools-toolbox-bottom-iframe");
|
||||
is (iframe.clientHeight, nboxHeight - 10, "The iframe fits within the available space ");
|
||||
is (iframe.clientHeight, nboxHeight - 25, "The iframe fits within the available space");
|
||||
|
||||
yield toolbox.switchHost(devtools.Toolbox.HostType.SIDE);
|
||||
iframe = document.getAnonymousElementByAttribute(nbox, "class", "devtools-toolbox-side-iframe");
|
||||
iframe.style.minWidth = "1px"; // Disable the min width set in css
|
||||
is (iframe.clientWidth, nboxWidth - 10, "The iframe fits within the available space");
|
||||
is (iframe.clientWidth, nboxWidth - 25, "The iframe fits within the available space");
|
||||
|
||||
yield cleanup(toolbox);
|
||||
});
|
||||
|
@ -10,6 +10,12 @@ const {Promise: promise} = require("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource:///modules/devtools/DOMHelpers.jsm");
|
||||
|
||||
/* A host should always allow this much space for the page to be displayed.
|
||||
* There is also a min-height on the browser, but we still don't want to set
|
||||
* frame.height to be larger than that, since it can cause problems with
|
||||
* resizing the toolbox and panel layout. */
|
||||
const MIN_PAGE_SIZE = 25;
|
||||
|
||||
/**
|
||||
* A toolbox host represents an object that contains a toolbox (e.g. the
|
||||
* sidebar or a separate window). Any host object should implement the
|
||||
@ -57,7 +63,7 @@ BottomHost.prototype = {
|
||||
this.frame.className = "devtools-toolbox-bottom-iframe";
|
||||
this.frame.height = Math.min(
|
||||
Services.prefs.getIntPref(this.heightPref),
|
||||
this._nbox.clientHeight - 10 // Always show at least some page content
|
||||
this._nbox.clientHeight - MIN_PAGE_SIZE
|
||||
);
|
||||
|
||||
this._nbox.appendChild(this._splitter);
|
||||
@ -144,7 +150,7 @@ SidebarHost.prototype = {
|
||||
|
||||
this.frame.width = Math.min(
|
||||
Services.prefs.getIntPref(this.widthPref),
|
||||
this._sidebar.clientWidth - 10 // Always show at least some page content
|
||||
this._sidebar.clientWidth - MIN_PAGE_SIZE
|
||||
);
|
||||
|
||||
this._sidebar.appendChild(this._splitter);
|
||||
|
@ -2328,7 +2328,7 @@ function ElementEditor(aContainer, aNode) {
|
||||
this.template = this.markup.template.bind(this.markup);
|
||||
this.doc = this.markup.doc;
|
||||
|
||||
this.attrs = {};
|
||||
this.attrElements = new Map();
|
||||
this.animationTimers = {};
|
||||
|
||||
// The templates will fill the following properties
|
||||
@ -2408,14 +2408,20 @@ ElementEditor.prototype = {
|
||||
* Update the state of the editor from the node.
|
||||
*/
|
||||
update: function() {
|
||||
let attrs = this.node.attributes || [];
|
||||
let attrsToRemove = new Set(this.attrList.querySelectorAll(".attreditor"));
|
||||
let nodeAttributes = this.node.attributes || [];
|
||||
|
||||
// Only loop through the current attributes on the node, anything that's
|
||||
// been removed will be removed from this DOM because it will be part of
|
||||
// the attrsToRemove set.
|
||||
for (let attr of attrs) {
|
||||
let el = this.attrs[attr.name];
|
||||
// Keep the data model in sync with attributes on the node.
|
||||
let currentAttributes = new Set(nodeAttributes.map(a=>a.name));
|
||||
for (let name of this.attrElements.keys()) {
|
||||
if (!currentAttributes.has(name)) {
|
||||
this.removeAttribute(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Only loop through the current attributes on the node. Missing
|
||||
// attributes have already been removed at this point.
|
||||
for (let attr of nodeAttributes) {
|
||||
let el = this.attrElements.get(attr.name);
|
||||
let valueChanged = el && el.querySelector(".attr-value").innerHTML !== attr.value;
|
||||
let isEditing = el && el.querySelector(".editable").inplaceEditor;
|
||||
let canSimplyShowEditor = el && (!valueChanged || isEditing);
|
||||
@ -2423,7 +2429,6 @@ ElementEditor.prototype = {
|
||||
if (canSimplyShowEditor) {
|
||||
// Element already exists and doesn't need to be recreated.
|
||||
// Just show it (it's hidden by default due to the template).
|
||||
attrsToRemove.delete(el);
|
||||
el.style.removeProperty("display");
|
||||
} else {
|
||||
// Create a new editor, because the value of an existing attribute
|
||||
@ -2439,10 +2444,6 @@ ElementEditor.prototype = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let el of attrsToRemove) {
|
||||
el.remove();
|
||||
}
|
||||
},
|
||||
|
||||
_startModifyingAttributes: function() {
|
||||
@ -2459,6 +2460,18 @@ ElementEditor.prototype = {
|
||||
".attreditor[data-attr=" + attrName + "] .attr-value");
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an attribute from the attrElements object and the DOM
|
||||
* @param string attrName The name of the attribute to remove
|
||||
*/
|
||||
removeAttribute: function(attrName) {
|
||||
let attr = this.attrElements.get(attrName);
|
||||
if (attr) {
|
||||
this.attrElements.delete(attrName);
|
||||
attr.remove();
|
||||
}
|
||||
},
|
||||
|
||||
_createAttribute: function(aAttr, aBefore = null) {
|
||||
// Create the template editor, which will save some variables here.
|
||||
let data = {
|
||||
@ -2541,18 +2554,13 @@ ElementEditor.prototype = {
|
||||
if (aAttr.name == "id") {
|
||||
before = this.attrList.firstChild;
|
||||
} else if (aAttr.name == "class") {
|
||||
let idNode = this.attrs["id"];
|
||||
let idNode = this.attrElements.get("id");
|
||||
before = idNode ? idNode.nextSibling : this.attrList.firstChild;
|
||||
}
|
||||
this.attrList.insertBefore(attr, before);
|
||||
|
||||
// Remove the old version of this attribute from the DOM.
|
||||
let oldAttr = this.attrs[aAttr.name];
|
||||
if (oldAttr && oldAttr.parentNode) {
|
||||
oldAttr.parentNode.removeChild(oldAttr);
|
||||
}
|
||||
|
||||
this.attrs[aAttr.name] = attr;
|
||||
this.removeAttribute(aAttr.name);
|
||||
this.attrElements.set(aAttr.name, attr);
|
||||
|
||||
let collapsedValue;
|
||||
if (aAttr.value.match(COLLAPSE_DATA_URL_REGEX)) {
|
||||
|
@ -140,7 +140,7 @@ function* checkData(index, editor, inspector) {
|
||||
} else {
|
||||
let nodeFront = yield getNodeFront("#node14", inspector);
|
||||
let editor = getContainerForNodeFront(nodeFront, inspector).editor;
|
||||
let attr = editor.attrs["style"].querySelector(".editable");
|
||||
let attr = editor.attrElements.get("style").querySelector(".editable");
|
||||
is(attr.textContent, completion, "Correct value is persisted after pressing Enter");
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,32 @@ const TEST_DATA = [
|
||||
}), "newattr attribute removed");
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Re-adding an attribute",
|
||||
test: () => {
|
||||
let node1 = getNode("#node1");
|
||||
node1.setAttribute("newattr", "newattrval");
|
||||
},
|
||||
check: function*(inspector) {
|
||||
let {editor} = yield getContainerForSelector("#node1", inspector);
|
||||
ok([...editor.attrList.querySelectorAll(".attreditor")].some(attr => {
|
||||
return attr.textContent.trim() === "newattr=\"newattrval\"";
|
||||
}), "newattr attribute found");
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Changing an attribute",
|
||||
test: () => {
|
||||
let node1 = getNode("#node1");
|
||||
node1.setAttribute("newattr", "newattrchanged");
|
||||
},
|
||||
check: function*(inspector) {
|
||||
let {editor} = yield getContainerForSelector("#node1", inspector);
|
||||
ok([...editor.attrList.querySelectorAll(".attreditor")].some(attr => {
|
||||
return attr.textContent.trim() === "newattr=\"newattrchanged\"";
|
||||
}), "newattr attribute found");
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Updating the text-content",
|
||||
test: () => {
|
||||
|
@ -123,7 +123,7 @@ function* assertNodeFlashing(nodeFront, inspector) {
|
||||
function* assertAttributeFlashing(nodeFront, attribute, inspector) {
|
||||
let container = getContainerForNodeFront(nodeFront, inspector);
|
||||
ok(container, "Markup container for node found");
|
||||
ok(container.editor.attrs[attribute], "Attribute exists on editor");
|
||||
ok(container.editor.attrElements.get(attribute), "Attribute exists on editor");
|
||||
|
||||
let attributeElement = container.editor.getAttributeElement(attribute);
|
||||
|
||||
|
@ -22,7 +22,7 @@ add_task(function*() {
|
||||
|
||||
info("Focus the ID attribute and change its content");
|
||||
let {editor} = yield getContainerForSelector("#test-div", inspector);
|
||||
let attr = editor.attrs["id"].querySelector(".editable");
|
||||
let attr = editor.attrElements.get("id").querySelector(".editable");
|
||||
let mutated = inspector.once("markupmutation");
|
||||
setEditableFieldValue(attr,
|
||||
attr.textContent + ' class="newclass" style="color:green"', inspector);
|
||||
|
@ -47,7 +47,7 @@ let TEST_DATA = [{
|
||||
},
|
||||
validate: (element, container, inspector) => {
|
||||
let editor = container.editor;
|
||||
let visibleAttrText = editor.attrs["style"].querySelector(".attr-value").textContent;
|
||||
let visibleAttrText = editor.attrElements.get("style").querySelector(".attr-value").textContent;
|
||||
is (visibleAttrText, DATA_URL_INLINE_STYLE_COLLAPSED);
|
||||
}
|
||||
}, {
|
||||
@ -58,7 +58,7 @@ let TEST_DATA = [{
|
||||
},
|
||||
validate: (element, container, inspector) => {
|
||||
let editor = container.editor;
|
||||
let visibleAttrText = editor.attrs["data-long"].querySelector(".attr-value").textContent;
|
||||
let visibleAttrText = editor.attrElements.get("data-long").querySelector(".attr-value").textContent;
|
||||
is (visibleAttrText, LONG_ATTRIBUTE_COLLAPSED)
|
||||
}
|
||||
}, {
|
||||
@ -69,7 +69,7 @@ let TEST_DATA = [{
|
||||
},
|
||||
validate: (element, container, inspector) => {
|
||||
let editor = container.editor;
|
||||
let visibleAttrText = editor.attrs["src"].querySelector(".attr-value").textContent;
|
||||
let visibleAttrText = editor.attrElements.get("src").querySelector(".attr-value").textContent;
|
||||
is (visibleAttrText, DATA_URL_ATTRIBUTE_COLLAPSED);
|
||||
}
|
||||
}];
|
||||
|
@ -36,7 +36,7 @@ function* testCollapsedLongAttribute(inspector) {
|
||||
});
|
||||
|
||||
let {editor} = yield getContainerForSelector("#node24", inspector);
|
||||
let attr = editor.attrs["data-long"].querySelector(".editable");
|
||||
let attr = editor.attrElements.get("data-long").querySelector(".editable");
|
||||
|
||||
// Check to make sure it has expanded after focus
|
||||
attr.focus();
|
||||
@ -48,7 +48,7 @@ function* testCollapsedLongAttribute(inspector) {
|
||||
setEditableFieldValue(attr, input.value + ' data-short="ABC"', inspector);
|
||||
yield inspector.once("markupmutation");
|
||||
|
||||
let visibleAttrText = editor.attrs["data-long"].querySelector(".attr-value").textContent;
|
||||
let visibleAttrText = editor.attrElements.get("data-long").querySelector(".attr-value").textContent;
|
||||
is (visibleAttrText, LONG_ATTRIBUTE_COLLAPSED)
|
||||
|
||||
yield assertAttributes("#node24", {
|
||||
@ -69,7 +69,7 @@ function* testModifyInlineStyleWithQuotes(inspector) {
|
||||
|
||||
let onMutated = inspector.once("markupmutation");
|
||||
let {editor} = yield getContainerForSelector("#node26", inspector);
|
||||
let attr = editor.attrs["style"].querySelector(".editable");
|
||||
let attr = editor.attrElements.get("style").querySelector(".editable");
|
||||
|
||||
attr.focus();
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
@ -105,7 +105,7 @@ function* testEditingAttributeWithMixedQuotes(inspector) {
|
||||
|
||||
let onMutated = inspector.once("markupmutation");
|
||||
let {editor} = yield getContainerForSelector("#node27", inspector);
|
||||
let attr = editor.attrs["class"].querySelector(".editable");
|
||||
let attr = editor.attrElements.get("class").querySelector(".editable");
|
||||
|
||||
attr.focus();
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
|
@ -27,7 +27,7 @@ function* testWellformedMixedCase(inspector) {
|
||||
|
||||
info("Focusing the viewBox attribute editor");
|
||||
let {editor} = yield getContainerForSelector("svg", inspector);
|
||||
let attr = editor.attrs["viewBox"].querySelector(".editable");
|
||||
let attr = editor.attrElements.get("viewBox").querySelector(".editable");
|
||||
attr.focus();
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
|
||||
@ -53,7 +53,7 @@ function* testMalformedMixedCase(inspector) {
|
||||
|
||||
info("Focusing the viewBox attribute editor");
|
||||
let {editor} = yield getContainerForSelector("svg", inspector);
|
||||
let attr = editor.attrs["viewBox"].querySelector(".editable");
|
||||
let attr = editor.attrElements.get("viewBox").querySelector(".editable");
|
||||
attr.focus();
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
|
||||
|
@ -133,7 +133,7 @@ function* runEditAttributesTest(test, inspector) {
|
||||
|
||||
info("Listening for the markupmutation event");
|
||||
let nodeMutated = inspector.once("markupmutation");
|
||||
let attr = container.editor.attrs[test.name].querySelector(".editable");
|
||||
let attr = container.editor.attrElements.get(test.name).querySelector(".editable");
|
||||
setEditableFieldValue(attr, test.value, inspector);
|
||||
yield nodeMutated;
|
||||
|
||||
|
@ -57,7 +57,6 @@ skip-if = e10s # Bug 1091596
|
||||
[browser_net_cyrillic-02.js]
|
||||
[browser_net_details-no-duplicated-content.js]
|
||||
[browser_net_filter-01.js]
|
||||
skip-if = e10s # Bug 1091603
|
||||
[browser_net_filter-02.js]
|
||||
[browser_net_filter-03.js]
|
||||
[browser_net_filter-04.js]
|
||||
|
@ -297,8 +297,10 @@ PerformanceFront.prototype = {
|
||||
return profilerStatus.currentTime;
|
||||
}
|
||||
|
||||
// Extend the profiler options so that protocol.js doesn't modify the original.
|
||||
let profilerOptions = extend({}, this._customProfilerOptions);
|
||||
// If this._customProfilerOptions is defined, use those to pass in
|
||||
// to the profiler actor. The profiler actor handles all the defaults
|
||||
// now, so this should only be used for tests.
|
||||
let profilerOptions = this._customProfilerOptions || {};
|
||||
yield this._request("profiler", "startProfiler", profilerOptions);
|
||||
|
||||
this.emit("profiler-activated");
|
||||
@ -400,19 +402,6 @@ PerformanceFront.prototype = {
|
||||
deferred.resolve();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Overrides the options sent to the built-in profiler module when activating,
|
||||
* such as the maximum entries count, the sampling interval etc.
|
||||
*
|
||||
* Used in tests and for older backend implementations.
|
||||
*/
|
||||
_customProfilerOptions: {
|
||||
entries: 1000000,
|
||||
interval: 1,
|
||||
features: ["js"],
|
||||
threadFilters: ["GeckoMain"]
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an object indicating if mock actors are being used or not.
|
||||
*/
|
||||
|
@ -33,9 +33,9 @@ public:
|
||||
// Check whether two markers should be considered the same,
|
||||
// for the purpose of pairing start and end markers. Normally
|
||||
// this definition suffices.
|
||||
virtual bool Equals(const TimelineMarker* aOther)
|
||||
virtual bool Equals(const TimelineMarker& aOther)
|
||||
{
|
||||
return strcmp(mName, aOther->mName) == 0;
|
||||
return strcmp(mName, aOther.mName) == 0;
|
||||
}
|
||||
|
||||
// Add details specific to this marker type to aMarker. The
|
||||
|
@ -2977,10 +2977,10 @@ nsDocShell::PopProfileTimelineMarkers(
|
||||
// If we see an unpaired START, we keep it around for the next call
|
||||
// to PopProfileTimelineMarkers. We store the kept START objects in
|
||||
// this array.
|
||||
nsTArray<TimelineMarker*> keptMarkers;
|
||||
nsTArray<UniquePtr<TimelineMarker>> keptMarkers;
|
||||
|
||||
for (uint32_t i = 0; i < mProfileTimelineMarkers.Length(); ++i) {
|
||||
TimelineMarker* startPayload = mProfileTimelineMarkers[i];
|
||||
UniquePtr<TimelineMarker>& startPayload = mProfileTimelineMarkers[i];
|
||||
const char* startMarkerName = startPayload->GetName();
|
||||
|
||||
bool hasSeenPaintedLayer = false;
|
||||
@ -3002,7 +3002,7 @@ nsDocShell::PopProfileTimelineMarkers(
|
||||
// enough for the amount of markers to always be small enough that the
|
||||
// nested for loop isn't going to be a performance problem.
|
||||
for (uint32_t j = i + 1; j < mProfileTimelineMarkers.Length(); ++j) {
|
||||
TimelineMarker* endPayload = mProfileTimelineMarkers[j];
|
||||
UniquePtr<TimelineMarker>& endPayload = mProfileTimelineMarkers[j];
|
||||
const char* endMarkerName = endPayload->GetName();
|
||||
|
||||
// Look for Layer markers to stream out paint markers.
|
||||
@ -3011,7 +3011,7 @@ nsDocShell::PopProfileTimelineMarkers(
|
||||
endPayload->AddLayerRectangles(layerRectangles);
|
||||
}
|
||||
|
||||
if (!startPayload->Equals(endPayload)) {
|
||||
if (!startPayload->Equals(*endPayload)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -3048,14 +3048,13 @@ nsDocShell::PopProfileTimelineMarkers(
|
||||
|
||||
// If we did not see the corresponding END, keep the START.
|
||||
if (!hasSeenEnd) {
|
||||
keptMarkers.AppendElement(mProfileTimelineMarkers[i]);
|
||||
keptMarkers.AppendElement(Move(mProfileTimelineMarkers[i]));
|
||||
mProfileTimelineMarkers.RemoveElementAt(i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClearProfileTimelineMarkers();
|
||||
mProfileTimelineMarkers.SwapElements(keptMarkers);
|
||||
|
||||
if (!ToJSValue(aCx, profileTimelineMarkers, aProfileTimelineMarkers)) {
|
||||
@ -3086,10 +3085,10 @@ nsDocShell::AddProfileTimelineMarker(const char* aName,
|
||||
}
|
||||
|
||||
void
|
||||
nsDocShell::AddProfileTimelineMarker(UniquePtr<TimelineMarker>& aMarker)
|
||||
nsDocShell::AddProfileTimelineMarker(UniquePtr<TimelineMarker>&& aMarker)
|
||||
{
|
||||
if (mProfileTimelineRecording) {
|
||||
mProfileTimelineMarkers.AppendElement(aMarker.release());
|
||||
mProfileTimelineMarkers.AppendElement(Move(aMarker));
|
||||
}
|
||||
}
|
||||
|
||||
@ -3125,9 +3124,6 @@ nsDocShell::GetWindowDraggingAllowed(bool* aValue)
|
||||
void
|
||||
nsDocShell::ClearProfileTimelineMarkers()
|
||||
{
|
||||
for (uint32_t i = 0; i < mProfileTimelineMarkers.Length(); ++i) {
|
||||
delete mProfileTimelineMarkers[i];
|
||||
}
|
||||
mProfileTimelineMarkers.Clear();
|
||||
}
|
||||
|
||||
|
@ -272,7 +272,7 @@ public:
|
||||
// See nsIDocShell::recordProfileTimelineMarkers
|
||||
void AddProfileTimelineMarker(const char* aName,
|
||||
TracingMetadata aMetaData);
|
||||
void AddProfileTimelineMarker(mozilla::UniquePtr<TimelineMarker>& aMarker);
|
||||
void AddProfileTimelineMarker(mozilla::UniquePtr<TimelineMarker>&& aMarker);
|
||||
|
||||
// Global counter for how many docShells are currently recording profile
|
||||
// timeline markers
|
||||
@ -984,7 +984,7 @@ private:
|
||||
// True if recording profiles.
|
||||
bool mProfileTimelineRecording;
|
||||
|
||||
nsTArray<TimelineMarker*> mProfileTimelineMarkers;
|
||||
nsTArray<mozilla::UniquePtr<TimelineMarker>> mProfileTimelineMarkers;
|
||||
|
||||
// Get rid of all the timeline markers accumulated so far
|
||||
void ClearProfileTimelineMarkers();
|
||||
|
@ -934,13 +934,13 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool Equals(const TimelineMarker* aOther) override
|
||||
virtual bool Equals(const TimelineMarker& aOther) override
|
||||
{
|
||||
if (!TimelineMarker::Equals(aOther)) {
|
||||
return false;
|
||||
}
|
||||
// Console markers must have matching causes as well.
|
||||
return GetCause() == aOther->GetCause();
|
||||
return GetCause() == aOther.GetCause();
|
||||
}
|
||||
|
||||
virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) override
|
||||
@ -1057,7 +1057,7 @@ Console::Method(JSContext* aCx, MethodName aMethodName,
|
||||
MakeUnique<ConsoleTimelineMarker>(docShell,
|
||||
aMethodName == MethodTime ? TRACING_INTERVAL_START : TRACING_INTERVAL_END,
|
||||
key);
|
||||
docShell->AddProfileTimelineMarker(marker);
|
||||
docShell->AddProfileTimelineMarker(Move(marker));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -312,7 +312,6 @@ AutoJSAPI::~AutoJSAPI()
|
||||
{
|
||||
if (mOwnErrorReporting) {
|
||||
MOZ_ASSERT(NS_IsMainThread(), "See corresponding assertion in TakeOwnershipOfErrorReporting()");
|
||||
JS::ContextOptionsRef(cx()).setAutoJSAPIOwnsErrorReporting(mOldAutoJSAPIOwnsErrorReporting);
|
||||
|
||||
if (HasException()) {
|
||||
|
||||
@ -342,6 +341,13 @@ AutoJSAPI::~AutoJSAPI()
|
||||
NS_WARNING("OOMed while acquiring uncaught exception from JSAPI");
|
||||
}
|
||||
}
|
||||
|
||||
// We need to do this _after_ processing the existing exception, because the
|
||||
// JS engine can throw while doing that, and uses this bit to determine what
|
||||
// to do in that case: squelch the exception if the bit is set, otherwise
|
||||
// call the error reporter. Calling WarningOnlyErrorReporter with a
|
||||
// non-warning will assert, so we need to make sure we do the former.
|
||||
JS::ContextOptionsRef(cx()).setAutoJSAPIOwnsErrorReporting(mOldAutoJSAPIOwnsErrorReporting);
|
||||
}
|
||||
|
||||
if (mOldErrorReporter.isSome()) {
|
||||
|
@ -1094,7 +1094,7 @@ EventListenerManager::HandleEventInternal(nsPresContext* aPresContext,
|
||||
mozilla::UniquePtr<TimelineMarker> marker =
|
||||
MakeUnique<EventTimelineMarker>(ds, TRACING_INTERVAL_START,
|
||||
phase, typeStr);
|
||||
ds->AddProfileTimelineMarker(marker);
|
||||
ds->AddProfileTimelineMarker(Move(marker));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1820,7 +1820,7 @@ _popupcontextmenu(NPP instance, NPMenu* menu)
|
||||
if (success) {
|
||||
return mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu(menu,
|
||||
screenX, screenY,
|
||||
PluginModuleChild::GetChrome(),
|
||||
InstCast(instance)->Manager(),
|
||||
ProcessBrowserEvents);
|
||||
} else {
|
||||
NS_WARNING("Convertpoint failed, could not created contextmenu.");
|
||||
|
@ -229,6 +229,13 @@ ReportError(JSContext *cx, const char *message, JSErrorReport *reportp,
|
||||
if (cx->options().autoJSAPIOwnsErrorReporting() || JS_IsRunning(cx)) {
|
||||
if (ErrorToException(cx, message, reportp, callback, userRef))
|
||||
return;
|
||||
|
||||
/*
|
||||
* The AutoJSAPI error reporter only allows warnings to be reported so
|
||||
* just ignore this error rather than try to report it.
|
||||
*/
|
||||
if (cx->options().autoJSAPIOwnsErrorReporting())
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -608,6 +608,12 @@ js::ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp,
|
||||
static bool
|
||||
IsDuckTypedErrorObject(JSContext *cx, HandleObject exnObject, const char **filename_strp)
|
||||
{
|
||||
/*
|
||||
* This function is called from ErrorReport::init and so should not generate
|
||||
* any new exceptions.
|
||||
*/
|
||||
AutoClearPendingException acpe(cx);
|
||||
|
||||
bool found;
|
||||
if (!JS_HasProperty(cx, exnObject, js_message_str, &found) || !found)
|
||||
return false;
|
||||
|
@ -115,6 +115,20 @@ ExnTypeFromProtoKey(JSProtoKey key)
|
||||
return type;
|
||||
}
|
||||
|
||||
class AutoClearPendingException
|
||||
{
|
||||
JSContext *cx;
|
||||
|
||||
public:
|
||||
explicit AutoClearPendingException(JSContext *cxArg)
|
||||
: cx(cxArg)
|
||||
{ }
|
||||
|
||||
~AutoClearPendingException() {
|
||||
cx->clearPendingException();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* jsexn_h */
|
||||
|
@ -4936,7 +4936,7 @@ FrameLayerBuilder::DrawPaintedLayer(PaintedLayer* aLayer,
|
||||
if (isRecording) {
|
||||
mozilla::UniquePtr<TimelineMarker> marker =
|
||||
MakeUnique<LayerTimelineMarker>(docShell, aRegionToDraw);
|
||||
docShell->AddProfileTimelineMarker(marker);
|
||||
docShell->AddProfileTimelineMarker(Move(marker));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,7 @@ public class LocalReadingListStorage implements ReadingListStorage {
|
||||
* These are not common: they should only occur if a conflict occurs.
|
||||
*/
|
||||
private final Queue<ClientReadingListRecord> deletions;
|
||||
private final Queue<String> deletedGUIDs;
|
||||
|
||||
/**
|
||||
* These are additions or changes fetched from the server.
|
||||
@ -63,16 +64,17 @@ public class LocalReadingListStorage implements ReadingListStorage {
|
||||
LocalReadingListChangeAccumulator() {
|
||||
this.changes = new ConcurrentLinkedQueue<>();
|
||||
this.deletions = new ConcurrentLinkedQueue<>();
|
||||
this.deletedGUIDs = new ConcurrentLinkedQueue<>();
|
||||
this.additionsOrChanges = new ConcurrentLinkedQueue<>();
|
||||
}
|
||||
|
||||
public boolean flushDeletions() throws RemoteException {
|
||||
if (deletions.isEmpty()) {
|
||||
if (deletions.isEmpty() && deletedGUIDs.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
long[] ids = new long[deletions.size()];
|
||||
String[] guids = new String[deletions.size()];
|
||||
String[] guids = new String[deletions.size() + deletedGUIDs.size()];
|
||||
int iID = 0;
|
||||
int iGUID = 0;
|
||||
for (ClientReadingListRecord record : deletions) {
|
||||
@ -86,6 +88,9 @@ public class LocalReadingListStorage implements ReadingListStorage {
|
||||
guids[iGUID++] = guid;
|
||||
}
|
||||
}
|
||||
for (String guid : deletedGUIDs) {
|
||||
guids[iGUID++] = guid;
|
||||
}
|
||||
|
||||
if (iID > 0) {
|
||||
client.delete(URI_WITH_DELETED, RepoUtils.computeSQLLongInClause(ids, ReadingListItems._ID), null);
|
||||
@ -96,6 +101,7 @@ public class LocalReadingListStorage implements ReadingListStorage {
|
||||
}
|
||||
|
||||
deletions.clear();
|
||||
deletedGUIDs.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -211,6 +217,11 @@ public class LocalReadingListStorage implements ReadingListStorage {
|
||||
deletions.add(record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDeletion(String guid) {
|
||||
deletedGUIDs.add(guid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChangedRecord(ClientReadingListRecord record) {
|
||||
changes.add(record);
|
||||
@ -318,6 +329,20 @@ public class LocalReadingListStorage implements ReadingListStorage {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getDeletedItems() {
|
||||
final String[] projection = new String[] {
|
||||
ReadingListItems.GUID,
|
||||
};
|
||||
|
||||
final String selection = "(" + ReadingListItems.IS_DELETED + " = 1) AND (" + ReadingListItems.GUID + " IS NOT NULL)";
|
||||
try {
|
||||
return client.query(URI_WITH_DELETED, projection, selection, null, null);
|
||||
} catch (RemoteException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getNew() {
|
||||
// N.B., query for items that have no GUID, regardless of status.
|
||||
|
@ -11,6 +11,7 @@ package org.mozilla.gecko.reading;
|
||||
* via UPDATE) to the DB.
|
||||
*/
|
||||
public interface ReadingListChangeAccumulator {
|
||||
void addDeletion(String guid);
|
||||
void addDeletion(ClientReadingListRecord record);
|
||||
void addChangedRecord(ClientReadingListRecord record);
|
||||
void addUploadedRecord(ClientReadingListRecord up, ServerReadingListRecord down);
|
||||
|
@ -431,6 +431,76 @@ public class ReadingListClient {
|
||||
}
|
||||
}
|
||||
|
||||
private class DeleteBatchingDelegate implements ReadingListDeleteDelegate {
|
||||
private final Queue<String> queue;
|
||||
private final ReadingListDeleteDelegate batchDeleteDelegate;
|
||||
private final Executor executor;
|
||||
|
||||
DeleteBatchingDelegate(Queue<String> guids,
|
||||
ReadingListDeleteDelegate batchDeleteDelegate,
|
||||
Executor executor) {
|
||||
this.queue = guids;
|
||||
this.batchDeleteDelegate = batchDeleteDelegate;
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
void next() {
|
||||
final String guid = queue.poll();
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (guid == null) {
|
||||
batchDeleteDelegate.onBatchDone();
|
||||
return;
|
||||
}
|
||||
|
||||
again(guid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void again(String guid) {
|
||||
delete(guid, DeleteBatchingDelegate.this, -1L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(ReadingListRecordResponse response,
|
||||
ReadingListRecord record) {
|
||||
batchDeleteDelegate.onSuccess(response, record);
|
||||
next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreconditionFailed(String guid, MozResponse response) {
|
||||
batchDeleteDelegate.onPreconditionFailed(guid, response);
|
||||
next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecordMissingOrDeleted(String guid, MozResponse response) {
|
||||
batchDeleteDelegate.onRecordMissingOrDeleted(guid, response);
|
||||
next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
batchDeleteDelegate.onFailure(e);
|
||||
next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(MozResponse response) {
|
||||
batchDeleteDelegate.onFailure(response);
|
||||
next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBatchDone() {
|
||||
// This should never occur, but if it does, pass through.
|
||||
batchDeleteDelegate.onBatchDone();
|
||||
}
|
||||
}
|
||||
|
||||
// Deliberately declare `delegate` non-final so we can't capture it below. We prefer
|
||||
// to use `recordDelegate` explicitly.
|
||||
public void getOne(final String guid, ReadingListRecordDelegate delegate, final long ifModifiedSince) {
|
||||
@ -511,6 +581,17 @@ public class ReadingListClient {
|
||||
r.post(body);
|
||||
}
|
||||
|
||||
public void delete(final Queue<String> guids, final Executor executor, final ReadingListDeleteDelegate batchDeleteDelegate) {
|
||||
if (guids.isEmpty()) {
|
||||
batchDeleteDelegate.onBatchDone();
|
||||
return;
|
||||
}
|
||||
|
||||
final ReadingListDeleteDelegate deleteDelegate = new DeleteBatchingDelegate(guids, batchDeleteDelegate, executor);
|
||||
|
||||
delete(guids.poll(), deleteDelegate, -1L);
|
||||
}
|
||||
|
||||
public void delete(final String guid, final ReadingListDeleteDelegate delegate, final long ifUnmodifiedSince) {
|
||||
final BaseResource r = getRelativeArticleResource(guid);
|
||||
|
||||
|
@ -10,9 +10,9 @@ import org.mozilla.gecko.reading.ReadingListRecord.ServerMetadata;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.database.AbstractWindowedCursor;
|
||||
import android.database.Cursor;
|
||||
import android.database.CursorWindow;
|
||||
import android.database.sqlite.SQLiteCursor;
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
@ -132,11 +132,11 @@ public class ReadingListClientRecordFactory {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private final void fillGingerbread(ExtendedJSONObject o, Cursor c, String f, int i) {
|
||||
if (!(c instanceof SQLiteCursor)) {
|
||||
if (!(c instanceof AbstractWindowedCursor)) {
|
||||
throw new IllegalStateException("Unable to handle cursors that don't have a CursorWindow!");
|
||||
}
|
||||
|
||||
final SQLiteCursor sqc = (SQLiteCursor) c;
|
||||
final AbstractWindowedCursor sqc = (AbstractWindowedCursor) c;
|
||||
final CursorWindow w = sqc.getWindow();
|
||||
final int pos = c.getPosition();
|
||||
if (w.isNull(pos, i)) {
|
||||
|
@ -8,7 +8,8 @@ import org.mozilla.gecko.sync.net.MozResponse;
|
||||
|
||||
/**
|
||||
* Response delegate for a server DELETE.
|
||||
* Only one of these methods will be called, and it will be called precisely once.
|
||||
* Only one of these methods will be called, and it will be called precisely once,
|
||||
* unless batching is used.
|
||||
*/
|
||||
public interface ReadingListDeleteDelegate {
|
||||
void onSuccess(ReadingListRecordResponse response, ReadingListRecord record);
|
||||
@ -16,4 +17,5 @@ public interface ReadingListDeleteDelegate {
|
||||
void onRecordMissingOrDeleted(String guid, MozResponse response);
|
||||
void onFailure(Exception e);
|
||||
void onFailure(MozResponse response);
|
||||
void onBatchDone();
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import android.database.Cursor;
|
||||
|
||||
public interface ReadingListStorage {
|
||||
Cursor getModified();
|
||||
Cursor getDeletedItems();
|
||||
Cursor getStatusChanges();
|
||||
Cursor getNew();
|
||||
Cursor getAll();
|
||||
|
@ -81,6 +81,12 @@ public class ReadingListSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
syncDelegate.handleError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeletionsUploadComplete() {
|
||||
Logger.debug(LOG_TAG, "Step: onDeletionsUploadComplete");
|
||||
this.result.stats.numEntries += 1; // TODO: Bug 1140809.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusUploadComplete(Collection<String> uploaded,
|
||||
Collection<String> failed) {
|
||||
|
@ -176,6 +176,80 @@ public class ReadingListSynchronizer {
|
||||
}
|
||||
}
|
||||
|
||||
private static class DeletionUploadDelegate implements ReadingListDeleteDelegate {
|
||||
private final ReadingListChangeAccumulator acc;
|
||||
private final StageDelegate next;
|
||||
|
||||
DeletionUploadDelegate(ReadingListChangeAccumulator acc, StageDelegate next) {
|
||||
this.acc = acc;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBatchDone() {
|
||||
try {
|
||||
acc.finish();
|
||||
} catch (Exception e) {
|
||||
next.fail(e);
|
||||
return;
|
||||
}
|
||||
|
||||
next.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(ReadingListRecordResponse response,
|
||||
ReadingListRecord record) {
|
||||
Logger.debug(LOG_TAG, "Tracking uploaded deletion " + record.getGUID());
|
||||
acc.addDeletion(record.getGUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreconditionFailed(String guid, MozResponse response) {
|
||||
// Should never happen.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecordMissingOrDeleted(String guid, MozResponse response) {
|
||||
// Great!
|
||||
Logger.debug(LOG_TAG, "Tracking redundant deletion " + guid);
|
||||
acc.addDeletion(guid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(MozResponse response) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Queue<String> collectDeletedIDsFromCursor(Cursor cursor) {
|
||||
try {
|
||||
final Queue<String> toDelete = new LinkedList<>();
|
||||
|
||||
final int columnGUID = cursor.getColumnIndexOrThrow(ReadingListItems.GUID);
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
final String guid = cursor.getString(columnGUID);
|
||||
if (guid == null) {
|
||||
// Nothing we can do here.
|
||||
continue;
|
||||
}
|
||||
|
||||
toDelete.add(guid);
|
||||
}
|
||||
|
||||
return toDelete;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static class StatusUploadDelegate implements ReadingListRecordUploadDelegate {
|
||||
private final ReadingListChangeAccumulator acc;
|
||||
|
||||
@ -462,6 +536,36 @@ public class ReadingListSynchronizer {
|
||||
}
|
||||
}
|
||||
|
||||
protected void uploadDeletions(final StageDelegate delegate) {
|
||||
try {
|
||||
final Cursor cursor = local.getDeletedItems();
|
||||
|
||||
if (cursor == null) {
|
||||
delegate.fail(new RuntimeException("Unable to get unread item cursor."));
|
||||
return;
|
||||
}
|
||||
|
||||
final Queue<String> toDelete = collectDeletedIDsFromCursor(cursor);
|
||||
|
||||
// Nothing to do.
|
||||
if (toDelete.isEmpty()) {
|
||||
Logger.debug(LOG_TAG, "No new deletions to upload. Skipping.");
|
||||
delegate.next();
|
||||
return;
|
||||
} else {
|
||||
Logger.debug(LOG_TAG, "Deleting " + toDelete.size() + " records from the server.");
|
||||
}
|
||||
|
||||
final ReadingListChangeAccumulator acc = this.local.getChangeAccumulator();
|
||||
final DeletionUploadDelegate deleteDelegate = new DeletionUploadDelegate(acc, delegate);
|
||||
|
||||
// Don't send I-U-S; we're happy for the client to win, because this is a one-way state change.
|
||||
this.remote.delete(toDelete, executor, deleteDelegate);
|
||||
} catch (Exception e) {
|
||||
delegate.fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
// N.B., status changes for items that haven't been uploaded yet are dealt with in
|
||||
// uploadNewItems.
|
||||
protected void uploadUnreadChanges(final StageDelegate delegate) {
|
||||
@ -697,13 +801,13 @@ public class ReadingListSynchronizer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload unread changes, then upload new items, then call `done`.
|
||||
* Upload deletions and unread changes, then upload new items, then call `done`.
|
||||
* Substantially modified records are uploaded last.
|
||||
*
|
||||
* @param syncDelegate only used for status callbacks.
|
||||
*/
|
||||
private void syncUp(final ReadingListSynchronizerDelegate syncDelegate, final StageDelegate done) {
|
||||
// Second.
|
||||
// Third.
|
||||
final StageDelegate onNewItemsUploaded = new NextDelegate(executor) {
|
||||
@Override
|
||||
public void doNext() {
|
||||
@ -717,7 +821,7 @@ public class ReadingListSynchronizer {
|
||||
}
|
||||
};
|
||||
|
||||
// First.
|
||||
// Second.
|
||||
final StageDelegate onUnreadChangesUploaded = new NextDelegate(executor) {
|
||||
@Override
|
||||
public void doNext() {
|
||||
@ -732,8 +836,23 @@ public class ReadingListSynchronizer {
|
||||
}
|
||||
};
|
||||
|
||||
// First.
|
||||
final StageDelegate onDeletionsUploaded = new NextDelegate(executor) {
|
||||
@Override
|
||||
public void doNext() {
|
||||
syncDelegate.onDeletionsUploadComplete();
|
||||
uploadUnreadChanges(onUnreadChangesUploaded);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFail(Exception e) {
|
||||
Logger.warn(LOG_TAG, "Uploading deletions failed.", e);
|
||||
done.fail(e);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
uploadUnreadChanges(onUnreadChangesUploaded);
|
||||
uploadDeletions(onDeletionsUploaded);
|
||||
} catch (Exception ee) {
|
||||
done.fail(ee);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ public interface ReadingListSynchronizerDelegate {
|
||||
|
||||
// These are called sequentially, or not at all
|
||||
// if a failure occurs.
|
||||
void onDeletionsUploadComplete();
|
||||
void onStatusUploadComplete(Collection<String> uploaded, Collection<String> failed);
|
||||
void onNewItemUploadComplete(Collection<String> uploaded, Collection<String> failed);
|
||||
void onDownloadComplete();
|
||||
|
@ -2406,7 +2406,29 @@ var WalkerActor = protocol.ActorClass({
|
||||
this._orphaned = new Set();
|
||||
}
|
||||
|
||||
return pending;
|
||||
|
||||
// Clear out any duplicate attribute mutations before sending them over
|
||||
// the protocol. Keep only the most recent change for each attribute.
|
||||
let targetMap = {};
|
||||
let filtered = pending.reverse().filter(mutation => {
|
||||
if (mutation.type === "attributes") {
|
||||
if (!targetMap[mutation.target]) {
|
||||
targetMap[mutation.target] = {};
|
||||
}
|
||||
let attributesForTarget = targetMap[mutation.target];
|
||||
|
||||
if (attributesForTarget[mutation.attributeName]) {
|
||||
// Since the array was reversed, if we've seen this attribute already
|
||||
// then this one is a duplicate and can be skipped.
|
||||
return false;
|
||||
}
|
||||
|
||||
attributesForTarget[mutation.attributeName] = true;
|
||||
}
|
||||
return true;
|
||||
}).reverse();
|
||||
|
||||
return filtered;
|
||||
}, {
|
||||
request: {
|
||||
cleanup: Option(0)
|
||||
|
@ -7,7 +7,7 @@ const {Cc, Ci, Cu, Cr} = require("chrome");
|
||||
const Services = require("Services");
|
||||
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils.js");
|
||||
|
||||
let DEFAULT_PROFILER_ENTRIES = 1000000;
|
||||
let DEFAULT_PROFILER_ENTRIES = 10000000;
|
||||
let DEFAULT_PROFILER_INTERVAL = 1;
|
||||
let DEFAULT_PROFILER_FEATURES = ["js"];
|
||||
let DEFAULT_PROFILER_THREADFILTERS = ["GeckoMain"];
|
||||
|
@ -71,6 +71,7 @@ function WebConsoleActor(aConnection, aParentActor)
|
||||
this._netEvents = new Map();
|
||||
this._gripDepth = 0;
|
||||
this._listeners = new Set();
|
||||
this._lastConsoleInputEvaluation = undefined;
|
||||
|
||||
this._onWillNavigate = this._onWillNavigate.bind(this);
|
||||
this._onChangedToplevelDocument = this._onChangedToplevelDocument.bind(this);
|
||||
@ -358,6 +359,7 @@ WebConsoleActor.prototype =
|
||||
this._actorPool = null;
|
||||
|
||||
this._jstermHelpersCache = null;
|
||||
this._lastConsoleInputEvaluation = null;
|
||||
this._evalWindow = null;
|
||||
this._netEvents.clear();
|
||||
this.dbg.enabled = false;
|
||||
@ -503,6 +505,17 @@ WebConsoleActor.prototype =
|
||||
this._actorPool.removeActor(aActor.actorID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the latest web console input evaluation.
|
||||
* This is undefined if no evaluations have been completed.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
getLastConsoleInputEvaluation: function WCU_getLastConsoleInputEvaluation()
|
||||
{
|
||||
return this._lastConsoleInputEvaluation;
|
||||
},
|
||||
|
||||
//////////////////
|
||||
// Request handlers for known packet types.
|
||||
//////////////////
|
||||
@ -816,6 +829,8 @@ WebConsoleActor.prototype =
|
||||
errorMessage = e;
|
||||
}
|
||||
|
||||
this._lastConsoleInputEvaluation = result;
|
||||
|
||||
return {
|
||||
from: this.actorID,
|
||||
input: input,
|
||||
|
@ -45,10 +45,12 @@ addTest(setupAttrTest);
|
||||
addTest(testAddAttribute);
|
||||
addTest(testChangeAttribute);
|
||||
addTest(testRemoveAttribute);
|
||||
addTest(testQueuedMutations);
|
||||
addTest(setupFrameAttrTest);
|
||||
addTest(testAddAttribute);
|
||||
addTest(testChangeAttribute);
|
||||
addTest(testRemoveAttribute);
|
||||
addTest(testQueuedMutations);
|
||||
|
||||
function setupAttrTest() {
|
||||
attrNode = gInspectee.querySelector("#a")
|
||||
@ -85,10 +87,13 @@ function testAddAttribute() {
|
||||
}
|
||||
|
||||
function testChangeAttribute() {
|
||||
attrNode.setAttribute("data-newattr", "changedvalue");
|
||||
gWalker.once("mutations", () => {
|
||||
attrNode.setAttribute("data-newattr", "changedvalue1");
|
||||
attrNode.setAttribute("data-newattr", "changedvalue2");
|
||||
attrNode.setAttribute("data-newattr", "changedvalue3");
|
||||
gWalker.once("mutations", mutations => {
|
||||
is(mutations.length, 1, "Only one mutation is sent for multiple queued attribute changes");
|
||||
is(attrFront.attributes.length, 3, "Should have id and two new attributes.");
|
||||
is(attrFront.getAttribute("data-newattr"), "changedvalue", "Node front should have the changed first value");
|
||||
is(attrFront.getAttribute("data-newattr"), "changedvalue3", "Node front should have the changed first value");
|
||||
is(attrFront.getAttribute("data-newattr2"), "newvalue", "Second value should remain unchanged.");
|
||||
runNextTest();
|
||||
});
|
||||
@ -98,12 +103,44 @@ function testRemoveAttribute() {
|
||||
attrNode.removeAttribute("data-newattr2");
|
||||
gWalker.once("mutations", () => {
|
||||
is(attrFront.attributes.length, 2, "Should have id and one remaining attribute.");
|
||||
is(attrFront.getAttribute("data-newattr"), "changedvalue", "Node front should still have the first value");
|
||||
is(attrFront.getAttribute("data-newattr"), "changedvalue3", "Node front should still have the first value");
|
||||
ok(!attrFront.hasAttribute("data-newattr2"), "Second value should be removed.");
|
||||
runNextTest();
|
||||
})
|
||||
}
|
||||
|
||||
function testQueuedMutations() {
|
||||
// All modifications to each attribute should be queued in one mutation event.
|
||||
|
||||
attrNode.removeAttribute("data-newattr");
|
||||
attrNode.setAttribute("data-newattr", "1");
|
||||
attrNode.removeAttribute("data-newattr");
|
||||
attrNode.setAttribute("data-newattr", "2");
|
||||
attrNode.removeAttribute("data-newattr");
|
||||
|
||||
for (var i = 0; i <= 1000; i++) {
|
||||
attrNode.setAttribute("data-newattr2", i);
|
||||
}
|
||||
|
||||
attrNode.removeAttribute("data-newattr3");
|
||||
attrNode.setAttribute("data-newattr3", "1");
|
||||
attrNode.removeAttribute("data-newattr3");
|
||||
attrNode.setAttribute("data-newattr3", "2");
|
||||
attrNode.removeAttribute("data-newattr3");
|
||||
attrNode.setAttribute("data-newattr3", "3");
|
||||
|
||||
gWalker.once("mutations", mutations => {
|
||||
is(mutations.length, 3, "Only one mutation each is sent for multiple queued attribute changes");
|
||||
is(attrFront.attributes.length, 3, "Should have id, data-newattr2, and data-newattr3.");
|
||||
|
||||
is(attrFront.getAttribute("data-newattr2"), "1000", "Node front should still have the correct value");
|
||||
is(attrFront.getAttribute("data-newattr3"), "3", "Node front should still have the correct value");
|
||||
ok(!attrFront.hasAttribute("data-newattr"), "Attribute value should be removed.");
|
||||
|
||||
runNextTest();
|
||||
})
|
||||
}
|
||||
|
||||
addTest(function cleanup() {
|
||||
delete gInspectee;
|
||||
delete gWalker;
|
||||
|
@ -753,13 +753,8 @@ NetworkMonitor.prototype = {
|
||||
event.private = httpActivity.private;
|
||||
|
||||
// Determine if this is an XHR request.
|
||||
try {
|
||||
let callbacks = aChannel.notificationCallbacks;
|
||||
let xhrRequest = callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null;
|
||||
httpActivity.isXHR = event.isXHR = !!xhrRequest;
|
||||
} catch (e) {
|
||||
httpActivity.isXHR = event.isXHR = false;
|
||||
}
|
||||
httpActivity.isXHR = event.isXHR =
|
||||
(aChannel.loadInfo.contentPolicyType === Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST);
|
||||
|
||||
// Determine the HTTP version.
|
||||
aChannel.QueryInterface(Ci.nsIHttpChannelInternal);
|
||||
|
@ -15,6 +15,8 @@ support-files =
|
||||
[test_file_uri.html]
|
||||
[test_reflow.html]
|
||||
[test_jsterm.html]
|
||||
[test_jsterm_cd_iframe.html]
|
||||
[test_jsterm_last_result.html]
|
||||
[test_network_get.html]
|
||||
[test_network_longstring.html]
|
||||
[test_network_post.html]
|
||||
@ -26,4 +28,3 @@ support-files =
|
||||
[test_object_actor_native_getters_lenient_this.html]
|
||||
[test_page_errors.html]
|
||||
[test_throw.html]
|
||||
[test_jsterm_cd_iframe.html]
|
||||
|
@ -8,6 +8,10 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
|
||||
// This gives logging to stdout for tests
|
||||
var {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
|
||||
|
||||
let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
|
||||
let WebConsoleUtils = devtools.require("devtools/toolkit/webconsole/utils").Utils;
|
||||
|
130
toolkit/devtools/webconsole/test/test_jsterm_last_result.html
Normal file
130
toolkit/devtools/webconsole/test/test_jsterm_last_result.html
Normal file
@ -0,0 +1,130 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>Test for the $_ getter</title>
|
||||
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript;version=1.8" src="common.js"></script>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
</head>
|
||||
<body>
|
||||
<p>Test for the $_ getter</p>
|
||||
|
||||
<iframe id="content-iframe" src="http://example.com/chrome/toolkit/devtools/webconsole/test/sandboxed_iframe.html"></iframe>
|
||||
|
||||
<script class="testbody" type="text/javascript;version=1.8">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
let gState;
|
||||
|
||||
function evaluateJS(input, callback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
gState.client.evaluateJSAsync(input, response => {
|
||||
if (callback) {
|
||||
callback(response);
|
||||
}
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function startTest()
|
||||
{
|
||||
removeEventListener("load", startTest);
|
||||
attachConsole([], state => {
|
||||
gState = state;
|
||||
let tests = [checkUndefinedResult,checkAdditionResult,checkObjectResult];
|
||||
runTests(tests, testEnd);
|
||||
}, true);
|
||||
}
|
||||
|
||||
let checkUndefinedResult = Task.async(function*() {
|
||||
info ("$_ returns undefined if nothing has evaluated yet");
|
||||
let response = yield evaluateJS("$_");
|
||||
basicResultCheck(response, "$_", undefined);
|
||||
nextTest();
|
||||
});
|
||||
|
||||
let checkAdditionResult = Task.async(function*() {
|
||||
info ("$_ returns last value and performs basic arithmetic");
|
||||
let response = yield evaluateJS("2+2");
|
||||
basicResultCheck(response, "2+2", 4);
|
||||
|
||||
response = yield evaluateJS("$_");
|
||||
basicResultCheck(response, "$_", 4);
|
||||
|
||||
response = yield evaluateJS("$_ + 2");
|
||||
basicResultCheck(response, "$_ + 2", 6);
|
||||
|
||||
response = yield evaluateJS("$_ + 4");
|
||||
basicResultCheck(response, "$_ + 4", 10);
|
||||
|
||||
nextTest();
|
||||
});
|
||||
|
||||
let checkObjectResult = Task.async(function*() {
|
||||
info ("$_ has correct references to objects");
|
||||
|
||||
let response = yield evaluateJS("var foo = {bar:1}; foo;");
|
||||
basicResultCheck(response, "var foo = {bar:1}; foo;", {
|
||||
type: "object",
|
||||
class: "Object",
|
||||
actor: /[a-z]/,
|
||||
});
|
||||
checkObject(response.result.preview.ownProperties, {
|
||||
bar: {
|
||||
value: 1
|
||||
}
|
||||
});
|
||||
|
||||
response = yield evaluateJS("$_");
|
||||
basicResultCheck(response, "$_", {
|
||||
type: "object",
|
||||
class: "Object",
|
||||
actor: /[a-z]/,
|
||||
});
|
||||
checkObject(response.result.preview.ownProperties, {
|
||||
bar: {
|
||||
value: 1
|
||||
}
|
||||
});
|
||||
|
||||
top.foo.bar = 2;
|
||||
|
||||
response = yield evaluateJS("$_");
|
||||
basicResultCheck(response, "$_", {
|
||||
type: "object",
|
||||
class: "Object",
|
||||
actor: /[a-z]/,
|
||||
});
|
||||
checkObject(response.result.preview.ownProperties, {
|
||||
bar: {
|
||||
value: 2
|
||||
}
|
||||
});
|
||||
|
||||
nextTest();
|
||||
});
|
||||
|
||||
function basicResultCheck(response, input, output) {
|
||||
checkObject(response, {
|
||||
from: gState.actor,
|
||||
input: input,
|
||||
result: output,
|
||||
});
|
||||
ok(!response.exception, "no eval exception");
|
||||
ok(!response.helperResult, "no helper result");
|
||||
}
|
||||
|
||||
function testEnd()
|
||||
{
|
||||
closeDebugger(gState, function() {
|
||||
gState = null;
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
|
||||
addEventListener("load", startTest);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1549,6 +1549,20 @@ function JSTermHelpers(aOwner)
|
||||
return aOwner.window.document.querySelectorAll(aSelector);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the result of the last console input evaluation
|
||||
*
|
||||
* @return object|undefined
|
||||
* Returns last console evaluation or undefined
|
||||
*/
|
||||
Object.defineProperty(aOwner.sandbox, "$_", {
|
||||
get: function() {
|
||||
return aOwner.consoleActor.getLastConsoleInputEvaluation();
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Runs an xPath query and returns all matched nodes.
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user