Merge m-c to inbound

This commit is contained in:
Wes Kocher 2013-10-14 19:10:27 -07:00
commit 566d259e6b
32 changed files with 451 additions and 146 deletions

View File

@ -529,6 +529,14 @@ var shell = {
content.dispatchEvent(event);
},
sendCustomEvent: function shell_sendCustomEvent(type, details) {
let content = getContentWindow();
let event = content.document.createEvent('CustomEvent');
let payload = details ? ObjectWrapper.wrap(details, content) : {};
event.initCustomEvent(type, true, true, payload);
content.dispatchEvent(event);
},
sendChromeEvent: function shell_sendChromeEvent(details) {
if (!this.isHomeLoaded) {
if (!('pendingChromeEvents' in this)) {
@ -545,8 +553,7 @@ var shell = {
openAppForSystemMessage: function shell_openAppForSystemMessage(msg) {
let origin = Services.io.newURI(msg.manifest, null, null).prePath;
this.sendChromeEvent({
type: 'open-app',
let payload = {
url: msg.uri,
manifestURL: msg.manifest,
isActivity: (msg.type == 'activity'),
@ -555,7 +562,11 @@ var shell = {
target: msg.target,
expectingSystemMessage: true,
extra: msg.extra
});
}
this.sendCustomEvent('open-app', payload);
// XXX Remove once the gaia part of bug 924032 has landed.
payload.type = 'open-app';
this.sendChromeEvent(payload);
},
receiveMessage: function shell_receiveMessage(message) {
@ -947,12 +958,21 @@ var WebappsHelper = {
return;
let manifest = new ManifestHelper(aManifest, json.origin);
shell.sendChromeEvent({
"type": "webapps-launch",
"timestamp": json.timestamp,
"url": manifest.fullLaunchPath(json.startPoint),
"manifestURL": json.manifestURL
});
let payload = {
__exposedProps__: {
timestamp: "r",
url: "r",
manifestURL: "r"
},
timestamp: json.timestamp,
url: manifest.fullLaunchPath(json.startPoint),
manifestURL: json.manifestURL
}
shell.sendEvent(getContentWindow(), "webapps-launch", payload);
// XXX Remove once the gaia side of bug 924032 lands.
payload.type = "webapps-launch";
delete payload.__exposedProps__;
shell.sendChromeEvent(payload);
});
break;
case "webapps-ask-install":
@ -964,10 +984,14 @@ var WebappsHelper = {
});
break;
case "webapps-close":
shell.sendChromeEvent({
"type": "webapps-close",
"manifestURL": json.manifestURL
});
shell.sendEvent(shell.getContentWindow(), "webapps-close",
{
__exposedProps__: { "manifestURL": "r" },
"manifestURL": json.manifestURL
});
// XXX Remove once the gaia side of bug 924032 lands.
shell.sendChromeEvent({ type: "webapps-close",
"manifestURL": json.manifestURL });
break;
}
}

View File

@ -1,4 +1,4 @@
{
"revision": "4cdfe9307db3882da00e49361aa2f6788c9efe54",
"revision": "4a8dcbcad9433e7cb0b619d80b041219e5946130",
"repo_path": "/integration/gaia-central"
}

View File

@ -4137,7 +4137,7 @@
* close many tabs at once.
*/
let target = event.originalTarget;
if (target.className == 'tab-close-button') {
if (target.classList.contains('tab-close-button')) {
// We preemptively set this to allow the closing-multiple-tabs-
// in-a-row case.
if (this._blockDblClick) {

View File

@ -1143,7 +1143,7 @@ SourceScripts.prototype = {
if (item) {
DebuggerView.Sources.callMethod("checkItem", item.target, !isBlackBoxed);
}
DebuggerView.Sources.maybeShowBlackBoxMessage();
DebuggerView.maybeShowBlackBoxMessage();
},
/**
@ -1153,17 +1153,27 @@ SourceScripts.prototype = {
* The source form.
* @param bool aBlackBoxFlag
* True to black box the source, false to un-black box it.
* @returns Promise
* A promize that resolves to [aSource, isBlackBoxed] or rejects to
* [aSource, error].
*/
blackBox: function(aSource, aBlackBoxFlag) {
const sourceClient = this.activeThread.source(aSource);
sourceClient[aBlackBoxFlag ? "blackBox" : "unblackBox"](({ error, message }) => {
const deferred = promise.defer();
sourceClient[aBlackBoxFlag ? "blackBox" : "unblackBox"](aPacket => {
const { error, message } = aPacket;
if (error) {
let msg = "Couldn't toggle black boxing for " + aSource.url + ": " + message;
dumpn(msg);
Cu.reportError(msg);
return;
deferred.reject([aSource, msg]);
} else {
deferred.resolve([aSource, sourceClient.isBlackBoxed]);
}
});
return deferred.promise;
},
/**

View File

@ -50,7 +50,6 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
this._cmPopup = document.getElementById("sourceEditorContextMenu");
this._cbPanel = document.getElementById("conditional-breakpoint-panel");
this._cbTextbox = document.getElementById("conditional-breakpoint-panel-textbox");
this._editorDeck = document.getElementById("editor-deck");
this._stopBlackBoxButton = document.getElementById("black-boxed-message-button");
this._prettyPrintButton = document.getElementById("pretty-print");
@ -383,6 +382,10 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
* Pretty print the selected source.
*/
prettyPrint: function() {
if (this._prettyPrintButton.hasAttribute("disabled")) {
return;
}
const resetEditor = ([{ url }]) => {
// Only set the text when the source is still selected.
if (url == this.selectedValue) {
@ -397,9 +400,11 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
return;
}
let { source } = this.selectedItem.attachment;
let prettyPrinted = DebuggerController.SourceScripts.prettyPrint(source);
prettyPrinted.then(resetEditor, printError);
DebuggerView.showProgressBar();
const { source } = this.selectedItem.attachment;
DebuggerController.SourceScripts.prettyPrint(source)
.then(resetEditor, printError)
.then(DebuggerView.showEditor);
},
/**
@ -690,17 +695,21 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
let script = sourceItem.value.split(" -> ").pop();
document.title = L10N.getFormatStr("DebuggerWindowScriptTitle", script);
this.maybeShowBlackBoxMessage();
DebuggerView.maybeShowBlackBoxMessage();
this._updatePrettyPrintButtonState();
},
/**
* Show or hide the black box message vs. source editor depending on if the
* selected source is black boxed or not.
* Enable or disable the pretty print button depending on whether the selected
* source is black boxed or not.
*/
maybeShowBlackBoxMessage: function() {
let sourceForm = this.selectedItem.attachment.source;
let sourceClient = DebuggerController.activeThread.source(sourceForm);
this._editorDeck.selectedIndex = sourceClient.isBlackBoxed ? 1 : 0;
_updatePrettyPrintButtonState: function() {
const { source } = this.selectedItem.attachment;
if (gThreadClient.source(source).isBlackBoxed) {
this._prettyPrintButton.setAttribute("disabled", true);
} else {
this._prettyPrintButton.removeAttribute("disabled");
}
},
/**
@ -715,8 +724,23 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
* The check listener for the sources container.
*/
_onSourceCheck: function({ detail: { checked }, target }) {
let item = this.getItemForElement(target);
DebuggerController.SourceScripts.blackBox(item.attachment.source, !checked);
const shouldBlackBox = !checked;
// Be optimistic that the (un-)black boxing will succeed and enable/disable
// the pretty print button immediately. Then, once we actually get the
// results from the server, make sure that it is in the correct state again
// by calling `_updatePrettyPrintButtonState`.
if (shouldBlackBox) {
this._prettyPrintButton.setAttribute("disabled", true);
} else {
this._prettyPrintButton.removeAttribute("disabled");
}
const { source } = this.getItemForElement(target).attachment;
DebuggerController.SourceScripts.blackBox(source, shouldBlackBox)
.then(this._updatePrettyPrintButtonState,
this._updatePrettyPrintButtonState);
},
/**

View File

@ -114,6 +114,12 @@ let DebuggerView = {
this._instrumentsPane = document.getElementById("instruments-pane");
this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
this.showEditor = this.showEditor.bind(this);
this.showBlackBoxMessage = this.showBlackBoxMessage.bind(this);
this.showProgressBar = this.showProgressBar.bind(this);
this.maybeShowBlackBoxMessage = this.maybeShowBlackBoxMessage.bind(this);
this._editorDeck = document.getElementById("editor-deck");
this._onTabSelect = this._onInstrumentsPaneTabSelect.bind(this);
this._instrumentsPane.tabpanels.addEventListener("select", this._onTabSelect);
@ -221,6 +227,40 @@ let DebuggerView = {
});
},
/**
* Display the source editor.
*/
showEditor: function() {
this._editorDeck.selectedIndex = 0;
},
/**
* Display the black box message.
*/
showBlackBoxMessage: function() {
this._editorDeck.selectedIndex = 1;
},
/**
* Display the progress bar.
*/
showProgressBar: function() {
this._editorDeck.selectedIndex = 2;
},
/**
* Show or hide the black box message vs. source editor depending on if the
* selected source is black boxed or not.
*/
maybeShowBlackBoxMessage: function() {
let { source } = DebuggerView.Sources.selectedItem.attachment;
if (gThreadClient.source(source).isBlackBoxed) {
this.showBlackBoxMessage();
} else {
this.showEditor();
}
},
/**
* Sets the currently displayed text contents in the source editor.
* This resets the mode and undo stack.
@ -522,6 +562,7 @@ let DebuggerView = {
_instrumentsPaneToggleButton: null,
_collapsePaneString: "",
_expandPaneString: "",
_editorDeck: null,
};
/**

View File

@ -345,6 +345,10 @@
image="chrome://browser/skin/devtools/blackBoxMessageEye.png"
command="unBlackBoxCommand"/>
</vbox>
<vbox id="source-progress-container" align="center" pack="center">
<progressmeter id="source-progress"
mode="undetermined"/>
</vbox>
</deck>
<splitter class="devtools-side-splitter"/>
<tabbox id="instruments-pane"

View File

@ -122,6 +122,7 @@ support-files =
[browser_dbg_pretty-print-07.js]
[browser_dbg_pretty-print-08.js]
[browser_dbg_pretty-print-09.js]
[browser_dbg_pretty-print-10.js]
[browser_dbg_progress-listener-bug.js]
[browser_dbg_reload-preferred-script-01.js]
[browser_dbg_reload-preferred-script-02.js]

View File

@ -24,9 +24,11 @@ function test() {
.then(() => {
const finished = waitForSourceShown(gPanel, "code_ugly.js");
clickPrettyPrintButton();
testProgressBarShown();
return finished;
})
.then(testSourceIsPretty)
.then(testEditorShown)
.then(testSourceIsStillPretty)
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
@ -46,11 +48,21 @@ function clickPrettyPrintButton() {
gDebugger);
}
function testProgressBarShown() {
const deck = gDebugger.document.getElementById("editor-deck");
is(deck.selectedIndex, 2, "The progress bar should be shown");
}
function testSourceIsPretty() {
ok(gEditor.getText().contains("\n "),
"The source should be pretty printed.")
}
function testEditorShown() {
const deck = gDebugger.document.getElementById("editor-deck");
is(deck.selectedIndex, 0, "The editor should be shown");
}
function testSourceIsStillPretty() {
const deferred = promise.defer();

View File

@ -0,0 +1,67 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that we disable the pretty print button for black boxed sources,
* and that clicking it doesn't do anything.
*/
const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gEditor, gSources;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
gSources = gDebugger.DebuggerView.Sources;
waitForSourceShown(gPanel, "code_ugly.js")
.then(testSourceIsUgly)
.then(blackBoxSource)
.then(waitForThreadEvents.bind(null, gPanel, "blackboxchange"))
.then(clickPrettyPrintButton)
.then(testSourceIsStillUgly)
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
});
});
}
function testSourceIsUgly() {
ok(!gEditor.getText().contains("\n "),
"The source shouldn't be pretty printed yet.");
}
function blackBoxSource() {
const checkbox = gDebugger.document.querySelector(
".selected .side-menu-widget-item-checkbox");
checkbox.click();
}
function clickPrettyPrintButton() {
EventUtils.sendMouseEvent({ type: "click" },
gDebugger.document.getElementById("pretty-print"),
gDebugger);
}
function testSourceIsStillUgly() {
const { source } = gSources.selectedItem.attachment;
return gDebugger.DebuggerController.SourceScripts.getText(source).then(([, text]) => {
ok(!text.contains("\n "));
});
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;
gSources = null;
});

View File

@ -30,4 +30,5 @@ support-files =
[browser_bug731721_debugger_stepping.js]
[browser_bug744021_next_prev_bracket_jump.js]
[browser_codemirror.js]
skip-if = os == "linux"
[browser_sourceeditor_initialization.js]

View File

@ -26,6 +26,10 @@
<field name="controller">null</field>
<!-- collection of child items excluding empty tiles -->
<property name="items" readonly="true" onget="return this.querySelectorAll('richgriditem');"/>
<property name="itemCount" readonly="true" onget="return this.items.length;"/>
<!-- nsIDOMXULMultiSelectControlElement (not fully implemented) -->
<method name="clearSelection">
@ -158,8 +162,6 @@
<!-- nsIDOMXULSelectControlElement -->
<property name="itemCount" readonly="true" onget="return this.children.length;"/>
<field name="_selectedItem">null</field>
<property name="selectedItem" onget="return this._selectedItem;">
<setter>
@ -202,7 +204,7 @@
<parameter name="aSkipArrange"/>
<body>
<![CDATA[
let addition = this.createItemElement(aLabel, aValue);
let addition = this._createItemElement(aLabel, aValue);
this.appendChild(addition);
if (!aSkipArrange)
this.arrangeItems();
@ -232,7 +234,7 @@
<body>
<![CDATA[
let existing = this.getItemAtIndex(anIndex);
let addition = this.createItemElement(aLabel, aValue);
let addition = this._createItemElement(aLabel, aValue);
if (existing) {
this.insertBefore(addition, existing);
} else {
@ -250,7 +252,9 @@
<body>
<![CDATA[
let item = this.getItemAtIndex(anIndex);
return item ? this.removeItem(item, aSkipArrange) : null;
if (!item)
return null;
return this.removeItem(item, aSkipArrange);
]]>
</body>
</method>
@ -260,7 +264,9 @@
<parameter name="aSkipArrange"/>
<body>
<![CDATA[
let removal = aItem.parentNode == this && this.removeChild(aItem);
if (!aItem || Array.indexOf(this.items, aItem) < 0)
return null;
let removal = this.removeChild(aItem);
if (removal && !aSkipArrange)
this.arrangeItems();
@ -271,7 +277,6 @@
</body>
</method>
<method name="getIndexOfItem">
<parameter name="anItem"/>
<body>
@ -279,7 +284,7 @@
if (!anItem)
return -1;
return Array.indexOf(this.children, anItem);
return Array.indexOf(this.items, anItem);
]]>
</body>
</method>
@ -290,7 +295,7 @@
<![CDATA[
if (!this._isIndexInBounds(anIndex))
return null;
return this.children.item(anIndex);
return this.items.item(anIndex);
]]>
</body>
</method>
@ -609,16 +614,18 @@
</body>
</method>
<method name="createItemElement">
<method name="_createItemElement">
<parameter name="aLabel"/>
<parameter name="aValue"/>
<body>
<![CDATA[
let item = this.ownerDocument.createElement("richgriditem");
item.setAttribute("label", aLabel);
if (aValue) {
item.setAttribute("value", aValue);
}
if (aLabel) {
item.setAttribute("label", aLabel);
}
if(this.hasAttribute("tiletype")) {
item.setAttribute("tiletype", this.getAttribute("tiletype"));
}
@ -860,7 +867,7 @@
<property name="control">
<getter><![CDATA[
let parent = this.parentNode;
while (parent) {
while (parent && parent != this.ownerDocument.documentElement) {
if (parent instanceof Components.interfaces.nsIDOMXULSelectControlElement)
return parent;
parent = parent.parentNode;

View File

@ -100,7 +100,7 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
case "pin":
let pinIndices = [];
Array.forEach(selectedTiles, function(aNode) {
pinIndices.push( Array.indexOf(aNode.control.children, aNode) );
pinIndices.push( Array.indexOf(aNode.control.items, aNode) );
aNode.contextActions.delete('pin');
aNode.contextActions.add('unpin');
});
@ -153,7 +153,6 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
// flush, recreate all
this.isUpdating = true;
// destroy and recreate all item nodes, skip calling arrangeItems
grid.clearAll(true);
this.populateGrid();
}
},
@ -165,14 +164,18 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
let filepath = PageThumbsStorage.getFilePathForURL(aSite.url);
if (yield OS.File.exists(filepath)) {
aSite.backgroundImage = 'url("'+PageThumbs.getThumbnailURL(aSite.url)+'")';
aTileNode.setAttribute("customImage", aSite.backgroundImage);
if (aTileNode.refresh) {
aTileNode.refresh()
if ('backgroundImage' in aTileNode) {
aTileNode.backgroundImage = aSite.backgroundImage;
} else {
aTileNode.setAttribute("customImage", aSite.backgroundImage);
}
}
});
aSite.applyToTileNode(aTileNode);
if (aTileNode.refresh) {
aTileNode.refresh();
}
if (aArrangeGrid) {
this._set.arrangeItems();
}
@ -182,24 +185,19 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
this.isUpdating = true;
let sites = TopSites.getSites();
let length = Math.min(sites.length, this._topSitesMax || Infinity);
let tileset = this._set;
// if we're updating with a collection that is smaller than previous
// remove any extra tiles
while (tileset.children.length > length) {
tileset.removeChild(tileset.children[tileset.children.length -1]);
if (this._topSitesMax) {
sites = sites.slice(0, this._topSitesMax);
}
let tileset = this._set;
tileset.clearAll(true);
for (let idx=0; idx < length; idx++) {
let isNew = !tileset.children[idx],
site = sites[idx];
let item = isNew ? tileset.createItemElement(site.title, site.url) : tileset.children[idx];
for (let site of sites) {
// call to private _createItemElement is a temp measure
// we'll eventually just request the next slot
let item = tileset._createItemElement(site.title, site.url);
this.updateTile(item, site);
if (isNew) {
tileset.appendChild(item);
}
tileset.appendChild(item);
}
tileset.arrangeItems();
this.isUpdating = false;

View File

@ -18,8 +18,8 @@ gTests.push({
ok(grid, "#grid1 is found");
is(typeof grid.clearSelection, "function", "#grid1 has the binding applied");
is(grid.children.length, 2, "#grid1 has a 2 items");
is(grid.children[0].control, grid, "#grid1 item's control points back at #grid1'");
is(grid.items.length, 2, "#grid1 has a 2 items");
is(grid.items[0].control, grid, "#grid1 item's control points back at #grid1'");
}
});
@ -29,7 +29,7 @@ gTests.push({
let grid = doc.querySelector("#grid1");
is(typeof grid.handleItemClick, "function", "grid.handleItemClick is a function");
let handleStub = stubMethod(grid, 'handleItemClick');
let itemId = "grid1_item1"; // grid.children[0].getAttribute("id");
let itemId = "grid1_item1"; // grid.items[0].getAttribute("id");
// send click to item and wait for next tick;
EventUtils.sendMouseEvent({type: 'click'}, itemId, doc.defaultView);
@ -114,9 +114,8 @@ gTests.push({
grid.clearAll();
is(grid.itemCount, 0, "grid has 0 itemCount after clearAll");
is(grid.children.length, 0, "grid has 0 children after clearAll");
is(grid.rowCount, 0, "grid has 0 rows when empty");
is(grid.columnCount, 0, "grid has 0 cols when empty");
is(grid.items.length, 0, "grid has 0 items after clearAll");
// now that we use slots, an empty grid may still have non-zero rows & columns
is(arrangeSpy.callCount, 1, "arrangeItems is called once when we clearAll");
arrangeSpy.restore();
@ -150,13 +149,13 @@ gTests.push({
let grid = doc.querySelector("#emptygrid");
is(grid.itemCount, 0, "0 itemCount when empty");
is(grid.children.length, 0, "0 children when empty");
is(grid.items.length, 0, "0 items when empty");
is(typeof grid.appendItem, "function", "appendItem is a function on the grid");
let arrangeStub = stubMethod(grid, "arrangeItems");
let newItem = grid.appendItem("test title", "about:blank");
ok(newItem && grid.children[0]==newItem, "appendItem gives back the item");
ok(newItem && grid.items[0]==newItem, "appendItem gives back the item");
is(grid.itemCount, 1, "itemCount is incremented when we appendItem");
is(newItem.getAttribute("label"), "test title", "title ends up on label attribute");
is(newItem.getAttribute("value"), "about:blank", "url ends up on value attribute");
@ -193,7 +192,7 @@ gTests.push({
ok(removedItem, "removeItemAt gives back an item");
is(removedItem.getAttribute("id"), "grid2_item1", "removeItemAt gives back the correct item");
is(grid.children[0].getAttribute("id"), "grid2_item2", "2nd item becomes the first item");
is(grid.items[0].getAttribute("id"), "grid2_item2", "2nd item becomes the first item");
is(grid.itemCount, 1, "itemCount is decremented when we removeItemAt");
is(arrangeStub.callCount, 1, "arrangeItems is called when we removeItemAt");
@ -215,10 +214,10 @@ gTests.push({
let insertedItem = grid.insertItemAt(1, "inserted item", "http://example.com/inserted");
ok(insertedItem, "insertItemAt gives back an item");
is(grid.children[1], insertedItem, "item is inserted at the correct index");
is(grid.items[1], insertedItem, "item is inserted at the correct index");
is(insertedItem.getAttribute("label"), "inserted item", "insertItemAt creates item with the correct label");
is(insertedItem.getAttribute("value"), "http://example.com/inserted", "insertItemAt creates item with the correct url value");
is(grid.children[2].getAttribute("id"), "grid3_item2", "following item ends up at the correct index");
is(grid.items[2].getAttribute("id"), "grid3_item2", "following item ends up at the correct index");
is(grid.itemCount, 3, "itemCount is incremented when we insertItemAt");
is(arrangeStub.callCount, 1, "arrangeItems is called when we insertItemAt");
@ -275,11 +274,11 @@ gTests.push({
is(typeof grid.removeItem, "function", "removeItem is a function on the grid");
let arrangeStub = stubMethod(grid, "arrangeItems");
let removedFirst = grid.removeItem( grid.children[0] );
let removedFirst = grid.removeItem( grid.items[0] );
is(arrangeStub.callCount, 1, "arrangeItems is called when we removeItem");
let removed2nd = grid.removeItem( grid.children[0], true);
let removed2nd = grid.removeItem( grid.items[0], true);
is(removed2nd.getAttribute("label"), "2nd item", "the next item was returned");
is(grid.itemCount, 2, "2 items remain");
@ -316,23 +315,23 @@ gTests.push({
is(grid.itemCount, 2, "2 items initially");
is(grid.selectedItems.length, 0, "nothing selected initially");
grid.toggleItemSelection(grid.children[1]);
ok(grid.children[1].selected, "toggleItemSelection sets truthy selected prop on previously-unselected item");
grid.toggleItemSelection(grid.items[1]);
ok(grid.items[1].selected, "toggleItemSelection sets truthy selected prop on previously-unselected item");
is(grid.selectedIndex, 1, "selectedIndex is correct");
grid.toggleItemSelection(grid.children[1]);
ok(!grid.children[1].selected, "toggleItemSelection sets falsy selected prop on previously-selected item");
grid.toggleItemSelection(grid.items[1]);
ok(!grid.items[1].selected, "toggleItemSelection sets falsy selected prop on previously-selected item");
is(grid.selectedIndex, -1, "selectedIndex reports correctly with nothing selected");
// item selection
grid.selectItem(grid.children[1]);
ok(grid.children[1].selected, "Item selected property is truthy after grid.selectItem");
ok(grid.children[1].getAttribute("selected"), "Item selected attribute is truthy after grid.selectItem");
grid.selectItem(grid.items[1]);
ok(grid.items[1].selected, "Item selected property is truthy after grid.selectItem");
ok(grid.items[1].getAttribute("selected"), "Item selected attribute is truthy after grid.selectItem");
ok(grid.selectedItems.length, "There are selectedItems after grid.selectItem");
// clearSelection
grid.selectItem(grid.children[0]);
grid.selectItem(grid.children[1]);
grid.selectItem(grid.items[0]);
grid.selectItem(grid.items[1]);
grid.clearSelection();
is(grid.selectedItems.length, 0, "Nothing selected when we clearSelection");
is(grid.selectedIndex, -1, "selectedIndex resets after clearSelection");
@ -347,10 +346,10 @@ gTests.push({
doc.defaultView.addEventListener("select", handler, false);
info("select listener added");
info("calling selectItem, currently it is:" + grid.children[0].selected);
info("calling selectItem, currently it is:" + grid.items[0].selected);
// Note: A richgrid in seltype=single mode fires "select" events from selectItem
grid.selectItem(grid.children[0]);
info("calling selectItem, now it is:" + grid.children[0].selected);
grid.selectItem(grid.items[0]);
info("calling selectItem, now it is:" + grid.items[0].selected);
yield waitForMs(0);
is(handlerStub.callCount, 1, "select event handler was called when we selected an item");
@ -378,22 +377,22 @@ gTests.push({
is(grid.itemCount, 2, "2 items initially");
is(grid.selectedItems.length, 0, "nothing selected initially");
grid.toggleItemSelection(grid.children[1]);
ok(grid.children[1].selected, "toggleItemSelection sets truthy selected prop on previously-unselected item");
grid.toggleItemSelection(grid.items[1]);
ok(grid.items[1].selected, "toggleItemSelection sets truthy selected prop on previously-unselected item");
is(grid.selectedItems.length, 1, "1 item selected when we first toggleItemSelection");
is(grid.selectedItems[0], grid.children[1], "the right item is selected");
is(grid.selectedItems[0], grid.items[1], "the right item is selected");
is(grid.selectedIndex, 1, "selectedIndex is correct");
grid.toggleItemSelection(grid.children[1]);
grid.toggleItemSelection(grid.items[1]);
is(grid.selectedItems.length, 0, "Nothing selected when we toggleItemSelection again");
// clearSelection
grid.children[0].selected=true;
grid.children[1].selected=true;
grid.items[0].selected=true;
grid.items[1].selected=true;
is(grid.selectedItems.length, 2, "Both items are selected before calling clearSelection");
grid.clearSelection();
is(grid.selectedItems.length, 0, "Nothing selected when we clearSelection");
ok(!(grid.children[0].selected || grid.children[1].selected), "selected properties all falsy when we clearSelection");
ok(!(grid.items[0].selected || grid.items[1].selected), "selected properties all falsy when we clearSelection");
// selectionchange events
// in seltype=multiple mode, we track selected state on all items
@ -405,10 +404,10 @@ gTests.push({
doc.defaultView.addEventListener("selectionchange", handler, false);
info("selectionchange listener added");
info("calling toggleItemSelection, currently it is:" + grid.children[0].selected);
info("calling toggleItemSelection, currently it is:" + grid.items[0].selected);
// Note: A richgrid in seltype=single mode fires "select" events from selectItem
grid.toggleItemSelection(grid.children[0]);
info("/calling toggleItemSelection, now it is:" + grid.children[0].selected);
grid.toggleItemSelection(grid.items[0]);
info("/calling toggleItemSelection, now it is:" + grid.items[0].selected);
yield waitForMs(0);
is(handlerStub.callCount, 1, "selectionchange event handler was called when we selected an item");
@ -418,6 +417,3 @@ gTests.push({
doc.defaultView.removeEventListener("selectionchange", handler, false);
}
});
// implements a getItemAtIndex method (or grid.children[idx] ?)

View File

@ -194,7 +194,7 @@ gTests.push({
},
run: function() {
let grid = TopSitesTestHelper.grid;
let items = grid.children;
let items = grid.items;
is(items.length, 8, "should be 8 topsites"); // i.e. not 10
if(items.length) {
let firstitem = items[0];
@ -230,7 +230,7 @@ gTests.push({
run: function() {
// test that pinned state of each site as rendered matches our expectations
let pins = this.pins.split(",");
let items = TopSitesTestHelper.grid.children;
let items = TopSitesTestHelper.grid.items;
is(items.length, 8, "should be 8 topsites in the grid");
is(TopSitesTestHelper.document.querySelectorAll("#start-topsites-grid > [pinned]").length, 3, "should be 3 children with 'pinned' attribute");
@ -273,10 +273,10 @@ gTests.push({
// test that site is pinned as expected
// and that sites fill positions around it
let grid = TopSitesTestHelper.grid,
items = grid.children;
items = grid.items;
is(items.length, 4, this.desc + ": should be 4 topsites");
let tile = grid.children[2],
let tile = grid.items[2],
url = tile.getAttribute("value"),
title = tile.getAttribute("label");
@ -291,7 +291,7 @@ gTests.push({
return !grid.controller.isUpdating;
});
let thirdTile = grid.children[2];
let thirdTile = grid.items[2];
ok( thirdTile.hasAttribute("pinned"), thirdTile.getAttribute("value")+ " should look pinned" );
// visit some more sites
@ -329,7 +329,7 @@ gTests.push({
// unpin a pinned site
// test that sites are unpinned as expected
let grid = TopSitesTestHelper.grid,
items = grid.children;
items = grid.items;
is(items.length, 8, this.desc + ": should be 8 topsites");
let site = {
url: items[1].getAttribute("value"),
@ -346,7 +346,7 @@ gTests.push({
return !grid.controller.isUpdating;
});
let secondTile = grid.children[1];
let secondTile = grid.items[1];
ok( !secondTile.hasAttribute("pinned"), "2nd item should no longer be marked as pinned" );
ok( !NewTabUtils.pinnedLinks.isPinned(site), "2nd item should no longer be pinned" );
}
@ -371,7 +371,7 @@ gTests.push({
// block a site
// test that sites are removed from the grid as expected
let grid = TopSitesTestHelper.grid,
items = grid.children;
items = grid.items;
is(items.length, 8, this.desc + ": should be 8 topsites");
let brianSite = TopSitesTestHelper.siteFromNode(items[0]);
@ -426,7 +426,7 @@ gTests.push({
is( grid.querySelectorAll("[value='"+dougalSite.url+"']").length, 1, "Unblocked site is back in the grid");
// ..and that a previously pinned site is re-pinned after being blocked, then restored
ok( NewTabUtils.pinnedLinks.isPinned(dougalSite), "Restoring previously pinned site makes it pinned again" );
is( grid.children[1].getAttribute("value"), dougalSite.url, "Blocked Site restored to pinned index" );
is( grid.items[1].getAttribute("value"), dougalSite.url, "Blocked Site restored to pinned index" );
ok( !NewTabUtils.blockedLinks.isBlocked(dylanSite), "site was unblocked" );
is( grid.querySelectorAll("[value='"+dylanSite.url+"']").length, 1, "Unblocked site is back in the grid");
@ -458,7 +458,7 @@ gTests.push({
// delete a both pinned and unpinned sites
// test that sites are removed from the grid
let grid = TopSitesTestHelper.grid,
items = grid.children;
items = grid.items;
is(items.length, 4, this.desc + ": should be 4 topsites");
let brianTile = grid.querySelector('richgriditem[value$="brian"]');

View File

@ -66,9 +66,10 @@
display: none;
}
/* Black box message */
/* Black box message and source progress meter */
#black-boxed-message {
#black-boxed-message,
#source-progress-container {
background: url(background-noise-toolbar.png) rgb(61,69,76);
/* Prevent the container deck from aquiring the height from this message. */
min-height: 1px;
@ -76,6 +77,11 @@
color: white;
}
#source-progress {
min-height: 2em;
min-width: 40em;
}
#black-boxed-message-label,
#black-boxed-message-button {
text-align: center;

View File

@ -64,9 +64,10 @@
display: none;
}
/* Black box message */
/* Black box message and source progress meter */
#black-boxed-message {
#black-boxed-message,
#source-progress-container {
background: url(background-noise-toolbar.png) rgb(61,69,76);
/* Prevent the container deck from aquiring the height from this message. */
min-height: 1px;
@ -74,6 +75,11 @@
color: white;
}
#source-progress {
min-height: 2em;
min-width: 40em;
}
#black-boxed-message-label,
#black-boxed-message-button {
text-align: center;

View File

@ -64,9 +64,10 @@
display: none;
}
/* Black box message */
/* Black box message and source progress meter */
#black-boxed-message {
#black-boxed-message,
#source-progress-container {
background: url(background-noise-toolbar.png) rgb(61,69,76);
/* Prevent the container deck from aquiring the height from this message. */
min-height: 1px;
@ -74,6 +75,11 @@
color: white;
}
#source-progress {
min-height: 2em;
min-width: 40em;
}
#black-boxed-message-label,
#black-boxed-message-button {
text-align: center;

View File

@ -340,6 +340,26 @@ abstract public class BrowserApp extends GeckoApp
});
}
void handleReaderListStatusRequest(final String url) {
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
final int inReadingList = BrowserDB.isReadingListItem(getContentResolver(), url) ? 1 : 0;
final JSONObject json = new JSONObject();
try {
json.put("url", url);
json.put("inReadingList", inReadingList);
} catch (JSONException e) {
Log.e(LOGTAG, "JSON error - failed to return inReadingList status", e);
return;
}
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:ListStatusReturn", json.toString()));
}
});
}
void handleReaderAdded(int result, final String title, final String url) {
if (result != READER_ADD_SUCCESS) {
if (result == READER_ADD_FAILED) {
@ -1104,6 +1124,8 @@ abstract public class BrowserApp extends GeckoApp
Telemetry.HistogramAdd("FENNEC_THUMBNAILS_COUNT", BrowserDB.getCount(getContentResolver(), "thumbnails"));
} else if (event.equals("Reader:ListCountRequest")) {
handleReaderListCountRequest();
} else if (event.equals("Reader:ListStatusRequest")) {
handleReaderListStatusRequest(message.getString("url"));
} else if (event.equals("Reader:Added")) {
final int result = message.getInt("result");
final String title = message.getString("title");

View File

@ -1461,6 +1461,7 @@ abstract public class GeckoApp
//register for events
registerEventListener("log");
registerEventListener("Reader:ListCountRequest");
registerEventListener("Reader:ListStatusRequest");
registerEventListener("Reader:Added");
registerEventListener("Reader:Removed");
registerEventListener("Reader:Share");
@ -1996,6 +1997,7 @@ abstract public class GeckoApp
{
unregisterEventListener("log");
unregisterEventListener("Reader:ListCountRequest");
unregisterEventListener("Reader:ListStatusRequest");
unregisterEventListener("Reader:Added");
unregisterEventListener("Reader:Removed");
unregisterEventListener("Reader:Share");

View File

@ -33,6 +33,7 @@ public class NotificationHandler {
* one download is in progress.
*/
private Notification mForegroundNotification;
private int mForegroundNotificationId;
public NotificationHandler(Context context) {
mContext = context;
@ -168,23 +169,26 @@ public class NotificationHandler {
}
protected void setForegroundNotification(int id, Notification notification) {
mForegroundNotificationId = id;
mForegroundNotification = notification;
}
private void updateForegroundNotification(int id, Notification oldNotification) {
if (mForegroundNotification == oldNotification) {
private void updateForegroundNotification(int oldId, Notification oldNotification) {
if (mForegroundNotificationId == oldId) {
// If we're removing the notification associated with the
// foreground, we need to pick another active notification to act
// as the foreground notification.
Notification foregroundNotification = null;
for (final Notification notification : mNotifications.values()) {
int foregroundId = 0;
for (final Integer id : mNotifications.keySet()) {
final Notification notification = mNotifications.get(id);
if (isOngoing(notification)) {
foregroundNotification = notification;
foregroundId = id;
break;
}
}
setForegroundNotification(id, foregroundNotification);
setForegroundNotification(foregroundId, foregroundNotification);
}
}
}

View File

@ -62,13 +62,12 @@ public class ReaderModeUtils {
return urlFromAboutReader.equals(currentUrl);
}
public static String getAboutReaderForUrl(String url, boolean inReadingList) {
return getAboutReaderForUrl(url, -1, inReadingList);
public static String getAboutReaderForUrl(String url) {
return getAboutReaderForUrl(url, -1);
}
public static String getAboutReaderForUrl(String url, int tabId, boolean inReadingList) {
String aboutReaderUrl = "about:reader?url=" + Uri.encode(url) +
"&readingList=" + (inReadingList ? 1 : 0);
public static String getAboutReaderForUrl(String url, int tabId) {
String aboutReaderUrl = "about:reader?url=" + Uri.encode(url);
if (tabId >= 0)
aboutReaderUrl += "&tabId=" + tabId;

View File

@ -464,7 +464,7 @@ public class Tab {
Tabs.getInstance().loadUrl(ReaderModeUtils.getUrlFromAboutReader(mUrl));
} else if (mReaderEnabled) {
mEnteringReaderMode = true;
Tabs.getInstance().loadUrl(ReaderModeUtils.getAboutReaderForUrl(mUrl, mId, mReadingListItem));
Tabs.getInstance().loadUrl(ReaderModeUtils.getAboutReaderForUrl(mUrl, mId));
}
}

View File

@ -351,6 +351,7 @@ public class Favicons {
"/favicon.ico", null,
null).toString();
} catch (URISyntaxException e) {
Log.e(LOGTAG, "URISyntaxException getting default favicon URL", e);
return null;
}
}

View File

@ -234,6 +234,10 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
} else {
// If we don't have a stored one, fall back to the default.
mFaviconUrl = Favicons.guessDefaultFaviconURL(mPageUrl);
if (TextUtils.isEmpty(mFaviconUrl)) {
return null;
}
isUsingDefaultURL = true;
}
}

View File

@ -205,6 +205,10 @@ public class FaviconCache {
* @return true if this favicon is blacklisted, false otherwise.
*/
public boolean isFailedFavicon(String faviconURL) {
if (faviconURL == null) {
return true;
}
startRead();
boolean isExpired = false;
@ -241,7 +245,7 @@ public class FaviconCache {
// Flag to prevent finally from doubly-unlocking.
isAborting = true;
Log.e(LOGTAG, "FaviconCache exception!", unhandled);
return false;
return true;
} finally {
if (!isAborting) {
if (isExpired) {

View File

@ -153,7 +153,7 @@ abstract class HomeFragment extends Fragment {
if (item.getItemId() == R.id.home_open_private_tab)
flags |= Tabs.LOADURL_PRIVATE;
final String url = (info.inReadingList ? ReaderModeUtils.getAboutReaderForUrl(info.url, true) : info.url);
final String url = (info.inReadingList ? ReaderModeUtils.getAboutReaderForUrl(info.url) : info.url);
Tabs.getInstance().loadUrl(url, flags);
Toast.makeText(context, R.string.new_tab_opened, Toast.LENGTH_SHORT).show();
return true;
@ -166,7 +166,7 @@ abstract class HomeFragment extends Fragment {
}
if (itemId == R.id.home_open_in_reader) {
final String url = ReaderModeUtils.getAboutReaderForUrl(info.url, true);
final String url = ReaderModeUtils.getAboutReaderForUrl(info.url);
Tabs.getInstance().loadUrl(url, Tabs.LOADURL_NONE);
return true;
}

View File

@ -101,7 +101,7 @@ public class ReadingListPage extends HomeFragment {
}
String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
url = ReaderModeUtils.getAboutReaderForUrl(url, true);
url = ReaderModeUtils.getAboutReaderForUrl(url);
// This item is a TwoLinePageRow, so we allow switch-to-tab.
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));

View File

@ -58,10 +58,12 @@ public class testBookmark extends AboutHomeTest {
boolean bookmarked = waitForTest(new BooleanTest() {
@Override
public boolean test() {
return mDatabaseHelper.isBookmark(BOOKMARK_URL);
return (isBookmarked) ?
mDatabaseHelper.isBookmark(BOOKMARK_URL) :
!mDatabaseHelper.isBookmark(BOOKMARK_URL);
}
}, WAIT_FOR_BOOKMARKED_TIMEOUT);
mAsserter.is(bookmarked, isBookmarked, BOOKMARK_URL + " was " + (bookmarked ? "added as a bookmark" : "removed from bookmarks"));
mAsserter.is(bookmarked, true, BOOKMARK_URL + " was " + (isBookmarked ? "added as a bookmark" : "removed from bookmarks"));
}
private void setUpBookmark() {

View File

@ -33,6 +33,7 @@ let AboutReader = function(doc, win) {
Services.obs.addObserver(this, "Reader:Remove", false);
Services.obs.addObserver(this, "Reader:ListCountReturn", false);
Services.obs.addObserver(this, "Reader:ListCountUpdated", false);
Services.obs.addObserver(this, "Reader:ListStatusReturn", false);
this._article = null;
@ -119,11 +120,12 @@ let AboutReader = function(doc, win) {
dump("Decoding query arguments");
let queryArgs = this._decodeQueryString(win.location.href);
this._isReadingListItem = (queryArgs.readingList == "1");
// Track status of reader toolbar add/remove toggle button
this._isReadingListItem = -1;
this._updateToggleButton();
// Track status of reader toolbar list button
this._readingListCount = 0;
this._readingListCount = -1;
this._updateListButton();
this._requestReadingListCount();
@ -192,8 +194,8 @@ AboutReader.prototype = {
case "Reader:Add": {
let args = JSON.parse(aData);
if (args.url == this._article.url) {
if (!this._isReadingListItem) {
this._isReadingListItem = true;
if (this._isReadingListItem != 1) {
this._isReadingListItem = 1;
this._updateToggleButton();
}
}
@ -202,8 +204,8 @@ AboutReader.prototype = {
case "Reader:Remove": {
if (aData == this._article.url) {
if (this._isReadingListItem) {
this._isReadingListItem = false;
if (this._isReadingListItem != 0) {
this._isReadingListItem = 0;
this._updateToggleButton();
}
}
@ -211,11 +213,39 @@ AboutReader.prototype = {
}
case "Reader:ListCountReturn":
case "Reader:ListCountUpdated": {
case "Reader:ListCountUpdated": {
let count = parseInt(aData);
if (this._readingListCount != count) {
let isInitialStateChange = (this._readingListCount == -1);
this._readingListCount = count;
this._updateListButton();
// Display the toolbar when all its initial component states are known
if (isInitialStateChange) {
this._setToolbarVisibility(true);
}
// Initial readinglist count is requested before any page is displayed
if (this._article) {
this._requestReadingListStatus();
}
}
break;
}
case "Reader:ListStatusReturn": {
let args = JSON.parse(aData);
if (args.url == this._article.url) {
if (this._isReadingListItem != args.inReadingList) {
let isInitialStateChange = (this._isReadingListItem == -1);
this._isReadingListItem = args.inReadingList;
this._updateToggleButton();
// Display the toolbar when all its initial component states are known
if (isInitialStateChange) {
this._setToolbarVisibility(true);
}
}
}
break;
}
@ -257,6 +287,7 @@ AboutReader.prototype = {
Services.obs.removeObserver(this, "Reader:Remove");
Services.obs.removeObserver(this, "Reader:ListCountReturn");
Services.obs.removeObserver(this, "Reader:ListCountUpdated");
Services.obs.removeObserver(this, "Reader:ListStatusReturn");
break;
}
},
@ -264,7 +295,7 @@ AboutReader.prototype = {
_updateToggleButton: function Reader_updateToggleButton() {
let classes = this._doc.getElementById("toggle-button").classList;
if (this._isReadingListItem) {
if (this._isReadingListItem == 1) {
classes.add("on");
} else {
classes.remove("on");
@ -285,14 +316,21 @@ AboutReader.prototype = {
gChromeWin.sendMessageToJava({ type: "Reader:ListCountRequest" });
},
_requestReadingListStatus: function Reader_requestReadingListStatus() {
gChromeWin.sendMessageToJava({
type: "Reader:ListStatusRequest",
url: this._article.url
});
},
_onReaderToggle: function Reader_onToggle() {
if (!this._article)
return;
this._isReadingListItem = !this._isReadingListItem;
this._isReadingListItem = (this._isReadingListItem == 1) ? 0 : 1;
this._updateToggleButton();
if (this._isReadingListItem) {
if (this._isReadingListItem == 1) {
gChromeWin.Reader.storeArticleInCache(this._article, function(success) {
dump("Reader:Add (in reader) success=" + success);
@ -324,7 +362,7 @@ AboutReader.prototype = {
},
_onList: function Reader_onList() {
if (!this._article || this._readingListCount == 0)
if (!this._article || this._readingListCount < 1)
return;
gChromeWin.sendMessageToJava({ type: "Reader:GoToReadingList" });
@ -455,6 +493,10 @@ AboutReader.prototype = {
if (!this._toolbarEnabled)
return;
// Don't allow visible toolbar until banner state is known
if (this._readingListCount == -1 || this._isReadingListItem == -1)
return;
if (this._getToolbarVisibility() === visible)
return;
@ -622,6 +664,7 @@ AboutReader.prototype = {
this._maybeSetTextDirection(article);
this._contentElement.style.display = "block";
this._requestReadingListStatus();
this._toolbarEnabled = true;
this._setToolbarVisibility(true);

View File

@ -95,7 +95,8 @@ var Downloads = {
smallIcon: URI_GENERIC_ICON_DOWNLOAD,
text: aMessage,
ongoing: false,
cookie: aDownload.guid
cookie: aDownload.guid,
when: aDownload.startTime
};
if (aOptions && aOptions.icon) {

View File

@ -163,7 +163,7 @@ FilePicker.prototype = {
show: function() {
if (this._domWin) {
PromptUtils.fireDialogEvent(this._domWin, "DOMWillOpenModalDialog");
this.fireDialogEvent(this._domWin, "DOMWillOpenModalDialog");
let winUtils = this._domWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
winUtils.enterModalState();
}
@ -176,6 +176,12 @@ FilePicker.prototype = {
thread.processNextEvent(true);
delete this._promptActive;
if (this._domWin) {
let winUtils = this._domWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
winUtils.leaveModalState();
this.fireDialogEvent(this._domWin, "DOMModalDialogClosed");
}
if (this._filePath)
return Ci.nsIFilePicker.returnOK;
@ -245,6 +251,20 @@ FilePicker.prototype = {
};
},
fireDialogEvent: function(aDomWin, aEventName) {
// accessing the document object can throw if this window no longer exists. See bug 789888.
try {
if (!aDomWin.document)
return;
let event = aDomWin.document.createEvent("Events");
event.initEvent(aEventName, true, true);
let winUtils = aDomWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
winUtils.dispatchEventToChromeOnly(aDomWin, event);
} catch(ex) {
}
},
classID: Components.ID("{18a4e042-7c7c-424b-a583-354e68553a7f}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFilePicker, Ci.nsIObserver])
};