Merge m-c to inbound

This commit is contained in:
Wes Kocher 2014-02-25 20:24:27 -08:00
commit cd6c2201e2
42 changed files with 1373 additions and 289 deletions

View File

@ -12,7 +12,7 @@
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8e21d0a5cdac94f05e9c3623fa3b9ad579ba2967"/> <project name="gaia.git" path="gaia" remote="mozillaorg" revision="80d6405725788327102cab36e8d8c017cf25fb23"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/> <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>

View File

@ -11,7 +11,7 @@
</project> </project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="8e21d0a5cdac94f05e9c3623fa3b9ad579ba2967"/> <project name="gaia" path="gaia" remote="mozillaorg" revision="80d6405725788327102cab36e8d8c017cf25fb23"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/> <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8c449b53328059e9b55bb34baec9b27a15055a7e"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8c449b53328059e9b55bb34baec9b27a15055a7e"/>

View File

@ -12,7 +12,7 @@
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8e21d0a5cdac94f05e9c3623fa3b9ad579ba2967"/> <project name="gaia.git" path="gaia" remote="mozillaorg" revision="80d6405725788327102cab36e8d8c017cf25fb23"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/> <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>

View File

@ -1,4 +1,4 @@
{ {
"revision": "823616f0af83eca32bceb0367a7a221f8b187110", "revision": "b2b0a8234336f7004812bf27e4053957cecad492",
"repo_path": "/integration/gaia-central" "repo_path": "/integration/gaia-central"
} }

View File

@ -11,7 +11,7 @@
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8e21d0a5cdac94f05e9c3623fa3b9ad579ba2967"/> <project name="gaia.git" path="gaia" remote="mozillaorg" revision="80d6405725788327102cab36e8d8c017cf25fb23"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/> <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>

View File

@ -10,7 +10,7 @@
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8e21d0a5cdac94f05e9c3623fa3b9ad579ba2967"/> <project name="gaia.git" path="gaia" remote="mozillaorg" revision="80d6405725788327102cab36e8d8c017cf25fb23"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/> <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>

View File

@ -12,7 +12,7 @@
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8e21d0a5cdac94f05e9c3623fa3b9ad579ba2967"/> <project name="gaia.git" path="gaia" remote="mozillaorg" revision="80d6405725788327102cab36e8d8c017cf25fb23"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/> <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>

View File

@ -11,7 +11,7 @@
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8e21d0a5cdac94f05e9c3623fa3b9ad579ba2967"/> <project name="gaia.git" path="gaia" remote="mozillaorg" revision="80d6405725788327102cab36e8d8c017cf25fb23"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/> <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>

View File

@ -11,7 +11,7 @@
</project> </project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="8e21d0a5cdac94f05e9c3623fa3b9ad579ba2967"/> <project name="gaia" path="gaia" remote="mozillaorg" revision="80d6405725788327102cab36e8d8c017cf25fb23"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/> <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8c449b53328059e9b55bb34baec9b27a15055a7e"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8c449b53328059e9b55bb34baec9b27a15055a7e"/>

View File

@ -11,7 +11,7 @@
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8e21d0a5cdac94f05e9c3623fa3b9ad579ba2967"/> <project name="gaia.git" path="gaia" remote="mozillaorg" revision="80d6405725788327102cab36e8d8c017cf25fb23"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/> <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>

View File

@ -592,6 +592,9 @@
cui-areatype="toolbar" cui-areatype="toolbar"
tooltiptext="&closeTab.label;"/> tooltiptext="&closeTab.label;"/>
#ifdef XP_WIN
<hbox id="private-browsing-indicator" skipintoolbarset="true" ordinal="1000"/>
#endif
#ifdef CAN_DRAW_IN_TITLEBAR #ifdef CAN_DRAW_IN_TITLEBAR
<hbox class="titlebar-placeholder" type="caption-buttons" <hbox class="titlebar-placeholder" type="caption-buttons"
id="titlebar-placeholder-on-TabsToolbar-for-captions-buttons" persist="width" id="titlebar-placeholder-on-TabsToolbar-for-captions-buttons" persist="width"

View File

@ -191,6 +191,19 @@ const CustomizableWidgets = [{
} }
recentlyClosedWindows.appendChild(windowsFragment); recentlyClosedWindows.appendChild(windowsFragment);
}, },
onCreated: function(aNode) {
// Middle clicking recently closed items won't close the panel - cope:
let onRecentlyClosedClick = function(aEvent) {
if (aEvent.button == 1) {
CustomizableUI.hidePanelForNode(this);
}
};
let doc = aNode.ownerDocument;
let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs");
let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows");
recentlyClosedTabs.addEventListener("click", onRecentlyClosedClick);
recentlyClosedWindows.addEventListener("click", onRecentlyClosedClick);
},
onViewHiding: function(aEvent) { onViewHiding: function(aEvent) {
LOG("History view is being hidden!"); LOG("History view is being hidden!");
} }

View File

@ -107,6 +107,8 @@ support-files =
test-bug_923281_test2.js test-bug_923281_test2.js
test-bug_939783_console_trace_duplicates.html test-bug_939783_console_trace_duplicates.html
test-bug-952277-highlight-nodes-in-vview.html test-bug-952277-highlight-nodes-in-vview.html
test-bug-609872-cd-iframe-parent.html
test-bug-609872-cd-iframe-child.html
[browser_bug664688_sandbox_update_after_navigation.js] [browser_bug664688_sandbox_update_after_navigation.js]
[browser_bug_638949_copy_link_location.js] [browser_bug_638949_copy_link_location.js]
@ -267,3 +269,4 @@ run-if = os == "mac"
[browser_webconsole_output_events.js] [browser_webconsole_output_events.js]
[browser_console_variables_view_highlighter.js] [browser_console_variables_view_highlighter.js]
[browser_webconsole_console_trace_duplicates.js] [browser_webconsole_console_trace_duplicates.js]
[browser_webconsole_cd_iframe.js]

View File

@ -0,0 +1,110 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the cd() jsterm helper function works as expected. See bug 609872.
function test() {
let hud;
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-609872-cd-iframe-parent.html";
const parentMessages = [{
name: "document.title in parent iframe",
text: "bug 609872 - iframe parent",
category: CATEGORY_OUTPUT,
}, {
name: "paragraph content",
text: "p: test for bug 609872 - iframe parent",
category: CATEGORY_OUTPUT,
}, {
name: "object content",
text: "obj: parent!",
category: CATEGORY_OUTPUT,
}];
const childMessages = [{
name: "document.title in child iframe",
text: "bug 609872 - iframe child",
category: CATEGORY_OUTPUT,
}, {
name: "paragraph content",
text: "p: test for bug 609872 - iframe child",
category: CATEGORY_OUTPUT,
}, {
name: "object content",
text: "obj: child!",
category: CATEGORY_OUTPUT,
}];
Task.spawn(runner).then(finishTest);
function* runner() {
const {tab} = yield loadTab(TEST_URI);
hud = yield openConsole(tab);
executeWindowTest();
yield waitForMessages({ webconsole: hud, messages: parentMessages });
info("cd() into the iframe using a selector");
hud.jsterm.clearOutput();
hud.jsterm.execute("cd('iframe')");
executeWindowTest();
yield waitForMessages({ webconsole: hud, messages: childMessages });
info("cd() out of the iframe, reset to default window");
hud.jsterm.clearOutput();
hud.jsterm.execute("cd()");
executeWindowTest();
yield waitForMessages({ webconsole: hud, messages: parentMessages });
info("call cd() with unexpected arguments");
hud.jsterm.clearOutput();
hud.jsterm.execute("cd(document)");
yield waitForMessages({
webconsole: hud,
messages: [{
text: "Cannot cd()",
category: CATEGORY_OUTPUT,
severity: SEVERITY_ERROR,
}],
});
hud.jsterm.clearOutput();
hud.jsterm.execute("cd('p')");
yield waitForMessages({
webconsole: hud,
messages: [{
text: "Cannot cd()",
category: CATEGORY_OUTPUT,
severity: SEVERITY_ERROR,
}],
});
info("cd() into the iframe using an iframe DOM element");
hud.jsterm.clearOutput();
hud.jsterm.execute("cd($('iframe'))");
executeWindowTest();
yield waitForMessages({ webconsole: hud, messages: childMessages });
info("cd(window.parent)");
hud.jsterm.clearOutput();
hud.jsterm.execute("cd(window.parent)");
executeWindowTest();
yield waitForMessages({ webconsole: hud, messages: parentMessages });
yield closeConsole(tab);
}
function executeWindowTest() {
hud.jsterm.execute("document.title");
hud.jsterm.execute("'p: ' + document.querySelector('p').textContent");
hud.jsterm.execute("'obj: ' + window.foobarBug609872");
}
}

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>test for bug 609872 - iframe child</title>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
</head>
<body>
<p>test for bug 609872 - iframe child</p>
<script>window.foobarBug609872 = 'child!';</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>test for bug 609872 - iframe parent</title>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
</head>
<body>
<p>test for bug 609872 - iframe parent</p>
<script>window.foobarBug609872 = 'parent!';</script>
<iframe src="test-bug-609872-cd-iframe-child.html"></iframe>
</body>
</html>

View File

@ -213,3 +213,7 @@ emptyPropertiesList=No properties to display
# #1 number of message repeats # #1 number of message repeats
# example: 3 repeats # example: 3 repeats
messageRepeats.tooltip2=#1 repeat;#1 repeats messageRepeats.tooltip2=#1 repeat;#1 repeats
# LOCALIZATION NOTE (cdFunctionInvalidArgument): the text that is displayed when
# cd() is invoked with an invalid argument.
cdFunctionInvalidArgument=Cannot cd() to the given window. Invalid argument.

View File

@ -64,6 +64,7 @@ var Browser = {
messageManager.loadFrameScript("chrome://browser/content/contenthandlers/SelectionHandler.js", true); messageManager.loadFrameScript("chrome://browser/content/contenthandlers/SelectionHandler.js", true);
messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ContextMenuHandler.js", true); messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ContextMenuHandler.js", true);
messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ConsoleAPIObserver.js", true); messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ConsoleAPIObserver.js", true);
messageManager.loadFrameScript("chrome://browser/content/contenthandlers/PluginHelper.js", true);
} catch (e) { } catch (e) {
// XXX whatever is calling startup needs to dump errors! // XXX whatever is calling startup needs to dump errors!
dump("###########" + e + "\n"); dump("###########" + e + "\n");

View File

@ -1120,6 +1120,9 @@ Desktop browser's sync prefs.
<setting pref="signon.rememberSignons" <setting pref="signon.rememberSignons"
title="&optionsHeader.privacy.passwords.label;" title="&optionsHeader.privacy.passwords.label;"
type="bool"/> type="bool"/>
<setting pref="browser.display.overlaynavbuttons"
title="&optionsHeader.displayOverlayButtons.label;"
type="bool"/>
<settings id="prefs-reporting" <settings id="prefs-reporting"
label="&optionsHeader.reporting.title;"> label="&optionsHeader.reporting.title;">
<setting pref="app.crashreporter.autosubmit" <setting pref="app.crashreporter.autosubmit"

View File

@ -0,0 +1,95 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
dump("### PluginHelper.js loaded\n");
/**
* Handle events generated by plugin click-to-play code.
*
* This "PluginBindingAttached" fires when a "pluginProblem" XBL binding is
* created. This binding overlays plugin content when the plugin is missing,
* blocked, click-to-play, or replaced by a "preview" extension plugin like
* Shumway or PDF.js.
*/
var PluginHelper = {
init: function () {
addEventListener("PluginBindingAttached", this, true, true);
},
handleEvent: function handleEvent(aEvent) {
switch (aEvent.type) {
case "PluginBindingAttached":
this.handlePluginBindingAttached(aEvent);
break;
}
},
getPluginMimeType: function (plugin) {
var tagMimetype = plugin.actualType;
if (tagMimetype == "") {
tagMimetype = plugin.type;
}
return tagMimetype;
},
handlePluginBindingAttached: function (aEvent) {
let plugin = aEvent.target;
let doc = plugin.ownerDocument;
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
if (!overlay || overlay._bindingHandled) {
return;
}
overlay._bindingHandled = true;
let eventType = PluginHelper._getBindingType(plugin);
if (!eventType) {
return;
}
switch (eventType) {
case "PluginPlayPreview": {
// Load the "preview" handler (an extension plugin like Shumway or PDF.js).
let previewContent = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
let mimeType = PluginHelper.getPluginMimeType(plugin);
let playPreviewInfo = pluginHost.getPlayPreviewInfo(mimeType);
let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
if (!iframe) {
// lazy initialization of the iframe
iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
iframe.className = "previewPluginContentFrame";
previewContent.appendChild(iframe);
}
iframe.src = playPreviewInfo.redirectURL;
break;
}
case "PluginNotFound": {
// TODO: Display a message about missing plugins (bug 936907)
break;
}
}
},
// Helper to get the binding handler type from a plugin object
_getBindingType: function(plugin) {
if (!(plugin instanceof Ci.nsIObjectLoadingContent))
return null;
switch (plugin.pluginFallbackType) {
case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED:
return "PluginNotFound";
case Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW:
return "PluginPlayPreview";
default:
// Metro Firefox does not yet support other fallback types.
return null;
}
},
};
PluginHelper.init();

View File

@ -59,6 +59,7 @@ chrome.jar:
content/contenthandlers/FormHelper.js (content/contenthandlers/FormHelper.js) content/contenthandlers/FormHelper.js (content/contenthandlers/FormHelper.js)
content/contenthandlers/ConsoleAPIObserver.js (content/contenthandlers/ConsoleAPIObserver.js) content/contenthandlers/ConsoleAPIObserver.js (content/contenthandlers/ConsoleAPIObserver.js)
content/contenthandlers/Content.js (content/contenthandlers/Content.js) content/contenthandlers/Content.js (content/contenthandlers/Content.js)
content/contenthandlers/PluginHelper.js (content/contenthandlers/PluginHelper.js)
content/library/SelectionPrototype.js (content/library/SelectionPrototype.js) content/library/SelectionPrototype.js (content/library/SelectionPrototype.js)

View File

@ -27,6 +27,7 @@
<!ENTITY clearPrivateData.logins "Active logins"> <!ENTITY clearPrivateData.logins "Active logins">
<!ENTITY optionsHeader.privacy.passwords.label "Remember Passwords"> <!ENTITY optionsHeader.privacy.passwords.label "Remember Passwords">
<!ENTITY optionsHeader.displayOverlayButtons.label "Show Navigation Buttons">
<!ENTITY doNotTrack.title "Do Not Track"> <!ENTITY doNotTrack.title "Do Not Track">
<!ENTITY doNotTrack.options.doNotTrack "Tell websites that I do not want to be tracked"> <!ENTITY doNotTrack.options.doNotTrack "Tell websites that I do not want to be tracked">
<!ENTITY doNotTrack.options.doTrack "Tell websites that I want to be tracked"> <!ENTITY doNotTrack.options.doTrack "Tell websites that I want to be tracked">

View File

@ -573,7 +573,12 @@ pref("pdfjs.previousHandler.alwaysAskBeforeHandling", false);
#endif #endif
#ifdef NIGHTLY_BUILD #ifdef NIGHTLY_BUILD
// Shumay is currently experimental. Toggle this pref to enable Shumway for
// testing and development.
pref("shumway.disabled", true); pref("shumway.disabled", true);
// When Shumway is enabled, use it all the time, not only when Flash is set to
// click-to-play (because Metro doesn't even load the native Flash plugin).
pref("shumway.ignoreCTP", true);
#endif #endif
// The maximum amount of decoded image data we'll willingly keep around (we // The maximum amount of decoded image data we'll willingly keep around (we

View File

@ -2509,9 +2509,7 @@ chatbox {
/* End customization mode */ /* End customization mode */
#main-window[privatebrowsingmode=temporary] #TabsToolbar::after { #main-window[privatebrowsingmode=temporary] #private-browsing-indicator {
content: "";
display: -moz-box;
width: 40px; width: 40px;
background: url("chrome://browser/skin/privatebrowsing-indicator.png") no-repeat center center; background: url("chrome://browser/skin/privatebrowsing-indicator.png") no-repeat center center;
} }

View File

@ -952,12 +952,6 @@ nsDOMCameraControl::Shutdown()
mCameraControl->Shutdown(); mCameraControl->Shutdown();
} }
nsRefPtr<ICameraControl>
nsDOMCameraControl::GetNativeCameraControl()
{
return mCameraControl;
}
nsresult nsresult
nsDOMCameraControl::NotifyRecordingStatusChange(const nsString& aMsg) nsDOMCameraControl::NotifyRecordingStatusChange(const nsString& aMsg)
{ {
@ -1139,21 +1133,31 @@ void
nsDOMCameraControl::OnAutoFocusComplete(bool aAutoFocusSucceeded) nsDOMCameraControl::OnAutoFocusComplete(bool aAutoFocusSucceeded)
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
ErrorResult ignored;
nsCOMPtr<CameraAutoFocusCallback> cb = mAutoFocusOnSuccessCb.forget(); nsCOMPtr<CameraAutoFocusCallback> cb = mAutoFocusOnSuccessCb.forget();
mAutoFocusOnErrorCb = nullptr; mAutoFocusOnErrorCb = nullptr;
cb->Call(aAutoFocusSucceeded, ignored); if (cb) {
ErrorResult ignored;
cb->Call(aAutoFocusSucceeded, ignored);
}
} }
void void
nsDOMCameraControl::OnTakePictureComplete(nsIDOMBlob* aPicture) nsDOMCameraControl::OnTakePictureComplete(nsIDOMBlob* aPicture)
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
ErrorResult ignored;
nsCOMPtr<CameraTakePictureCallback> cb = mTakePictureOnSuccessCb.forget(); nsCOMPtr<CameraTakePictureCallback> cb = mTakePictureOnSuccessCb.forget();
mTakePictureOnErrorCb = nullptr; mTakePictureOnErrorCb = nullptr;
if (!cb) {
// Warn because it shouldn't be possible to get here without
// having passed a success callback into takePicture(), even
// though we guard against a nullptr dereference.
NS_WARNING("DOM Null success callback in OnTakePictureComplete()");
return;
}
ErrorResult ignored;
cb->Call(aPicture, ignored); cb->Call(aPicture, ignored);
} }

View File

@ -45,7 +45,6 @@ public:
dom::GetCameraCallback* aOnSuccess, dom::GetCameraCallback* aOnSuccess,
dom::CameraErrorCallback* aOnError, dom::CameraErrorCallback* aOnError,
nsPIDOMWindow* aWindow); nsPIDOMWindow* aWindow);
nsRefPtr<ICameraControl> GetNativeCameraControl();
void Shutdown(); void Shutdown();

View File

@ -272,53 +272,86 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
} }
} }
/*
* This utility is replicated from RepoUtils, which is managed by android-sync.
*/
private static String computeSQLInClause(int items, String field) {
final StringBuilder builder = new StringBuilder(field);
builder.append(" IN (");
int i = 0;
for (; i < items - 1; ++i) {
builder.append("?, ");
}
if (i < items) {
builder.append("?");
}
builder.append(")");
return builder.toString();
}
/**
* Turn a single-column cursor of longs into a single SQL "IN" clause.
* We can do this without using selection arguments because Long isn't
* vulnerable to injection.
*/
private static String computeSQLInClauseFromLongs(final Cursor cursor, String field) {
final StringBuilder builder = new StringBuilder(field);
builder.append(" IN (");
final int commaLimit = cursor.getCount() - 1;
int i = 0;
while (cursor.moveToNext()) {
builder.append(cursor.getLong(0));
if (i++ < commaLimit) {
builder.append(", ");
}
}
builder.append(")");
return builder.toString();
}
/**
* Clean up some deleted records from the specified table.
*
* If called in an existing transaction, it is the caller's responsibility
* to ensure that the transaction is already upgraded to a writer, because
* this method issues a read followed by a write, and thus is potentially
* vulnerable to an unhandled SQLITE_BUSY failure during the upgrade.
*
* If not called in an existing transaction, no new explicit transaction
* will be begun.
*/
private void cleanupSomeDeletedRecords(Uri fromUri, Uri targetUri, String tableName) { private void cleanupSomeDeletedRecords(Uri fromUri, Uri targetUri, String tableName) {
Log.d(LOGTAG, "Cleaning up deleted records from " + tableName); Log.d(LOGTAG, "Cleaning up deleted records from " + tableName);
// we cleanup records marked as deleted that are older than a // We clean up records marked as deleted that are older than a
// predefined max age. It's important not be too greedy here and // predefined max age. It's important not be too greedy here and
// remove only a few old deleted records at a time. // remove only a few old deleted records at a time.
// The PARAM_SHOW_DELETED argument is necessary to return the records // Android SQLite doesn't have LIMIT on DELETE. Instead, query for the
// that were marked as deleted. We use PARAM_IS_SYNC here to ensure // IDs of matching rows, then delete them in one go.
// that we'll be actually deleting records instead of flagging them. final long now = System.currentTimeMillis();
Uri.Builder uriBuilder = targetUri.buildUpon() final String selection = SyncColumns.IS_DELETED + " = 1 AND " +
.appendQueryParameter(BrowserContract.PARAM_LIMIT, String.valueOf(DELETED_RECORDS_PURGE_LIMIT)) SyncColumns.DATE_MODIFIED + " <= " +
.appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1") (now - MAX_AGE_OF_DELETED_RECORDS);
.appendQueryParameter(BrowserContract.PARAM_IS_SYNC, "1");
String profile = fromUri.getQueryParameter(BrowserContract.PARAM_PROFILE);
if (!TextUtils.isEmpty(profile))
uriBuilder = uriBuilder.appendQueryParameter(BrowserContract.PARAM_PROFILE, profile);
if (isTest(fromUri))
uriBuilder = uriBuilder.appendQueryParameter(BrowserContract.PARAM_IS_TEST, "1");
Uri uriWithArgs = uriBuilder.build();
Cursor cursor = null;
final String profile = fromUri.getQueryParameter(BrowserContract.PARAM_PROFILE);
final SQLiteDatabase db = getWritableDatabaseForProfile(profile, isTest(fromUri));
final String[] ids;
final String limit = Long.toString(DELETED_RECORDS_PURGE_LIMIT, 10);
final Cursor cursor = db.query(tableName, new String[] { CommonColumns._ID }, selection, null, null, null, null, limit);
try { try {
long now = System.currentTimeMillis(); ids = new String[cursor.getCount()];
String selection = SyncColumns.IS_DELETED + " = 1 AND " + int i = 0;
SyncColumns.DATE_MODIFIED + " <= " + (now - MAX_AGE_OF_DELETED_RECORDS);
cursor = query(uriWithArgs,
new String[] { CommonColumns._ID },
selection,
null,
null);
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
Uri uriWithId = ContentUris.withAppendedId(uriWithArgs, cursor.getLong(0)); ids[i++] = Long.toString(cursor.getLong(0), 10);
delete(uriWithId, null, null);
debug("Removed old deleted item with URI: " + uriWithId);
} }
} finally { } finally {
if (cursor != null) cursor.close();
cursor.close();
} }
final String inClause = computeSQLInClause(ids.length,
CommonColumns._ID);
db.delete(tableName, inClause, ids);
} }
/** /**
@ -328,8 +361,6 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
* Provide <code>keepAfter</code> less than or equal to zero to skip that check. * Provide <code>keepAfter</code> less than or equal to zero to skip that check.
* *
* Items will be removed according to an approximate frecency calculation. * Items will be removed according to an approximate frecency calculation.
*
* Call this method within a transaction.
*/ */
private void expireHistory(final SQLiteDatabase db, final int retain, final long keepAfter) { private void expireHistory(final SQLiteDatabase db, final int retain, final long keepAfter) {
Log.d(LOGTAG, "Expiring history."); Log.d(LOGTAG, "Expiring history.");
@ -358,6 +389,8 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
"ORDER BY " + sortOrder + " LIMIT " + toRemove + ")"; "ORDER BY " + sortOrder + " LIMIT " + toRemove + ")";
} }
trace("Deleting using query: " + sql); trace("Deleting using query: " + sql);
beginWrite(db);
db.execSQL(sql); db.execSQL(sql);
} }
@ -442,6 +475,7 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) { public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
trace("Calling delete in transaction on URI: " + uri); trace("Calling delete in transaction on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri); final SQLiteDatabase db = getWritableDatabase(uri);
final int match = URI_MATCHER.match(uri); final int match = URI_MATCHER.match(uri);
int deleted = 0; int deleted = 0;
@ -469,6 +503,7 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
// fall through // fall through
case HISTORY: { case HISTORY: {
trace("Deleting history: " + uri); trace("Deleting history: " + uri);
beginWrite(db);
deleted = deleteHistory(uri, selection, selectionArgs); deleted = deleteHistory(uri, selection, selectionArgs);
deleteUnusedImages(uri); deleteUnusedImages(uri);
break; break;
@ -498,6 +533,7 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
// fall through // fall through
case FAVICONS: { case FAVICONS: {
trace("Deleting favicons: " + uri); trace("Deleting favicons: " + uri);
beginWrite(db);
deleted = deleteFavicons(uri, selection, selectionArgs); deleted = deleteFavicons(uri, selection, selectionArgs);
break; break;
} }
@ -511,6 +547,7 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
// fall through // fall through
case THUMBNAILS: { case THUMBNAILS: {
trace("Deleting thumbnails: " + uri); trace("Deleting thumbnails: " + uri);
beginWrite(db);
deleted = deleteThumbnails(uri, selection, selectionArgs); deleted = deleteThumbnails(uri, selection, selectionArgs);
break; break;
} }
@ -577,6 +614,7 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
int match = URI_MATCHER.match(uri); int match = URI_MATCHER.match(uri);
int updated = 0; int updated = 0;
final SQLiteDatabase db = getWritableDatabase(uri);
switch (match) { switch (match) {
// We provide a dedicated (hacky) API for callers to bulk-update the positions of // We provide a dedicated (hacky) API for callers to bulk-update the positions of
// folder children by passing an array of GUID strings as `selectionArgs`. // folder children by passing an array of GUID strings as `selectionArgs`.
@ -589,13 +627,16 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
// `values` and `selection` are ignored. // `values` and `selection` are ignored.
case BOOKMARKS_POSITIONS: { case BOOKMARKS_POSITIONS: {
debug("Update on BOOKMARKS_POSITIONS: " + uri); debug("Update on BOOKMARKS_POSITIONS: " + uri);
// This already starts and finishes its own transaction.
updated = updateBookmarkPositions(uri, selectionArgs); updated = updateBookmarkPositions(uri, selectionArgs);
break; break;
} }
case BOOKMARKS_PARENT: { case BOOKMARKS_PARENT: {
debug("Update on BOOKMARKS_PARENT: " + uri); debug("Update on BOOKMARKS_PARENT: " + uri);
updated = updateBookmarkParents(uri, values, selection, selectionArgs); beginWrite(db);
updated = updateBookmarkParents(db, values, selection, selectionArgs);
break; break;
} }
@ -608,10 +649,11 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
// fall through // fall through
case BOOKMARKS: { case BOOKMARKS: {
debug("Updating bookmark: " + uri); debug("Updating bookmark: " + uri);
if (shouldUpdateOrInsert(uri)) if (shouldUpdateOrInsert(uri)) {
updated = updateOrInsertBookmark(uri, values, selection, selectionArgs); updated = updateOrInsertBookmark(uri, values, selection, selectionArgs);
else } else {
updated = updateBookmarks(uri, values, selection, selectionArgs); updated = updateBookmarks(uri, values, selection, selectionArgs);
}
break; break;
} }
@ -624,10 +666,11 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
// fall through // fall through
case HISTORY: { case HISTORY: {
debug("Updating history: " + uri); debug("Updating history: " + uri);
if (shouldUpdateOrInsert(uri)) if (shouldUpdateOrInsert(uri)) {
updated = updateOrInsertHistory(uri, values, selection, selectionArgs); updated = updateOrInsertHistory(uri, values, selection, selectionArgs);
else } else {
updated = updateHistory(uri, values, selection, selectionArgs); updated = updateHistory(uri, values, selection, selectionArgs);
}
break; break;
} }
@ -643,11 +686,11 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
faviconSelectionArgs = new String[] { url }; faviconSelectionArgs = new String[] { url };
} }
if (shouldUpdateOrInsert(uri)) if (shouldUpdateOrInsert(uri)) {
updated = updateOrInsertFavicon(uri, values, faviconSelection, faviconSelectionArgs); updated = updateOrInsertFavicon(uri, values, faviconSelection, faviconSelectionArgs);
else } else {
updated = updateExistingFavicon(uri, values, faviconSelection, faviconSelectionArgs); updated = updateExistingFavicon(uri, values, faviconSelection, faviconSelectionArgs);
}
break; break;
} }
@ -657,15 +700,15 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
String url = values.getAsString(Thumbnails.URL); String url = values.getAsString(Thumbnails.URL);
// if no URL is provided, update all of the entries // if no URL is provided, update all of the entries
if (TextUtils.isEmpty(values.getAsString(Thumbnails.URL))) if (TextUtils.isEmpty(values.getAsString(Thumbnails.URL))) {
updated = updateExistingThumbnail(uri, values, null, null); updated = updateExistingThumbnail(uri, values, null, null);
else if (shouldUpdateOrInsert(uri)) } else if (shouldUpdateOrInsert(uri)) {
updated = updateOrInsertThumbnail(uri, values, Thumbnails.URL + " = ?", updated = updateOrInsertThumbnail(uri, values, Thumbnails.URL + " = ?",
new String[] { url }); new String[] { url });
else } else {
updated = updateExistingThumbnail(uri, values, Thumbnails.URL + " = ?", updated = updateExistingThumbnail(uri, values, Thumbnails.URL + " = ?",
new String[] { url }); new String[] { url });
}
break; break;
} }
@ -674,7 +717,6 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
} }
debug("Updated " + updated + " rows for URI: " + uri); debug("Updated " + updated + " rows for URI: " + uri);
return updated; return updated;
} }
@ -876,40 +918,42 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
return cursor; return cursor;
} }
int getUrlCount(SQLiteDatabase db, String table, String url) { private static int getUrlCount(SQLiteDatabase db, String table, String url) {
Cursor c = db.query(table, new String[] { "COUNT(*)" }, final Cursor c = db.query(table, new String[] { "COUNT(*)" },
URLColumns.URL + " = ?", new String[] { url }, null, null, URLColumns.URL + " = ?", new String[] { url },
null); null, null, null);
int count = 0;
try { try {
if (c.moveToFirst()) if (c.moveToFirst()) {
count = c.getInt(0); return c.getInt(0);
}
} finally { } finally {
c.close(); c.close();
} }
return count; return 0;
} }
/** /**
* Update the positions of bookmarks in batches. * Update the positions of bookmarks in batches.
* *
* Begins and ends its own transactions.
*
* @see #updateBookmarkPositionsInTransaction(SQLiteDatabase, String[], int, int) * @see #updateBookmarkPositionsInTransaction(SQLiteDatabase, String[], int, int)
*/ */
int updateBookmarkPositions(Uri uri, String[] guids) { int updateBookmarkPositions(Uri uri, String[] guids) {
if (guids == null) if (guids == null) {
return 0; return 0;
}
int guidsCount = guids.length; int guidsCount = guids.length;
if (guidsCount == 0) if (guidsCount == 0) {
return 0; return 0;
}
final SQLiteDatabase db = getWritableDatabase(uri);
int offset = 0; int offset = 0;
int updated = 0; int updated = 0;
final SQLiteDatabase db = getWritableDatabase(uri);
db.beginTransaction(); db.beginTransaction();
while (offset < guidsCount) { while (offset < guidsCount) {
@ -942,8 +986,8 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
* Construct and execute an update expression that will modify the positions * Construct and execute an update expression that will modify the positions
* of records in-place. * of records in-place.
*/ */
int updateBookmarkPositionsInTransaction(final SQLiteDatabase db, final String[] guids, private static int updateBookmarkPositionsInTransaction(final SQLiteDatabase db, final String[] guids,
final int offset, final int max) { final int offset, final int max) {
int guidsCount = guids.length; int guidsCount = guids.length;
int processCount = Math.min(max, guidsCount - offset); int processCount = Math.min(max, guidsCount - offset);
@ -969,6 +1013,7 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
b.append(" WHEN ? THEN " + i); b.append(" WHEN ? THEN " + i);
} }
// TODO: use computeSQLInClause
b.append(" END WHERE " + Bookmarks.GUID + " IN ("); b.append(" END WHERE " + Bookmarks.GUID + " IN (");
i = 1; i = 1;
while (i++ < processCount) { while (i++ < processCount) {
@ -985,13 +1030,13 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
* Construct an update expression that will modify the parents of any records * Construct an update expression that will modify the parents of any records
* that match. * that match.
*/ */
int updateBookmarkParents(Uri uri, ContentValues values, String selection, String[] selectionArgs) { private int updateBookmarkParents(SQLiteDatabase db, ContentValues values, String selection, String[] selectionArgs) {
trace("Updating bookmark parents of " + selection + " (" + selectionArgs[0] + ")"); trace("Updating bookmark parents of " + selection + " (" + selectionArgs[0] + ")");
String where = Bookmarks._ID + " IN (" + String where = Bookmarks._ID + " IN (" +
" SELECT DISTINCT " + Bookmarks.PARENT + " SELECT DISTINCT " + Bookmarks.PARENT +
" FROM " + TABLE_BOOKMARKS + " FROM " + TABLE_BOOKMARKS +
" WHERE " + selection + " )"; " WHERE " + selection + " )";
return getWritableDatabase(uri).update(TABLE_BOOKMARKS, values, where, selectionArgs); return db.update(TABLE_BOOKMARKS, values, where, selectionArgs);
} }
long insertBookmark(Uri uri, ContentValues values) { long insertBookmark(Uri uri, ContentValues values) {
@ -1017,10 +1062,10 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
} }
String url = values.getAsString(Bookmarks.URL); String url = values.getAsString(Bookmarks.URL);
Integer type = values.getAsInteger(Bookmarks.TYPE);
debug("Inserting bookmark in database with URL: " + url); debug("Inserting bookmark in database with URL: " + url);
final SQLiteDatabase db = getWritableDatabase(uri); final SQLiteDatabase db = getWritableDatabase(uri);
beginWrite(db);
return db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.TITLE, values); return db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.TITLE, values);
} }
@ -1028,9 +1073,11 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
int updateOrInsertBookmark(Uri uri, ContentValues values, String selection, int updateOrInsertBookmark(Uri uri, ContentValues values, String selection,
String[] selectionArgs) { String[] selectionArgs) {
int updated = updateBookmarks(uri, values, selection, selectionArgs); int updated = updateBookmarks(uri, values, selection, selectionArgs);
if (updated > 0) if (updated > 0) {
return updated; return updated;
}
// Transaction already begun by updateBookmarks.
if (0 <= insertBookmark(uri, values)) { if (0 <= insertBookmark(uri, values)) {
// We 'updated' one row. // We 'updated' one row.
return 1; return 1;
@ -1044,49 +1091,35 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
String[] selectionArgs) { String[] selectionArgs) {
trace("Updating bookmarks on URI: " + uri); trace("Updating bookmarks on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
int updated = 0;
final String[] bookmarksProjection = new String[] { final String[] bookmarksProjection = new String[] {
Bookmarks._ID, // 0 Bookmarks._ID, // 0
Bookmarks.URL, // 1
}; };
trace("Quering bookmarks to update on URI: " + uri); if (!values.containsKey(Bookmarks.DATE_MODIFIED)) {
values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
Cursor cursor = db.query(TABLE_BOOKMARKS, bookmarksProjection,
selection, selectionArgs, null, null, null);
try {
if (!values.containsKey(Bookmarks.DATE_MODIFIED))
values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
boolean updatingUrl = values.containsKey(Bookmarks.URL);
String url = null;
if (updatingUrl)
url = values.getAsString(Bookmarks.URL);
while (cursor.moveToNext()) {
long id = cursor.getLong(0);
trace("Updating bookmark with ID: " + id);
updated += db.update(TABLE_BOOKMARKS, values, "_id = ?",
new String[] { Long.toString(id) });
}
} finally {
if (cursor != null)
cursor.close();
} }
return updated; trace("Querying bookmarks to update on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
// Compute matching IDs.
final Cursor cursor = db.query(TABLE_BOOKMARKS, bookmarksProjection,
selection, selectionArgs, null, null, null);
// Now that we're done reading, open a transaction.
final String inClause;
try {
inClause = computeSQLInClauseFromLongs(cursor, Bookmarks._ID);
} finally {
cursor.close();
}
beginWrite(db);
return db.update(TABLE_BOOKMARKS, values, inClause, null);
} }
long insertHistory(Uri uri, ContentValues values) { long insertHistory(Uri uri, ContentValues values) {
final SQLiteDatabase db = getWritableDatabase(uri); final long now = System.currentTimeMillis();
long now = System.currentTimeMillis();
values.put(History.DATE_CREATED, now); values.put(History.DATE_CREATED, now);
values.put(History.DATE_MODIFIED, now); values.put(History.DATE_MODIFIED, now);
@ -1098,20 +1131,25 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
String url = values.getAsString(History.URL); String url = values.getAsString(History.URL);
debug("Inserting history in database with URL: " + url); debug("Inserting history in database with URL: " + url);
final SQLiteDatabase db = getWritableDatabase(uri);
beginWrite(db);
return db.insertOrThrow(TABLE_HISTORY, History.VISITS, values); return db.insertOrThrow(TABLE_HISTORY, History.VISITS, values);
} }
int updateOrInsertHistory(Uri uri, ContentValues values, String selection, int updateOrInsertHistory(Uri uri, ContentValues values, String selection,
String[] selectionArgs) { String[] selectionArgs) {
int updated = updateHistory(uri, values, selection, selectionArgs); final int updated = updateHistory(uri, values, selection, selectionArgs);
if (updated > 0) if (updated > 0) {
return updated; return updated;
}
// Insert a new entry if necessary // Insert a new entry if necessary
if (!values.containsKey(History.VISITS)) if (!values.containsKey(History.VISITS)) {
values.put(History.VISITS, 1); values.put(History.VISITS, 1);
if (!values.containsKey(History.TITLE)) }
if (!values.containsKey(History.TITLE)) {
values.put(History.TITLE, values.getAsString(History.URL)); values.put(History.TITLE, values.getAsString(History.URL));
}
if (0 <= insertHistory(uri, values)) { if (0 <= insertHistory(uri, values)) {
return 1; return 1;
@ -1124,7 +1162,6 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
String[] selectionArgs) { String[] selectionArgs) {
trace("Updating history on URI: " + uri); trace("Updating history on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
int updated = 0; int updated = 0;
final String[] historyProjection = new String[] { final String[] historyProjection = new String[] {
@ -1133,20 +1170,15 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
History.VISITS // 2 History.VISITS // 2
}; };
Cursor cursor = db.query(TABLE_HISTORY, historyProjection, selection, final SQLiteDatabase db = getWritableDatabase(uri);
selectionArgs, null, null, null); final Cursor cursor = db.query(TABLE_HISTORY, historyProjection, selection,
selectionArgs, null, null, null);
try { try {
if (!values.containsKey(Bookmarks.DATE_MODIFIED)) { if (!values.containsKey(Bookmarks.DATE_MODIFIED)) {
values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis()); values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
} }
boolean updatingUrl = values.containsKey(History.URL);
String url = null;
if (updatingUrl)
url = values.getAsString(History.URL);
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
long id = cursor.getLong(0); long id = cursor.getLong(0);
@ -1161,11 +1193,10 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
} }
updated += db.update(TABLE_HISTORY, values, "_id = ?", updated += db.update(TABLE_HISTORY, values, "_id = ?",
new String[] { Long.toString(id) }); new String[] { Long.toString(id) });
} }
} finally { } finally {
if (cursor != null) cursor.close();
cursor.close();
} }
return updated; return updated;
@ -1193,7 +1224,6 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
// If changes are needed, please update both // If changes are needed, please update both
String faviconUrl = values.getAsString(Favicons.URL); String faviconUrl = values.getAsString(Favicons.URL);
String pageUrl = null; String pageUrl = null;
long faviconId;
trace("Inserting favicon for URL: " + faviconUrl); trace("Inserting favicon for URL: " + faviconUrl);
@ -1210,15 +1240,16 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
values.put(Favicons.URL, org.mozilla.gecko.favicons.Favicons.guessDefaultFaviconURL(pageUrl)); values.put(Favicons.URL, org.mozilla.gecko.favicons.Favicons.guessDefaultFaviconURL(pageUrl));
} }
long now = System.currentTimeMillis(); final long now = System.currentTimeMillis();
values.put(Favicons.DATE_CREATED, now); values.put(Favicons.DATE_CREATED, now);
values.put(Favicons.DATE_MODIFIED, now); values.put(Favicons.DATE_MODIFIED, now);
faviconId = db.insertOrThrow(TABLE_FAVICONS, null, values);
beginWrite(db);
final long faviconId = db.insertOrThrow(TABLE_FAVICONS, null, values);
if (pageUrl != null) { if (pageUrl != null) {
updateFaviconIdsForUrl(db, pageUrl, faviconId); updateFaviconIdsForUrl(db, pageUrl, faviconId);
} }
return faviconId; return faviconId;
} }
@ -1239,8 +1270,6 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
String faviconUrl = values.getAsString(Favicons.URL); String faviconUrl = values.getAsString(Favicons.URL);
String pageUrl = null; String pageUrl = null;
int updated = 0; int updated = 0;
final SQLiteDatabase db = getWritableDatabase(uri);
Cursor cursor = null;
Long faviconId = null; Long faviconId = null;
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
@ -1256,33 +1285,38 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
values.put(Favicons.DATE_MODIFIED, now); values.put(Favicons.DATE_MODIFIED, now);
final SQLiteDatabase db = getWritableDatabase(uri);
// If there's no favicon URL given and we're inserting if needed, skip // If there's no favicon URL given and we're inserting if needed, skip
// the update and only do an insert (otherwise all rows would be // the update and only do an insert (otherwise all rows would be
// updated) // updated).
if (!(insertIfNeeded && (faviconUrl == null))) { if (!(insertIfNeeded && (faviconUrl == null))) {
updated = db.update(TABLE_FAVICONS, values, selection, selectionArgs); updated = db.update(TABLE_FAVICONS, values, selection, selectionArgs);
} }
if (updated > 0) { if (updated > 0) {
if ((faviconUrl != null) && (pageUrl != null)) { if ((faviconUrl != null) && (pageUrl != null)) {
final Cursor cursor = db.query(TABLE_FAVICONS,
new String[] { Favicons._ID },
Favicons.URL + " = ?",
new String[] { faviconUrl },
null, null, null);
try { try {
cursor = db.query(TABLE_FAVICONS,
new String[] { Favicons._ID },
Favicons.URL + " = ?",
new String[] { faviconUrl },
null, null, null);
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
faviconId = cursor.getLong(cursor.getColumnIndexOrThrow(Favicons._ID)); faviconId = cursor.getLong(cursor.getColumnIndexOrThrow(Favicons._ID));
} }
} finally { } finally {
if (cursor != null) cursor.close();
cursor.close();
} }
} }
if (pageUrl != null) {
beginWrite(db);
}
} else if (insertIfNeeded) { } else if (insertIfNeeded) {
values.put(Favicons.DATE_CREATED, now); values.put(Favicons.DATE_CREATED, now);
trace("No update, inserting favicon for URL: " + faviconUrl); trace("No update, inserting favicon for URL: " + faviconUrl);
beginWrite(db);
faviconId = db.insert(TABLE_FAVICONS, null, values); faviconId = db.insert(TABLE_FAVICONS, null, values);
updated = 1; updated = 1;
} }
@ -1294,40 +1328,40 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
return updated; return updated;
} }
long insertThumbnail(Uri uri, ContentValues values) { private long insertThumbnail(Uri uri, ContentValues values) {
String url = values.getAsString(Thumbnails.URL); final String url = values.getAsString(Thumbnails.URL);
final SQLiteDatabase db = getWritableDatabase(uri);
trace("Inserting thumbnail for URL: " + url); trace("Inserting thumbnail for URL: " + url);
DBUtils.stripEmptyByteArray(values, Thumbnails.DATA); DBUtils.stripEmptyByteArray(values, Thumbnails.DATA);
final SQLiteDatabase db = getWritableDatabase(uri);
beginWrite(db);
return db.insertOrThrow(TABLE_THUMBNAILS, null, values); return db.insertOrThrow(TABLE_THUMBNAILS, null, values);
} }
int updateOrInsertThumbnail(Uri uri, ContentValues values, String selection, private int updateOrInsertThumbnail(Uri uri, ContentValues values, String selection,
String[] selectionArgs) { String[] selectionArgs) {
return updateThumbnail(uri, values, selection, selectionArgs, return updateThumbnail(uri, values, selection, selectionArgs,
true /* insert if needed */); true /* insert if needed */);
} }
int updateExistingThumbnail(Uri uri, ContentValues values, String selection, private int updateExistingThumbnail(Uri uri, ContentValues values, String selection,
String[] selectionArgs) { String[] selectionArgs) {
return updateThumbnail(uri, values, selection, selectionArgs, return updateThumbnail(uri, values, selection, selectionArgs,
false /* only update, no insert */); false /* only update, no insert */);
} }
int updateThumbnail(Uri uri, ContentValues values, String selection, private int updateThumbnail(Uri uri, ContentValues values, String selection,
String[] selectionArgs, boolean insertIfNeeded) { String[] selectionArgs, boolean insertIfNeeded) {
String url = values.getAsString(Thumbnails.URL); final String url = values.getAsString(Thumbnails.URL);
int updated = 0;
final SQLiteDatabase db = getWritableDatabase(uri);
DBUtils.stripEmptyByteArray(values, Thumbnails.DATA); DBUtils.stripEmptyByteArray(values, Thumbnails.DATA);
trace("Updating thumbnail for URL: " + url); trace("Updating thumbnail for URL: " + url);
updated = db.update(TABLE_THUMBNAILS, values, selection, selectionArgs); final SQLiteDatabase db = getWritableDatabase(uri);
beginWrite(db);
int updated = db.update(TABLE_THUMBNAILS, values, selection, selectionArgs);
if (updated == 0 && insertIfNeeded) { if (updated == 0 && insertIfNeeded) {
trace("No update, inserting thumbnail for URL: " + url); trace("No update, inserting thumbnail for URL: " + url);
@ -1338,6 +1372,12 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
return updated; return updated;
} }
/**
* This method does not create a new transaction. Its first operation is
* guaranteed to be a write, which in the case of a new enclosing
* transaction will guarantee that a read does not need to be upgraded to
* a write.
*/
int deleteHistory(Uri uri, String selection, String[] selectionArgs) { int deleteHistory(Uri uri, String selection, String[] selectionArgs) {
debug("Deleting history entry for URI: " + uri); debug("Deleting history entry for URI: " + uri);
@ -1360,8 +1400,20 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
values.put(History.VISITS, 0); values.put(History.VISITS, 0);
values.put(History.DATE_MODIFIED, System.currentTimeMillis()); values.put(History.DATE_MODIFIED, System.currentTimeMillis());
cleanupSomeDeletedRecords(uri, History.CONTENT_URI, TABLE_HISTORY); // Doing this UPDATE (or the DELETE above) first ensures that the
return db.update(TABLE_HISTORY, values, selection, selectionArgs); // first operation within a new enclosing transaction is a write.
// The cleanup call below will do a SELECT first, and thus would
// require the transaction to be upgraded from a reader to a writer.
// In some cases that upgrade can fail (SQLITE_BUSY), so we avoid
// it if we can.
final int updated = db.update(TABLE_HISTORY, values, selection, selectionArgs);
try {
cleanupSomeDeletedRecords(uri, History.CONTENT_URI, TABLE_HISTORY);
} catch (Exception e) {
// We don't care.
Log.e(LOGTAG, "Unable to clean up deleted history records: ", e);
}
return updated;
} }
int deleteBookmarks(Uri uri, String selection, String[] selectionArgs) { int deleteBookmarks(Uri uri, String selection, String[] selectionArgs) {
@ -1370,6 +1422,7 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
final SQLiteDatabase db = getWritableDatabase(uri); final SQLiteDatabase db = getWritableDatabase(uri);
if (isCallerSync(uri)) { if (isCallerSync(uri)) {
beginWrite(db);
return db.delete(TABLE_BOOKMARKS, selection, selectionArgs); return db.delete(TABLE_BOOKMARKS, selection, selectionArgs);
} }
@ -1378,8 +1431,18 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(Bookmarks.IS_DELETED, 1); values.put(Bookmarks.IS_DELETED, 1);
cleanupSomeDeletedRecords(uri, Bookmarks.CONTENT_URI, TABLE_BOOKMARKS); // Doing this UPDATE (or the DELETE above) first ensures that the
return updateBookmarks(uri, values, selection, selectionArgs); // first operation within this transaction is a write.
// The cleanup call below will do a SELECT first, and thus would
// require the transaction to be upgraded from a reader to a writer.
final int updated = updateBookmarks(uri, values, selection, selectionArgs);
try {
cleanupSomeDeletedRecords(uri, Bookmarks.CONTENT_URI, TABLE_BOOKMARKS);
} catch (Exception e) {
// We don't care.
Log.e(LOGTAG, "Unable to clean up deleted bookmark records: ", e);
}
return updated;
} }
int deleteFavicons(Uri uri, String selection, String[] selectionArgs) { int deleteFavicons(Uri uri, String selection, String[] selectionArgs) {
@ -1430,29 +1493,36 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
throws OperationApplicationException { throws OperationApplicationException {
final int numOperations = operations.size(); final int numOperations = operations.size();
final ContentProviderResult[] results = new ContentProviderResult[numOperations]; final ContentProviderResult[] results = new ContentProviderResult[numOperations];
boolean failures = false;
SQLiteDatabase db = null;
if (numOperations >= 1) { if (numOperations < 1) {
// We only have 1 database for all Uri's that we can get debug("applyBatch: no operations; returning immediately.");
db = getWritableDatabase(operations.get(0).getUri());
} else {
// The original Android implementation returns a zero-length // The original Android implementation returns a zero-length
// array in this case, we do the same. // array in this case. We do the same.
return results; return results;
} }
boolean failures = false;
// We only have 1 database for all Uris that we can get.
SQLiteDatabase db = getWritableDatabase(operations.get(0).getUri());
// Note that the apply() call may cause us to generate // Note that the apply() call may cause us to generate
// additional transactions for the invidual operations. // additional transactions for the individual operations.
// But Android's wrapper for SQLite supports nested transactions, // But Android's wrapper for SQLite supports nested transactions,
// so this will do the right thing. // so this will do the right thing.
db.beginTransaction(); //
// Note further that in some circumstances this can result in
// exceptions: if this transaction is first involved in reading,
// and then (naturally) tries to perform writes, SQLITE_BUSY can
// be raised. See Bug 947939 and friends.
beginBatch(db);
for (int i = 0; i < numOperations; i++) { for (int i = 0; i < numOperations; i++) {
try { try {
results[i] = operations.get(i).apply(this, results, i); final ContentProviderOperation operation = operations.get(i);
results[i] = operation.apply(this, results, i);
} catch (SQLException e) { } catch (SQLException e) {
Log.w(LOGTAG, "SQLite Exception during applyBatch: ", e); Log.w(LOGTAG, "SQLite Exception during applyBatch.", e);
// The Android API makes it implementation-defined whether // The Android API makes it implementation-defined whether
// the failure of a single operation makes all others abort // the failure of a single operation makes all others abort
// or not. For our use cases, best-effort operation makes // or not. For our use cases, best-effort operation makes
@ -1485,8 +1555,8 @@ public class BrowserProvider extends TransactionalProvider<BrowserDatabaseHelper
} }
trace("Flushing DB applyBatch..."); trace("Flushing DB applyBatch...");
db.setTransactionSuccessful(); markBatchSuccessful(db);
db.endTransaction(); endBatch(db);
if (failures) { if (failures) {
throw new OperationApplicationException(); throw new OperationApplicationException();

View File

@ -57,21 +57,23 @@ public abstract class TransactionalProvider<T extends SQLiteOpenHelper> extends
/* /*
* Deletes items from the database within a DB transaction. * Deletes items from the database within a DB transaction.
* *
* @param uri query URI * @param uri Query URI.
* @param selection An optional filter to match rows to update. * @param selection An optional filter to match rows to delete.
* @param selectionArgs arguments for the selection * @param selectionArgs An array of arguments to substitute into the selection.
* @return number of rows impacted by the deletion *
* @return number of rows impacted by the deletion.
*/ */
abstract protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs); abstract protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs);
/* /*
* Updates the database within a DB transaction. * Updates the database within a DB transaction.
* *
* @param uri Query URI * @param uri Query URI.
* @param values A set of column_name/value pairs to add to the database. * @param values A set of column_name/value pairs to add to the database.
* @param selection An optional filter to match rows to update. * @param selection An optional filter to match rows to update.
* @param selectionArgs Arguments for the selection * @param selectionArgs An array of arguments to substitute into the selection.
* @return number of rows impacted by the update *
* @return number of rows impacted by the update.
*/ */
abstract protected int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs); abstract protected int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs);
@ -109,6 +111,10 @@ public abstract class TransactionalProvider<T extends SQLiteOpenHelper> extends
return mDatabases.getDatabaseHelperForProfile(profile, isTest(uri)).getWritableDatabase(); return mDatabases.getDatabaseHelperForProfile(profile, isTest(uri)).getWritableDatabase();
} }
protected SQLiteDatabase getWritableDatabaseForProfile(String profile, boolean isTest) {
return mDatabases.getDatabaseHelperForProfile(profile, isTest).getWritableDatabase();
}
@Override @Override
public boolean onCreate() { public boolean onCreate() {
synchronized (this) { synchronized (this) {
@ -125,25 +131,133 @@ public abstract class TransactionalProvider<T extends SQLiteOpenHelper> extends
return true; return true;
} }
/**
* Return true if OS version and database parallelism support indicates
* that this provider should bundle writes into transactions.
*/
@SuppressWarnings("static-method")
protected boolean shouldUseTransactions() {
return Build.VERSION.SDK_INT >= 11;
}
/**
* Track whether we're in a batch operation.
*
* When we're in a batch operation, individual write steps won't even try
* to start a transaction... and neither will they attempt to finish one.
*
* Set this to <code>Boolean.TRUE</code> when you're entering a batch --
* a section of code in which {@link ContentProvider} methods will be
* called, but nested transactions should not be started. Callers are
* responsible for beginning and ending the enclosing transaction, and
* for setting this to <code>Boolean.FALSE</code> when done.
*
* This is a ThreadLocal separate from `db.inTransaction` because batched
* operations start transactions independent of individual ContentProvider
* operations. This doesn't work well with the entire concept of this
* abstract class -- that is, automatically beginning and ending transactions
* for each insert/delete/update operation -- and doing so without
* causing arbitrary nesting requires external tracking.
*
* Note that beginWrite takes a DB argument, but we don't differentiate
* between databases in this tracking flag. If your ContentProvider manages
* multiple database transactions within the same thread, you'll need to
* amend this scheme -- but then, you're already doing some serious wizardry,
* so rock on.
*/
final ThreadLocal<Boolean> isInBatchOperation = new ThreadLocal<Boolean>();
private boolean isInBatch() {
final Boolean isInBatch = isInBatchOperation.get();
if (isInBatch == null) {
return false;
}
return isInBatch.booleanValue();
}
/**
* If we're not currently in a transaction, and we should be, start one.
*/
protected void beginWrite(final SQLiteDatabase db) {
if (isInBatch()) {
trace("Not bothering with an intermediate write transaction: inside batch operation.");
return;
}
if (shouldUseTransactions() && !db.inTransaction()) {
trace("beginWrite: beginning transaction.");
db.beginTransaction();
}
}
/**
* If we're not in a batch, but we are in a write transaction, mark it as
* successful.
*/
protected void markWriteSuccessful(final SQLiteDatabase db) {
if (isInBatch()) {
trace("Not marking write successful: inside batch operation.");
return;
}
if (shouldUseTransactions() && db.inTransaction()) {
trace("Marking write transaction successful.");
db.setTransactionSuccessful();
}
}
/**
* If we're not in a batch, but we are in a write transaction,
* end it.
*
* @see TransactionalProvider#markWriteSuccessful(SQLiteDatabase)
*/
protected void endWrite(final SQLiteDatabase db) {
if (isInBatch()) {
trace("Not ending write: inside batch operation.");
return;
}
if (shouldUseTransactions() && db.inTransaction()) {
trace("endWrite: ending transaction.");
db.endTransaction();
}
}
protected void beginBatch(final SQLiteDatabase db) {
trace("Beginning batch.");
isInBatchOperation.set(Boolean.TRUE);
db.beginTransaction();
}
protected void markBatchSuccessful(final SQLiteDatabase db) {
if (isInBatch()) {
trace("Marking batch successful.");
db.setTransactionSuccessful();
return;
}
Log.w(LOGTAG, "Unexpectedly asked to mark batch successful, but not in batch!");
throw new IllegalStateException("Not in batch.");
}
protected void endBatch(final SQLiteDatabase db) {
trace("Ending batch.");
db.endTransaction();
isInBatchOperation.set(Boolean.FALSE);
}
@Override @Override
public int delete(Uri uri, String selection, String[] selectionArgs) { public int delete(Uri uri, String selection, String[] selectionArgs) {
trace("Calling delete on URI: " + uri); trace("Calling delete on URI: " + uri + ", " + selection + ", " + selectionArgs);
final SQLiteDatabase db = getWritableDatabase(uri); final SQLiteDatabase db = getWritableDatabase(uri);
int deleted = 0; int deleted = 0;
if (Build.VERSION.SDK_INT >= 11) { try {
trace("Beginning delete transaction: " + uri);
db.beginTransaction();
try {
deleted = deleteInTransaction(uri, selection, selectionArgs);
db.setTransactionSuccessful();
trace("Successful delete transaction: " + uri);
} finally {
db.endTransaction();
}
} else {
deleted = deleteInTransaction(uri, selection, selectionArgs); deleted = deleteInTransaction(uri, selection, selectionArgs);
markWriteSuccessful(db);
} finally {
endWrite(db);
} }
if (deleted > 0) { if (deleted > 0) {
@ -160,23 +274,14 @@ public abstract class TransactionalProvider<T extends SQLiteOpenHelper> extends
final SQLiteDatabase db = getWritableDatabase(uri); final SQLiteDatabase db = getWritableDatabase(uri);
Uri result = null; Uri result = null;
try { try {
if (Build.VERSION.SDK_INT >= 11) { result = insertInTransaction(uri, values);
trace("Beginning insert transaction: " + uri); markWriteSuccessful(db);
db.beginTransaction();
try {
result = insertInTransaction(uri, values);
db.setTransactionSuccessful();
trace("Successful insert transaction: " + uri);
} finally {
db.endTransaction();
}
} else {
result = insertInTransaction(uri, values);
}
} catch (SQLException sqle) { } catch (SQLException sqle) {
Log.e(LOGTAG, "exception in DB operation", sqle); Log.e(LOGTAG, "exception in DB operation", sqle);
} catch (UnsupportedOperationException uoe) { } catch (UnsupportedOperationException uoe) {
Log.e(LOGTAG, "don't know how to perform that insert", uoe); Log.e(LOGTAG, "don't know how to perform that insert", uoe);
} finally {
endWrite(db);
} }
if (result != null) { if (result != null) {
@ -190,23 +295,17 @@ public abstract class TransactionalProvider<T extends SQLiteOpenHelper> extends
@Override @Override
public int update(Uri uri, ContentValues values, String selection, public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) { String[] selectionArgs) {
trace("Calling update on URI: " + uri); trace("Calling update on URI: " + uri + ", " + selection + ", " + selectionArgs);
final SQLiteDatabase db = getWritableDatabase(uri); final SQLiteDatabase db = getWritableDatabase(uri);
int updated = 0; int updated = 0;
if (Build.VERSION.SDK_INT >= 11) { try {
trace("Beginning update transaction: " + uri); updated = updateInTransaction(uri, values, selection,
db.beginTransaction(); selectionArgs);
try { markWriteSuccessful(db);
updated = updateInTransaction(uri, values, selection, selectionArgs); } finally {
db.setTransactionSuccessful(); endWrite(db);
trace("Successful update transaction: " + uri);
} finally {
db.endTransaction();
}
} else {
updated = updateInTransaction(uri, values, selection, selectionArgs);
} }
if (updated > 0) { if (updated > 0) {
@ -227,7 +326,8 @@ public abstract class TransactionalProvider<T extends SQLiteOpenHelper> extends
final SQLiteDatabase db = getWritableDatabase(uri); final SQLiteDatabase db = getWritableDatabase(uri);
db.beginTransaction(); debug("bulkInsert: explicitly starting transaction.");
beginBatch(db);
try { try {
for (int i = 0; i < numValues; i++) { for (int i = 0; i < numValues; i++) {
@ -235,9 +335,10 @@ public abstract class TransactionalProvider<T extends SQLiteOpenHelper> extends
successes++; successes++;
} }
trace("Flushing DB bulkinsert..."); trace("Flushing DB bulkinsert...");
db.setTransactionSuccessful(); markBatchSuccessful(db);
} finally { } finally {
db.endTransaction(); debug("bulkInsert: explicitly ending transaction.");
endBatch(db);
} }
if (successes > 0) { if (successes > 0) {

View File

@ -297,11 +297,14 @@ gbjar.sources += [
'PrivateTab.java', 'PrivateTab.java',
'prompts/ColorPickerInput.java', 'prompts/ColorPickerInput.java',
'prompts/IconGridInput.java', 'prompts/IconGridInput.java',
'prompts/IntentChooserPrompt.java',
'prompts/IntentHandler.java',
'prompts/Prompt.java', 'prompts/Prompt.java',
'prompts/PromptInput.java', 'prompts/PromptInput.java',
'prompts/PromptListAdapter.java', 'prompts/PromptListAdapter.java',
'prompts/PromptListItem.java', 'prompts/PromptListItem.java',
'prompts/PromptService.java', 'prompts/PromptService.java',
'prompts/TabInput.java',
'ReaderModeUtils.java', 'ReaderModeUtils.java',
'ReferrerReceiver.java', 'ReferrerReceiver.java',
'RemoteTabs.java', 'RemoteTabs.java',

View File

@ -0,0 +1,157 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.prompts;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.GeckoActionProvider;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.widget.ListView;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
/**
* Shows a prompt letting the user pick from a list of intent handlers for a set of Intents or
* for a GeckoActionProvider. Basic usage:
* IntentChooserPrompt prompt = new IntentChooserPrompt(context, new Intent[] {
* ... // some intents
* });
* prompt.show("Title", context, new IntentHandler() {
* public void onIntentSelected(Intent intent, int position) { }
* public void onCancelled() { }
* });
**/
public class IntentChooserPrompt {
private static final String LOGTAG = "GeckoIntentChooser";
private final ArrayList<PromptListItem> mItems;
public IntentChooserPrompt(Context context, Intent[] intents) {
mItems = getItems(context, intents);
}
public IntentChooserPrompt(Context context, GeckoActionProvider provider) {
mItems = getItems(context, provider);
}
/* If an IntentHandler is passed in, will asynchronously call the handler when the dialog is closed
* Otherwise, will return the Intent that was chosen by the user. Must be called on the UI thread.
*/
public void show(final String title, final Context context, final IntentHandler handler) {
ThreadUtils.assertOnUiThread();
if (mItems.isEmpty()) {
Log.i(LOGTAG, "No activities for the intent chooser!");
handler.onCancelled();
return;
}
// If there's only one item in the intent list, just return it
if (mItems.size() == 1) {
handler.onIntentSelected(mItems.get(0).intent, 0);
return;
}
final Prompt prompt = new Prompt(context, new Prompt.PromptCallback() {
@Override
public void onPromptFinished(String promptServiceResult) {
if (handler == null) {
return;
}
int itemId = -1;
try {
itemId = new JSONObject(promptServiceResult).getInt("button");
} catch (JSONException e) {
Log.e(LOGTAG, "result from promptservice was invalid: ", e);
}
if (itemId == -1) {
handler.onCancelled();
} else {
handler.onIntentSelected(mItems.get(itemId).intent, itemId);
}
}
});
PromptListItem[] arrays = new PromptListItem[mItems.size()];
mItems.toArray(arrays);
prompt.show(title, "", arrays, ListView.CHOICE_MODE_NONE);
return;
}
// Whether or not any activities were found. Useful for checking if you should try a different Intent set
public boolean hasActivities(Context context) {
return mItems.isEmpty();
}
// Gets a list of PromptListItems for an Intent array
private ArrayList<PromptListItem> getItems(final Context context, Intent[] intents) {
final ArrayList<PromptListItem> items = new ArrayList<PromptListItem>();
// If we have intents, use them to build the initial list
for (final Intent intent : intents) {
items.addAll(getItemsForIntent(context, intent));
}
return items;
}
// Gets a list of PromptListItems for a GeckoActionProvider
private ArrayList<PromptListItem> getItems(final Context context, final GeckoActionProvider provider) {
final ArrayList<PromptListItem> items = new ArrayList<PromptListItem>();
// Add any intents from the provider.
final PackageManager packageManager = context.getPackageManager();
final ArrayList<ResolveInfo> infos = provider.getSortedActivites();
for (final ResolveInfo info : infos) {
items.add(getItemForResolveInfo(info, packageManager, provider.getIntent()));
}
return items;
}
private PromptListItem getItemForResolveInfo(ResolveInfo info, PackageManager pm, Intent intent) {
PromptListItem item = new PromptListItem(info.loadLabel(pm).toString());
item.icon = info.loadIcon(pm);
item.intent = new Intent(intent);
// These intents should be implicit.
item.intent.setComponent(new ComponentName(info.activityInfo.applicationInfo.packageName,
info.activityInfo.name));
return item;
}
private ArrayList<PromptListItem> getItemsForIntent(Context context, Intent intent) {
ArrayList<PromptListItem> items = new ArrayList<PromptListItem>();
PackageManager pm = context.getPackageManager();
List<ResolveInfo> lri = pm.queryIntentActivityOptions(GeckoAppShell.getGeckoInterface().getActivity().getComponentName(), null, intent, 0);
// If we didn't find any activities, just return the empty list
if (lri == null) {
return items;
}
// Otherwise, convert the ResolveInfo. Note we don't currently check for duplicates here.
for (ResolveInfo ri : lri) {
items.add(getItemForResolveInfo(ri, pm, intent));
}
return items;
}
}

View File

@ -0,0 +1,12 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.prompts;
import android.content.Intent;
public interface IntentHandler {
public void onIntentSelected(Intent intent, int position);
public void onCancelled();
}

View File

@ -76,6 +76,7 @@ public class PromptInput {
mView = (View)input; mView = (View)input;
return mView; return mView;
} }
@Override @Override
public Object getValue() { public Object getValue() {
EditText edit = (EditText)mView; EditText edit = (EditText)mView;
@ -111,6 +112,7 @@ public class PromptInput {
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
return input; return input;
} }
@Override @Override
public Object getValue() { public Object getValue() {
EditText edit = (EditText)mView; EditText edit = (EditText)mView;
@ -135,6 +137,7 @@ public class PromptInput {
mView = (View)checkbox; mView = (View)checkbox;
return mView; return mView;
} }
@Override @Override
public Object getValue() { public Object getValue() {
CheckBox checkbox = (CheckBox)mView; CheckBox checkbox = (CheckBox)mView;
@ -213,6 +216,7 @@ public class PromptInput {
private static String formatDateString(String dateFormat, Calendar calendar) { private static String formatDateString(String dateFormat, Calendar calendar) {
return new SimpleDateFormat(dateFormat).format(calendar.getTime()); return new SimpleDateFormat(dateFormat).format(calendar.getTime());
} }
@Override @Override
public Object getValue() { public Object getValue() {
if (Build.VERSION.SDK_INT < 11 && mType.equals("date")) { if (Build.VERSION.SDK_INT < 11 && mType.equals("date")) {
@ -293,6 +297,7 @@ public class PromptInput {
return spinner; return spinner;
} }
@Override @Override
public Object getValue() { public Object getValue() {
return new Integer(spinner.getSelectedItemPosition()); return new Integer(spinner.getSelectedItemPosition());
@ -312,10 +317,6 @@ public class PromptInput {
mView = view; mView = view;
return mView; return mView;
} }
@Override
public Object getValue() {
return "";
}
} }
public PromptInput(JSONObject obj) { public PromptInput(JSONObject obj) {
@ -344,6 +345,8 @@ public class PromptInput {
return new IconGridInput(obj); return new IconGridInput(obj);
} else if (ColorPickerInput.INPUT_TYPE.equals(type)) { } else if (ColorPickerInput.INPUT_TYPE.equals(type)) {
return new ColorPickerInput(obj); return new ColorPickerInput(obj);
} else if (TabInput.INPUT_TYPE.equals(type)) {
return new TabInput(obj);
} else { } else {
for (String dtType : DateTimeInput.INPUT_TYPES) { for (String dtType : DateTimeInput.INPUT_TYPES) {
if (dtType.equals(type)) { if (dtType.equals(type)) {
@ -363,7 +366,7 @@ public class PromptInput {
} }
public Object getValue() { public Object getValue() {
return ""; return null;
} }
public boolean getScrollable() { public boolean getScrollable() {
@ -371,6 +374,6 @@ public class PromptInput {
} }
public boolean canApplyInputStyle() { public boolean canApplyInputStyle() {
return true; return true;
} }
} }

View File

@ -4,7 +4,9 @@ import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.json.JSONException; import org.json.JSONException;
import android.content.Intent;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import java.util.List; import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;
@ -17,6 +19,7 @@ public class PromptListItem {
public final boolean disabled; public final boolean disabled;
public final int id; public final int id;
public boolean selected; public boolean selected;
public Intent intent;
public boolean isParent; public boolean isParent;
public Drawable icon; public Drawable icon;

View File

@ -0,0 +1,109 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.prompts;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.ThreadUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONException;
import android.content.Context;
import android.view.View;
import android.view.LayoutInflater;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TabHost;
import android.widget.TabWidget;
import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.LinearLayout.LayoutParams;
import java.util.LinkedHashMap;
import android.util.Log;
import android.widget.AdapterView;
public class TabInput extends PromptInput implements AdapterView.OnItemClickListener {
public static final String INPUT_TYPE = "tabs";
public static final String LOGTAG = "GeckoTabInput";
/* Keeping the order of this in sync with the JSON is important. */
final private LinkedHashMap<String, PromptListItem[]> mTabs;
private TabHost mHost;
private int mPosition;
public TabInput(JSONObject obj) {
super(obj);
mTabs = new LinkedHashMap<String, PromptListItem[]>();
try {
JSONArray tabs = obj.getJSONArray("items");
for (int i = 0; i < tabs.length(); i++) {
JSONObject tab = tabs.getJSONObject(i);
String title = tab.getString("label");
JSONArray items = tab.getJSONArray("items");
mTabs.put(title, PromptListItem.getArray(items));
}
} catch(JSONException ex) {
Log.e(LOGTAG, "Exception", ex);
}
}
@Override
public View getView(final Context context) throws UnsupportedOperationException {
LayoutInflater inflater = LayoutInflater.from(context);
mHost = (TabHost) inflater.inflate(R.layout.tab_prompt_input, null);
mHost.setup();
for (String title : mTabs.keySet()) {
final TabHost.TabSpec spec = mHost.newTabSpec(title);
spec.setContent(new TabHost.TabContentFactory() {
@Override
public View createTabContent(final String tag) {
PromptListAdapter adapter = new PromptListAdapter(context, android.R.layout.simple_list_item_1, mTabs.get(tag));
ListView listView = new ListView(context);
listView.setOnItemClickListener(TabInput.this);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
listView.setAdapter(adapter);
return listView;
}
});
spec.setIndicator(title);
mHost.addTab(spec);
}
mView = mHost;
return mHost;
}
@Override
public Object getValue() {
JSONObject obj = new JSONObject();
try {
obj.put("tab", mHost.getCurrentTab());
obj.put("item", mPosition);
} catch(JSONException ex) { }
return obj;
}
@Override
public boolean getScrollable() {
return true;
}
@Override
public boolean canApplyInputStyle() {
return false;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ThreadUtils.assertOnUiThread();
mPosition = position;
}
}

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:minHeight="100dp">
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</TabWidget>
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</FrameLayout>
</LinearLayout>
</TabHost>

View File

@ -65,17 +65,41 @@ public class testBrowserProvider extends ContentProviderTest {
BrowserContract.Bookmarks.READING_LIST_FOLDER_GUID }); BrowserContract.Bookmarks.READING_LIST_FOLDER_GUID });
c = mProvider.query(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), null, null, null, null); c = mProvider.query(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), null, null, null, null);
mAsserter.is(c.getCount(), 7, "All non-special bookmarks and folders were deleted"); assertCountIsAndClose(c, 7, "All non-special bookmarks and folders were deleted");
mProvider.delete(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"), null, null); mProvider.delete(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"), null, null);
c = mProvider.query(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), null, null, null, null); c = mProvider.query(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), null, null, null, null);
mAsserter.is(c.getCount(), 0, "All history entries were deleted"); assertCountIsAndClose(c, 0, "All history entries were deleted");
mProvider.delete(BrowserContract.Favicons.CONTENT_URI, null, null); /**
mAsserter.is(c.getCount(), 0, "All favicons were deleted"); * There's no reason why the following two parts should fail.
* But sometimes they do, and I'm not going to spend the time
* to figure out why in an unrelated bug.
*/
mProvider.delete(BrowserContract.Thumbnails.CONTENT_URI, null, null); mProvider.delete(appendUriParam(BrowserContract.Favicons.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"), null, null);
mAsserter.is(c.getCount(), 0, "All thumbnails were deleted"); c = mProvider.query(appendUriParam(BrowserContract.Favicons.CONTENT_URI,
BrowserContract.PARAM_SHOW_DELETED, "1"),
null, null, null, null);
if (c.getCount() > 0) {
mAsserter.dumpLog("Unexpected favicons in ensureEmptyDatabase.");
}
c.close();
// assertCountIsAndClose(c, 0, "All favicons were deleted");
mProvider.delete(appendUriParam(BrowserContract.Thumbnails.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"), null, null);
c = mProvider.query(appendUriParam(BrowserContract.Thumbnails.CONTENT_URI,
BrowserContract.PARAM_SHOW_DELETED, "1"),
null, null, null, null);
if (c.getCount() > 0) {
mAsserter.dumpLog("Unexpected thumbnails in ensureEmptyDatabase.");
}
c.close();
// assertCountIsAndClose(c, 0, "All thumbnails were deleted");
} }
private ContentValues createBookmark(String title, String url, long parentId, private ContentValues createBookmark(String title, String url, long parentId,
@ -326,7 +350,7 @@ public class testBrowserProvider extends ContentProviderTest {
mAsserter.is(seenException, false, "Batch updating succeded"); mAsserter.is(seenException, false, "Batch updating succeded");
mOperations.clear(); mOperations.clear();
// Delte all visits // Delete all visits
for (int i = 0; i < TESTCOUNT; i++) { for (int i = 0; i < TESTCOUNT; i++) {
builder = ContentProviderOperation.newDelete(BrowserContract.History.CONTENT_URI); builder = ContentProviderOperation.newDelete(BrowserContract.History.CONTENT_URI);
builder.withSelection(BrowserContract.History.URL + " = ?", builder.withSelection(BrowserContract.History.URL + " = ?",
@ -340,7 +364,7 @@ public class testBrowserProvider extends ContentProviderTest {
} catch (OperationApplicationException ex) { } catch (OperationApplicationException ex) {
seenException = true; seenException = true;
} }
mAsserter.is(seenException, false, "Batch deletion succeded"); mAsserter.is(seenException, false, "Batch deletion succeeded");
} }
// Force a Constraint error, see if later operations still apply correctly // Force a Constraint error, see if later operations still apply correctly
@ -475,6 +499,8 @@ public class testBrowserProvider extends ContentProviderTest {
mAsserter.is(new Integer(parentId), new Integer(rootId), mAsserter.is(new Integer(parentId), new Integer(rootId),
"The PARENT of the " + guid + " special folder is correct"); "The PARENT of the " + guid + " special folder is correct");
} }
c.close();
} }
} }
@ -549,6 +575,7 @@ public class testBrowserProvider extends ContentProviderTest {
mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.Bookmarks.TYPE)), String.valueOf(BrowserContract.Bookmarks.TYPE_BOOKMARK), mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.Bookmarks.TYPE)), String.valueOf(BrowserContract.Bookmarks.TYPE_BOOKMARK),
"Inserted bookmark has correct default type"); "Inserted bookmark has correct default type");
c.close();
} }
} }
@ -571,12 +598,14 @@ public class testBrowserProvider extends ContentProviderTest {
mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Bookmarks.FAVICON)), "UTF8"), mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Bookmarks.FAVICON)), "UTF8"),
favicon, "Inserted bookmark has corresponding favicon image"); favicon, "Inserted bookmark has corresponding favicon image");
c.close();
c = getFaviconsByUrl(pageUrl); c = getFaviconsByUrl(pageUrl);
mAsserter.is(c.moveToFirst(), true, "Inserted favicon found"); mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"), mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
favicon, "Inserted favicon has corresponding favicon image"); favicon, "Inserted favicon has corresponding favicon image");
c.close();
} }
} }
@ -587,6 +616,7 @@ public class testBrowserProvider extends ContentProviderTest {
Cursor c = getBookmarkById(id); Cursor c = getBookmarkById(id);
mAsserter.is(c.moveToFirst(), true, "Inserted bookmark found"); mAsserter.is(c.moveToFirst(), true, "Inserted bookmark found");
c.close();
return id; return id;
} }
@ -603,6 +633,7 @@ public class testBrowserProvider extends ContentProviderTest {
Cursor c = getBookmarkById(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id); Cursor c = getBookmarkById(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id);
mAsserter.is(c.moveToFirst(), true, "Deleted bookmark was only marked as deleted"); mAsserter.is(c.moveToFirst(), true, "Deleted bookmark was only marked as deleted");
c.close();
deleted = mProvider.delete(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"), deleted = mProvider.delete(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"),
BrowserContract.Bookmarks._ID + " = ?", BrowserContract.Bookmarks._ID + " = ?",
@ -612,6 +643,7 @@ public class testBrowserProvider extends ContentProviderTest {
c = getBookmarkById(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id); c = getBookmarkById(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id);
mAsserter.is(c.moveToFirst(), false, "Inserted bookmark is now actually deleted"); mAsserter.is(c.moveToFirst(), false, "Inserted bookmark is now actually deleted");
c.close();
id = insertOneBookmark(); id = insertOneBookmark();
@ -622,6 +654,7 @@ public class testBrowserProvider extends ContentProviderTest {
c = getBookmarkById(id); c = getBookmarkById(id);
mAsserter.is(c.moveToFirst(), false, mAsserter.is(c.moveToFirst(), false,
"Inserted bookmark can't be found after deletion using URI with ID"); "Inserted bookmark can't be found after deletion using URI with ID");
c.close();
if (Build.VERSION.SDK_INT >= 8 && if (Build.VERSION.SDK_INT >= 8 &&
Build.VERSION.SDK_INT < 16) { Build.VERSION.SDK_INT < 16) {
@ -631,6 +664,7 @@ public class testBrowserProvider extends ContentProviderTest {
long parentId = ContentUris.parseId(mProvider.insert(BrowserContract.Bookmarks.CONTENT_URI, b)); long parentId = ContentUris.parseId(mProvider.insert(BrowserContract.Bookmarks.CONTENT_URI, b));
c = getBookmarkById(parentId); c = getBookmarkById(parentId);
mAsserter.is(c.moveToFirst(), true, "Inserted bookmarks folder found"); mAsserter.is(c.moveToFirst(), true, "Inserted bookmarks folder found");
c.close();
b = createBookmark("Example", "http://example.com", parentId, b = createBookmark("Example", "http://example.com", parentId,
BrowserContract.Bookmarks.TYPE_BOOKMARK, 0, "tags", "description", "keyword"); BrowserContract.Bookmarks.TYPE_BOOKMARK, 0, "tags", "description", "keyword");
@ -638,6 +672,7 @@ public class testBrowserProvider extends ContentProviderTest {
id = ContentUris.parseId(mProvider.insert(BrowserContract.Bookmarks.CONTENT_URI, b)); id = ContentUris.parseId(mProvider.insert(BrowserContract.Bookmarks.CONTENT_URI, b));
c = getBookmarkById(id); c = getBookmarkById(id);
mAsserter.is(c.moveToFirst(), true, "Inserted bookmark found"); mAsserter.is(c.moveToFirst(), true, "Inserted bookmark found");
c.close();
deleted = 0; deleted = 0;
try { try {
@ -664,11 +699,13 @@ public class testBrowserProvider extends ContentProviderTest {
Cursor c = getFaviconsByUrl(pageUrl); Cursor c = getFaviconsByUrl(pageUrl);
mAsserter.is(c.moveToFirst(), true, "Inserted favicon found"); mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
c.close();
mProvider.delete(ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id), null, null); mProvider.delete(ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id), null, null);
c = getFaviconsByUrl(pageUrl); c = getFaviconsByUrl(pageUrl);
mAsserter.is(c.moveToFirst(), false, "Favicon is deleted with last reference to it"); mAsserter.is(c.moveToFirst(), false, "Favicon is deleted with last reference to it");
c.close();
} }
} }
@ -713,6 +750,7 @@ public class testBrowserProvider extends ContentProviderTest {
new String[] { String.valueOf(id) }); new String[] { String.valueOf(id) });
mAsserter.is((updated == 1), true, "Inserted bookmark was updated"); mAsserter.is((updated == 1), true, "Inserted bookmark was updated");
c.close();
c = getBookmarkById(id); c = getBookmarkById(id);
mAsserter.is(c.moveToFirst(), true, "Updated bookmark found"); mAsserter.is(c.moveToFirst(), true, "Updated bookmark found");
@ -752,12 +790,14 @@ public class testBrowserProvider extends ContentProviderTest {
u.put(BrowserContract.Bookmarks.URL, "http://examples2.com"); u.put(BrowserContract.Bookmarks.URL, "http://examples2.com");
updated = mProvider.update(ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id), u, null, null); updated = mProvider.update(ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id), u, null, null);
c.close();
c = getBookmarkById(id); c = getBookmarkById(id);
mAsserter.is(c.moveToFirst(), true, "Updated bookmark found"); mAsserter.is(c.moveToFirst(), true, "Updated bookmark found");
mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.Bookmarks.URL)), u.getAsString(BrowserContract.Bookmarks.URL), mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.Bookmarks.URL)), u.getAsString(BrowserContract.Bookmarks.URL),
"Updated bookmark has correct URL using URI with id"); "Updated bookmark has correct URL using URI with id");
c.close();
} }
} }
@ -784,12 +824,14 @@ public class testBrowserProvider extends ContentProviderTest {
ContentValues u = createFaviconEntry(pageUrl, newFavicon); ContentValues u = createFaviconEntry(pageUrl, newFavicon);
mProvider.update(BrowserContract.Favicons.CONTENT_URI, u, null, null); mProvider.update(BrowserContract.Favicons.CONTENT_URI, u, null, null);
c.close();
c = getFaviconsByUrl(pageUrl); c = getFaviconsByUrl(pageUrl);
mAsserter.is(c.moveToFirst(), true, "Updated favicon found"); mAsserter.is(c.moveToFirst(), true, "Updated favicon found");
mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"), mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
newFavicon, "Updated favicon has corresponding favicon image"); newFavicon, "Updated favicon has corresponding favicon image");
c.close();
} }
} }
@ -825,6 +867,7 @@ public class testBrowserProvider extends ContentProviderTest {
} }
mAsserter.is(i, count, "Folder has the right number of children."); mAsserter.is(i, count, "Folder has the right number of children.");
c.close();
} }
public static final int NUMBER_OF_CHILDREN = 1001; public static final int NUMBER_OF_CHILDREN = 1001;
@ -921,6 +964,7 @@ public class testBrowserProvider extends ContentProviderTest {
id = insertWithNullCol(BrowserContract.History.VISITS); id = insertWithNullCol(BrowserContract.History.VISITS);
mAsserter.is(new Long(id), new Long(-1), mAsserter.is(new Long(id), new Long(-1),
"Should not be able to insert history with null number of visits"); "Should not be able to insert history with null number of visits");
c.close();
} }
} }
@ -943,12 +987,14 @@ public class testBrowserProvider extends ContentProviderTest {
mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.History.FAVICON)), "UTF8"), mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.History.FAVICON)), "UTF8"),
favicon, "Inserted history entry has corresponding favicon image"); favicon, "Inserted history entry has corresponding favicon image");
c.close();
c = getFaviconsByUrl(pageUrl); c = getFaviconsByUrl(pageUrl);
mAsserter.is(c.moveToFirst(), true, "Inserted favicon found"); mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"), mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
favicon, "Inserted favicon has corresponding favicon image"); favicon, "Inserted favicon has corresponding favicon image");
c.close();
} }
} }
@ -959,6 +1005,7 @@ public class testBrowserProvider extends ContentProviderTest {
Cursor c = getHistoryEntryById(id); Cursor c = getHistoryEntryById(id);
mAsserter.is(c.moveToFirst(), true, "Inserted history entry found"); mAsserter.is(c.moveToFirst(), true, "Inserted history entry found");
c.close();
return id; return id;
} }
@ -981,6 +1028,7 @@ public class testBrowserProvider extends ContentProviderTest {
new String[] { String.valueOf(id) }); new String[] { String.valueOf(id) });
mAsserter.is((deleted == 1), true, "Inserted history entry was deleted"); mAsserter.is((deleted == 1), true, "Inserted history entry was deleted");
c.close();
c = getHistoryEntryById(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id); c = getHistoryEntryById(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id);
mAsserter.is(c.moveToFirst(), false, "Inserted history is now actually deleted"); mAsserter.is(c.moveToFirst(), false, "Inserted history is now actually deleted");
@ -990,10 +1038,12 @@ public class testBrowserProvider extends ContentProviderTest {
deleted = mProvider.delete(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), null, null); deleted = mProvider.delete(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), null, null);
mAsserter.is((deleted == 1), true, mAsserter.is((deleted == 1), true,
"Inserted history entry was deleted using URI with id"); "Inserted history entry was deleted using URI with id");
c.close();
c = getHistoryEntryById(id); c = getHistoryEntryById(id);
mAsserter.is(c.moveToFirst(), false, mAsserter.is(c.moveToFirst(), false,
"Inserted history entry can't be found after deletion using URI with ID"); "Inserted history entry can't be found after deletion using URI with ID");
c.close();
} }
} }
@ -1012,9 +1062,11 @@ public class testBrowserProvider extends ContentProviderTest {
mAsserter.is(c.moveToFirst(), true, "Inserted favicon found"); mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
mProvider.delete(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), null, null); mProvider.delete(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), null, null);
c.close();
c = getFaviconsByUrl(pageUrl); c = getFaviconsByUrl(pageUrl);
mAsserter.is(c.moveToFirst(), false, "Favicon is deleted with last reference to it"); mAsserter.is(c.moveToFirst(), false, "Favicon is deleted with last reference to it");
c.close();
} }
} }
@ -1056,6 +1108,7 @@ public class testBrowserProvider extends ContentProviderTest {
new String[] { String.valueOf(id) }); new String[] { String.valueOf(id) });
mAsserter.is((updated == 1), true, "Inserted history entry was updated"); mAsserter.is((updated == 1), true, "Inserted history entry was updated");
c.close();
c = getHistoryEntryById(id); c = getHistoryEntryById(id);
mAsserter.is(c.moveToFirst(), true, "Updated history entry found"); mAsserter.is(c.moveToFirst(), true, "Updated history entry found");
@ -1089,12 +1142,14 @@ public class testBrowserProvider extends ContentProviderTest {
u.put(BrowserContract.History.URL, "http://examples2.com"); u.put(BrowserContract.History.URL, "http://examples2.com");
updated = mProvider.update(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), u, null, null); updated = mProvider.update(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), u, null, null);
c.close();
c = getHistoryEntryById(id); c = getHistoryEntryById(id);
mAsserter.is(c.moveToFirst(), true, "Updated history entry found"); mAsserter.is(c.moveToFirst(), true, "Updated history entry found");
mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.History.URL)), u.getAsString(BrowserContract.History.URL), mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.History.URL)), u.getAsString(BrowserContract.History.URL),
"Updated history entry has correct URL using URI with id"); "Updated history entry has correct URL using URI with id");
c.close();
} }
} }
@ -1121,12 +1176,14 @@ public class testBrowserProvider extends ContentProviderTest {
ContentValues u = createFaviconEntry(pageUrl, newFavicon); ContentValues u = createFaviconEntry(pageUrl, newFavicon);
mProvider.update(BrowserContract.Favicons.CONTENT_URI, u, null, null); mProvider.update(BrowserContract.Favicons.CONTENT_URI, u, null, null);
c.close();
c = getFaviconsByUrl(pageUrl); c = getFaviconsByUrl(pageUrl);
mAsserter.is(c.moveToFirst(), true, "Updated favicon found"); mAsserter.is(c.moveToFirst(), true, "Updated favicon found");
mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"), mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
newFavicon, "Updated favicon has corresponding favicon image"); newFavicon, "Updated favicon has corresponding favicon image");
c.close();
} }
} }
@ -1195,6 +1252,7 @@ public class testBrowserProvider extends ContentProviderTest {
BrowserContract.History._ID + " = ?", BrowserContract.History._ID + " = ?",
new String[] { String.valueOf(id) }); new String[] { String.valueOf(id) });
mAsserter.is((updated == 1), true, "Inserted history entry was updated"); mAsserter.is((updated == 1), true, "Inserted history entry was updated");
c.close();
c = getHistoryEntryById(id); c = getHistoryEntryById(id);
mAsserter.is(c.moveToFirst(), true, "Updated history entry found"); mAsserter.is(c.moveToFirst(), true, "Updated history entry found");
@ -1220,6 +1278,8 @@ public class testBrowserProvider extends ContentProviderTest {
mAsserter.is((updated == 1), true, "History entry was inserted"); mAsserter.is((updated == 1), true, "History entry was inserted");
id = getHistoryEntryIdByUrl(TEST_URL_2); id = getHistoryEntryIdByUrl(TEST_URL_2);
c.close();
c = getHistoryEntryById(id); c = getHistoryEntryById(id);
mAsserter.is(c.moveToFirst(), true, "History entry was inserted"); mAsserter.is(c.moveToFirst(), true, "History entry was inserted");
@ -1239,6 +1299,7 @@ public class testBrowserProvider extends ContentProviderTest {
BrowserContract.History._ID + " = ?", BrowserContract.History._ID + " = ?",
new String[] { String.valueOf(id) }); new String[] { String.valueOf(id) });
mAsserter.is((updated == 1), true, "Inserted history entry was updated"); mAsserter.is((updated == 1), true, "Inserted history entry was updated");
c.close();
c = getHistoryEntryById(id); c = getHistoryEntryById(id);
mAsserter.is(c.moveToFirst(), true, "Updated history entry found"); mAsserter.is(c.moveToFirst(), true, "Updated history entry found");
@ -1253,6 +1314,7 @@ public class testBrowserProvider extends ContentProviderTest {
"Updated history entry has same creation date"); "Updated history entry has same creation date");
mAsserter.isnot(new Long(c.getLong(c.getColumnIndex(BrowserContract.History.DATE_MODIFIED))), new Long(dateModified), mAsserter.isnot(new Long(c.getLong(c.getColumnIndex(BrowserContract.History.DATE_MODIFIED))), new Long(dateModified),
"Updated history entry has new modification date"); "Updated history entry has new modification date");
c.close();
} }
} }
@ -1275,6 +1337,7 @@ public class testBrowserProvider extends ContentProviderTest {
mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Thumbnails.DATA)), "UTF8"), mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Thumbnails.DATA)), "UTF8"),
thumbnail, "Inserted thumbnail has corresponding thumbnail image"); thumbnail, "Inserted thumbnail has corresponding thumbnail image");
c.close();
} }
} }
@ -1301,12 +1364,14 @@ public class testBrowserProvider extends ContentProviderTest {
ContentValues u = createThumbnailEntry(pageUrl, newThumbnail); ContentValues u = createThumbnailEntry(pageUrl, newThumbnail);
mProvider.update(BrowserContract.Thumbnails.CONTENT_URI, u, null, null); mProvider.update(BrowserContract.Thumbnails.CONTENT_URI, u, null, null);
c.close();
c = getThumbnailByUrl(pageUrl); c = getThumbnailByUrl(pageUrl);
mAsserter.is(c.moveToFirst(), true, "Updated thumbnail found"); mAsserter.is(c.moveToFirst(), true, "Updated thumbnail found");
mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Thumbnails.DATA)), "UTF8"), mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Thumbnails.DATA)), "UTF8"),
newThumbnail, "Updated thumbnail has corresponding thumbnail image"); newThumbnail, "Updated thumbnail has corresponding thumbnail image");
c.close();
} }
} }
@ -1325,9 +1390,11 @@ public class testBrowserProvider extends ContentProviderTest {
mAsserter.is(c.moveToFirst(), true, "Inserted thumbnail found"); mAsserter.is(c.moveToFirst(), true, "Inserted thumbnail found");
mProvider.delete(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), null, null); mProvider.delete(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), null, null);
c.close();
c = getThumbnailByUrl(pageUrl); c = getThumbnailByUrl(pageUrl);
mAsserter.is(c.moveToFirst(), false, "Thumbnail is deleted with last reference to it"); mAsserter.is(c.moveToFirst(), false, "Thumbnail is deleted with last reference to it");
c.close();
} }
} }
@ -1435,6 +1502,7 @@ public class testBrowserProvider extends ContentProviderTest {
"Combined entry has correct number of visits"); "Combined entry has correct number of visits");
mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.DATE_LAST_VISITED))), new Long(LAST_VISITED), mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.DATE_LAST_VISITED))), new Long(LAST_VISITED),
"Combined entry has correct last visit time"); "Combined entry has correct last visit time");
c.close();
} }
} }
@ -1495,6 +1563,7 @@ public class testBrowserProvider extends ContentProviderTest {
mAsserter.is(new Integer(display), new Integer(expectedDisplay), mAsserter.is(new Integer(display), new Integer(expectedDisplay),
"Combined display column should always be DISPLAY_READER for the reading list item"); "Combined display column should always be DISPLAY_READER for the reading list item");
} }
c.close();
} }
} }
@ -1527,6 +1596,7 @@ public class testBrowserProvider extends ContentProviderTest {
new String[] { String.valueOf(combinedBookmarkId) }); new String[] { String.valueOf(combinedBookmarkId) });
mAsserter.is((deleted == 1), true, "Inserted combined bookmark was deleted"); mAsserter.is((deleted == 1), true, "Inserted combined bookmark was deleted");
c.close();
c = mProvider.query(BrowserContract.Combined.CONTENT_URI, null, "", null, null); c = mProvider.query(BrowserContract.Combined.CONTENT_URI, null, "", null, null);
mAsserter.is(c.getCount(), 1, "1 combined entry found"); mAsserter.is(c.getCount(), 1, "1 combined entry found");
@ -1534,6 +1604,7 @@ public class testBrowserProvider extends ContentProviderTest {
mAsserter.is(c.moveToFirst(), true, "Found combined entry without bookmark id"); mAsserter.is(c.moveToFirst(), true, "Found combined entry without bookmark id");
mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.BOOKMARK_ID))), new Long(0), mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.BOOKMARK_ID))), new Long(0),
"Bookmark id should not be set to removed bookmark id"); "Bookmark id should not be set to removed bookmark id");
c.close();
} }
} }
@ -1569,6 +1640,7 @@ public class testBrowserProvider extends ContentProviderTest {
new String[] { String.valueOf(combinedReadingListItemId) }); new String[] { String.valueOf(combinedReadingListItemId) });
mAsserter.is((deleted == 1), true, "Inserted combined reading list item was deleted"); mAsserter.is((deleted == 1), true, "Inserted combined reading list item was deleted");
c.close();
c = mProvider.query(BrowserContract.Combined.CONTENT_URI, null, "", null, null); c = mProvider.query(BrowserContract.Combined.CONTENT_URI, null, "", null, null);
mAsserter.is(c.getCount(), 1, "1 combined entry found"); mAsserter.is(c.getCount(), 1, "1 combined entry found");
@ -1578,6 +1650,7 @@ public class testBrowserProvider extends ContentProviderTest {
"Bookmark id should not be set to removed bookmark id"); "Bookmark id should not be set to removed bookmark id");
mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.DISPLAY))), new Long(BrowserContract.Combined.DISPLAY_NORMAL), mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.DISPLAY))), new Long(BrowserContract.Combined.DISPLAY_NORMAL),
"Combined entry should have reader display type"); "Combined entry should have reader display type");
c.close();
} }
} }
@ -1608,7 +1681,8 @@ public class testBrowserProvider extends ContentProviderTest {
} }
Cursor c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null); Cursor c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
mAsserter.is(c.getCount(), count, count + " history entries found");
assertCountIsAndClose(c, count, count + " history entries found");
// add thumbnails for each entry // add thumbnails for each entry
allVals = new ContentValues[count]; allVals = new ContentValues[count];
@ -1622,7 +1696,7 @@ public class testBrowserProvider extends ContentProviderTest {
mAsserter.is(inserts, count, "Expected number of inserts matches"); mAsserter.is(inserts, count, "Expected number of inserts matches");
c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null); c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null);
mAsserter.is(c.getCount(), count, count + " thumbnails entries found"); assertCountIsAndClose(c, count, count + " thumbnails entries found");
} }
@Override @Override
@ -1637,57 +1711,61 @@ public class testBrowserProvider extends ContentProviderTest {
Uri url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "NORMAL"); Uri url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "NORMAL");
mProvider.delete(url, null, null); mProvider.delete(url, null, null);
Cursor c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null); Cursor c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
mAsserter.is(c.getCount(), count, count + " history entries found"); assertCountIsAndClose(c, count, count + " history entries found");
// expiring with a normal priority should delete all but 10 thumbnails // expiring with a normal priority should delete all but 10 thumbnails
c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null); c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null);
mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found"); assertCountIsAndClose(c, thumbCount, thumbCount + " thumbnails found");
ensureEmptyDatabase(); ensureEmptyDatabase();
// insert a bunch of new entries
// Insert a bunch of new entries.
createFakeHistory(0, count); createFakeHistory(0, count);
// expiring with a aggressive priority should leave 500 entries // Expiring with a aggressive priority should leave 500 entries.
url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "AGGRESSIVE"); url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "AGGRESSIVE");
mProvider.delete(url, null, null); mProvider.delete(url, null, null);
c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
mAsserter.is(c.getCount(), 500, "500 history entries found");
// expiring with a aggressive priority should delete all but 10 thumbnails c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
assertCountIsAndClose(c, 500, "500 history entries found");
// Expiring with a aggressive priority should delete all but 10 thumbnails.
c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null); c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null);
mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found"); assertCountIsAndClose(c, thumbCount, thumbCount + " thumbnails found");
ensureEmptyDatabase(); ensureEmptyDatabase();
// insert a bunch of entries with an old time created/modified
// Insert a bunch of entries with an old time created/modified.
long time = 1000L * 60L * 60L * 24L * 30L * 3L; long time = 1000L * 60L * 60L * 24L * 30L * 3L;
createFakeHistory(time, count); createFakeHistory(time, count);
// expiring with an normal priority should remove at most 1000 entries // Expiring with an normal priority should remove at most 1000 entries,
// entries leaving at least 2000 // entries leaving at least 2000.
url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "NORMAL"); url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "NORMAL");
mProvider.delete(url, null, null); mProvider.delete(url, null, null);
c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
mAsserter.is(c.getCount(), 2000, "2000 history entries found");
// expiring with a normal priority should delete all but 10 thumbnails c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
assertCountIsAndClose(c, 2000, "2000 history entries found");
// Expiring with a normal priority should delete all but 10 thumbnails.
c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null); c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null);
mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found"); assertCountIsAndClose(c, thumbCount, thumbCount + " thumbnails found");
ensureEmptyDatabase(); ensureEmptyDatabase();
// insert a bunch of entries with an old time created/modified // insert a bunch of entries with an old time created/modified
time = 1000L * 60L * 60L * 24L * 30L * 3L; time = 1000L * 60L * 60L * 24L * 30L * 3L;
createFakeHistory(time, count); createFakeHistory(time, count);
// expiring with an agressive priority should remove old // Expiring with an aggressive priority should remove old
// entries leaving at least 500 // entries, leaving at least 500.
url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "AGGRESSIVE"); url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "AGGRESSIVE");
mProvider.delete(url, null, null); mProvider.delete(url, null, null);
c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null); c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
mAsserter.is(c.getCount(), 500, "500 history entries found"); assertCountIsAndClose(c, 500, "500 history entries found");
// expiring with an aggressive priority should delete all but 10 thumbnails // expiring with an aggressive priority should delete all but 10 thumbnails
c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null); c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null);
mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found"); assertCountIsAndClose(c, thumbCount, thumbCount + " thumbnails found");
} }
} }
@ -1776,4 +1854,16 @@ public class testBrowserProvider extends ContentProviderTest {
ensureOnlyChangeNotifiedStartsWith(BrowserContract.History.CONTENT_URI, "bulkInsert"); ensureOnlyChangeNotifiedStartsWith(BrowserContract.History.CONTENT_URI, "bulkInsert");
} }
} }
/**
* Assert that the provided cursor has the expected number of rows,
* closing the cursor afterwards.
*/
private void assertCountIsAndClose(Cursor c, int expectedCount, String message) {
try {
mAsserter.is(c.getCount(), expectedCount, message);
} finally {
c.close();
}
}
} }

View File

@ -19,6 +19,8 @@ import android.view.SubMenu;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import java.util.ArrayList;
public class GeckoActionProvider extends ActionProvider { public class GeckoActionProvider extends ActionProvider {
private static int MAX_HISTORY_SIZE = 2; private static int MAX_HISTORY_SIZE = 2;
@ -125,6 +127,20 @@ public class GeckoActionProvider extends ActionProvider {
mOnTargetListener = listener; mOnTargetListener = listener;
} }
public ArrayList<ResolveInfo> getSortedActivites() {
ArrayList<ResolveInfo> infos = new ArrayList<ResolveInfo>();
ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
PackageManager packageManager = mContext.getPackageManager();
// Populate the sub-menu with a sub set of the activities.
final int count = dataModel.getActivityCount();
for (int i = 0; i < count; i++) {
infos.add(dataModel.getActivity(i));
}
return infos;
}
/** /**
* Listener for handling default activity / menu item clicks. * Listener for handling default activity / menu item clicks.
*/ */

View File

@ -1045,6 +1045,8 @@ BrowserAddonActor.prototype = {
function DebuggerProgressListener(aBrowserTabActor) { function DebuggerProgressListener(aBrowserTabActor) {
this._tabActor = aBrowserTabActor; this._tabActor = aBrowserTabActor;
this._tabActor._tabbrowser.addProgressListener(this); this._tabActor._tabbrowser.addProgressListener(this);
let EventEmitter = devtools.require("devtools/shared/event-emitter");
EventEmitter.decorate(this);
} }
DebuggerProgressListener.prototype = { DebuggerProgressListener.prototype = {
@ -1072,28 +1074,32 @@ DebuggerProgressListener.prototype = {
this._tabActor._pendingNavigation = aRequest; this._tabActor._pendingNavigation = aRequest;
} }
this._tabActor.threadActor.disableAllBreakpoints(); let packet = {
this._tabActor.conn.send({
from: this._tabActor.actorID, from: this._tabActor.actorID,
type: "tabNavigated", type: "tabNavigated",
url: aRequest.URI.spec, url: aRequest.URI.spec,
nativeConsoleAPI: true, nativeConsoleAPI: true,
state: "start" state: "start"
}); };
this._tabActor.threadActor.disableAllBreakpoints();
this._tabActor.conn.send(packet);
this.emit("will-navigate", packet);
} else if (isStop) { } else if (isStop) {
if (this._tabActor.threadActor.state == "running") { if (this._tabActor.threadActor.state == "running") {
this._tabActor.threadActor.dbg.enabled = true; this._tabActor.threadActor.dbg.enabled = true;
} }
let window = this._tabActor.window; let window = this._tabActor.window;
this._tabActor.conn.send({ let packet = {
from: this._tabActor.actorID, from: this._tabActor.actorID,
type: "tabNavigated", type: "tabNavigated",
url: this._tabActor.url, url: this._tabActor.url,
title: this._tabActor.title, title: this._tabActor.title,
nativeConsoleAPI: this._tabActor.hasNativeConsoleAPI(window), nativeConsoleAPI: this._tabActor.hasNativeConsoleAPI(window),
state: "stop" state: "stop"
}); };
this._tabActor.conn.send(packet);
this.emit("navigate", packet);
} }
}, "DebuggerProgressListener.prototype.onStateChange"), }, "DebuggerProgressListener.prototype.onStateChange"),

View File

@ -59,10 +59,10 @@ function WebConsoleActor(aConnection, aParentActor)
this.dbg = new Debugger(); this.dbg = new Debugger();
this._protoChains = new Map();
this._netEvents = new Map(); this._netEvents = new Map();
this._gripDepth = 0; this._gripDepth = 0;
this._onWillNavigate = this._onWillNavigate.bind(this);
this._onObserverNotification = this._onObserverNotification.bind(this); this._onObserverNotification = this._onObserverNotification.bind(this);
if (this.parentActor.isRootActor) { if (this.parentActor.isRootActor) {
Services.obs.addObserver(this._onObserverNotification, Services.obs.addObserver(this._onObserverNotification,
@ -112,16 +112,6 @@ WebConsoleActor.prototype =
*/ */
_netEvents: null, _netEvents: null,
/**
* A cache of prototype chains for objects that have received a
* prototypeAndProperties request.
*
* @private
* @type Map
* @see dbg-script-actors.js, ThreadActor._protoChains
*/
_protoChains: null,
/** /**
* The debugger server connection instance. * The debugger server connection instance.
* @type object * @type object
@ -209,6 +199,31 @@ WebConsoleActor.prototype =
*/ */
_lastChromeWindow: null, _lastChromeWindow: null,
// The evalWindow is used at the scope for JS evaluation.
_evalWindow: null,
get evalWindow() {
return this._evalWindow || this.window;
},
set evalWindow(aWindow) {
this._evalWindow = aWindow;
if (!this._progressListenerActive && this.parentActor._progressListener) {
this.parentActor._progressListener.once("will-navigate", this._onWillNavigate);
this._progressListenerActive = true;
}
},
/**
* Flag used to track if we are listening for events from the progress
* listener of the tab actor. We use the progress listener to clear
* this.evalWindow on page navigation.
*
* @private
* @type boolean
*/
_progressListenerActive: false,
/** /**
* The ConsoleServiceListener instance. * The ConsoleServiceListener instance.
* @type object * @type object
@ -295,8 +310,9 @@ WebConsoleActor.prototype =
} }
this._actorPool = null; this._actorPool = null;
this._jstermHelpersCache = null;
this._evalWindow = null;
this._netEvents.clear(); this._netEvents.clear();
this._protoChains.clear();
this.dbg.enabled = false; this.dbg.enabled = false;
this.dbg = null; this.dbg = null;
this.conn = null; this.conn = null;
@ -725,7 +741,7 @@ WebConsoleActor.prototype =
} }
// This is the general case (non-paused debugger) // This is the general case (non-paused debugger)
else { else {
dbgObject = this.dbg.makeGlobalObjectReference(this.window); dbgObject = this.dbg.makeGlobalObjectReference(this.evalWindow);
} }
let result = JSPropertyProvider(dbgObject, environment, aRequest.text, let result = JSPropertyProvider(dbgObject, environment, aRequest.text,
@ -822,12 +838,13 @@ WebConsoleActor.prototype =
_getJSTermHelpers: function WCA__getJSTermHelpers(aDebuggerGlobal) _getJSTermHelpers: function WCA__getJSTermHelpers(aDebuggerGlobal)
{ {
let helpers = { let helpers = {
window: this.window, window: this.evalWindow,
chromeWindow: this.chromeWindow.bind(this), chromeWindow: this.chromeWindow.bind(this),
makeDebuggeeValue: aDebuggerGlobal.makeDebuggeeValue.bind(aDebuggerGlobal), makeDebuggeeValue: aDebuggerGlobal.makeDebuggeeValue.bind(aDebuggerGlobal),
createValueGrip: this.createValueGrip.bind(this), createValueGrip: this.createValueGrip.bind(this),
sandbox: Object.create(null), sandbox: Object.create(null),
helperResult: null, helperResult: null,
consoleActor: this,
}; };
JSTermHelpers(helpers); JSTermHelpers(helpers);
@ -924,12 +941,12 @@ WebConsoleActor.prototype =
// as ordinary objects, not as references to be followed, so mixing // as ordinary objects, not as references to be followed, so mixing
// debuggers causes strange behaviors.) // debuggers causes strange behaviors.)
let dbg = frame ? frameActor.threadActor.dbg : this.dbg; let dbg = frame ? frameActor.threadActor.dbg : this.dbg;
let dbgWindow = dbg.makeGlobalObjectReference(this.window); let dbgWindow = dbg.makeGlobalObjectReference(this.evalWindow);
// If we have an object to bind to |_self|, create a Debugger.Object // If we have an object to bind to |_self|, create a Debugger.Object
// referring to that object, belonging to dbg. // referring to that object, belonging to dbg.
let bindSelf = null; let bindSelf = null;
let dbgWindow = dbg.makeGlobalObjectReference(this.window); let dbgWindow = dbg.makeGlobalObjectReference(this.evalWindow);
if (aOptions.bindObjectActor) { if (aOptions.bindObjectActor) {
let objActor = this.getActorByID(aOptions.bindObjectActor); let objActor = this.getActorByID(aOptions.bindObjectActor);
if (objActor) { if (objActor) {
@ -1289,7 +1306,17 @@ WebConsoleActor.prototype =
}); });
break; break;
} }
} },
/**
* The "will-navigate" progress listener. This is used to clear the current
* eval scope.
*/
_onWillNavigate: function WCA__onWillNavigate()
{
this._evalWindow = null;
this._progressListenerActive = false;
},
}; };
WebConsoleActor.prototype.requestTypes = WebConsoleActor.prototype.requestTypes =

View File

@ -21,3 +21,4 @@ support-files =
[test_object_actor_native_getters_lenient_this.html] [test_object_actor_native_getters_lenient_this.html]
[test_page_errors.html] [test_page_errors.html]
[test_throw.html] [test_throw.html]
[test_jsterm_cd_iframe.html]

View File

@ -0,0 +1,154 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf8">
<title>Test for the cd() function</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 cd() function</p>
<script class="testbody" type="text/javascript;version=1.8">
SimpleTest.waitForExplicitFinish();
let gState;
function startTest()
{
removeEventListener("load", startTest);
attachConsole([], onAttach, true);
}
function onAttach(aState, aResponse)
{
top.foobarObject = Object.create(null);
top.foobarObject.bug609872 = "parent";
window.foobarObject = Object.create(null);
window.foobarObject.bug609872 = "child";
gState = aState;
let tests = [doCheckParent, doCdIframe, doCheckIframe, doCdParent,
doCheckParent2];
runTests(tests, testEnd);
}
function doCheckParent()
{
info("check parent window");
gState.client.evaluateJS("window.foobarObject.bug609872",
onFooObjectFromParent);
}
function onFooObjectFromParent(aResponse)
{
checkObject(aResponse, {
from: gState.actor,
input: "window.foobarObject.bug609872",
result: "parent",
});
ok(!aResponse.exception, "no eval exception");
ok(!aResponse.helperResult, "no helper result");
nextTest();
}
function doCdIframe()
{
info("test cd('iframe')");
gState.client.evaluateJS("cd('iframe')", onCdIframe);
}
function onCdIframe(aResponse)
{
checkObject(aResponse, {
from: gState.actor,
input: "cd('iframe')",
result: { type: "undefined" },
helperResult: { type: "cd" },
});
ok(!aResponse.exception, "no eval exception");
nextTest();
}
function doCheckIframe()
{
info("check foobarObject from the iframe");
gState.client.evaluateJS("window.foobarObject.bug609872",
onFooObjectFromIframe);
}
function onFooObjectFromIframe(aResponse)
{
checkObject(aResponse, {
from: gState.actor,
input: "window.foobarObject.bug609872",
result: "child",
});
ok(!aResponse.exception, "no js eval exception");
ok(!aResponse.helperResult, "no helper result");
nextTest();
}
function doCdParent()
{
info("test cd() back to parent");
gState.client.evaluateJS("cd()", onCdParent);
}
function onCdParent(aResponse)
{
checkObject(aResponse, {
from: gState.actor,
input: "cd()",
result: { type: "undefined" },
helperResult: { type: "cd" },
});
ok(!aResponse.exception, "no eval exception");
nextTest();
}
function doCheckParent2()
{
gState.client.evaluateJS("window.foobarObject.bug609872",
onFooObjectFromParent2);
}
function onFooObjectFromParent2(aResponse)
{
checkObject(aResponse, {
from: gState.actor,
input: "window.foobarObject.bug609872",
result: "parent",
});
ok(!aResponse.exception, "no eval exception");
ok(!aResponse.helperResult, "no helper result");
nextTest();
}
function testEnd()
{
closeDebugger(gState, function() {
gState = null;
SimpleTest.finish();
});
}
addEventListener("load", startTest);
</script>
</body>
</html>

View File

@ -1512,6 +1512,40 @@ function JSTermHelpers(aOwner)
aOwner.helperResult = { type: "help" }; aOwner.helperResult = { type: "help" };
}; };
/**
* Change the JS evaluation scope.
*
* @param DOMElement|string|window aWindow
* The window object to use for eval scope. This can be a string that
* is used to perform document.querySelector(), to find the iframe that
* you want to cd() to. A DOMElement can be given as well, the
* .contentWindow property is used. Lastly, you can directly pass
* a window object. If you call cd() with no arguments, the current
* eval scope is cleared back to its default (the top window).
*/
aOwner.sandbox.cd = function JSTH_cd(aWindow)
{
if (!aWindow) {
aOwner.consoleActor.evalWindow = null;
aOwner.helperResult = { type: "cd" };
return;
}
if (typeof aWindow == "string") {
aWindow = aOwner.window.document.querySelector(aWindow);
}
if (aWindow instanceof Ci.nsIDOMElement && aWindow.contentWindow) {
aWindow = aWindow.contentWindow;
}
if (!(aWindow instanceof Ci.nsIDOMWindow)) {
aOwner.helperResult = { type: "error", message: "cdFunctionInvalidArgument" };
return;
}
aOwner.consoleActor.evalWindow = aWindow;
aOwner.helperResult = { type: "cd" };
};
/** /**
* Inspects the passed aObject. This is done by opening the PropertyPanel. * Inspects the passed aObject. This is done by opening the PropertyPanel.
* *