Merge m-c to inbound. a=merge

This commit is contained in:
Ryan VanderMeulen 2014-07-31 16:06:22 -04:00
commit d7c06606cf
81 changed files with 1954 additions and 2306 deletions

View File

@ -802,8 +802,7 @@ var WebappsHelper = {
if (!aManifest)
return;
let manifest = new ManifestHelper(aManifest, json.origin,
json.manifestURL);
let manifest = new ManifestHelper(aManifest, json.origin);
let payload = {
timestamp: json.timestamp,
url: manifest.fullLaunchPath(json.startPoint),

View File

@ -167,8 +167,7 @@ let AlertsHelper = {
this._listeners[uid] = listener;
appsService.getManifestFor(listener.manifestURL).then((manifest) => {
let app = appsService.getAppByManifestURL(listener.manifestURL);
let helper = new ManifestHelper(manifest, app.origin, app.manifestURL);
let helper = new ManifestHelper(manifest, listener.manifestURL);
let getNotificationURLFor = function(messages) {
if (!messages) {
return null;
@ -180,7 +179,8 @@ let AlertsHelper = {
return helper.fullLaunchPath();
} else if (typeof message === "object" &&
kNotificationSystemMessageName in message) {
return helper.resolveURL(message[kNotificationSystemMessageName]);
return helper.resolveFromOrigin(
message[kNotificationSystemMessageName]);
}
}
@ -220,8 +220,7 @@ let AlertsHelper = {
// If we have a manifest URL, get the icon and title from the manifest
// to prevent spoofing.
appsService.getManifestFor(manifestURL).then((manifest) => {
let app = appsService.getAppByManifestURL(manifestURL);
let helper = new ManifestHelper(manifest, app.origin, manifestURL);
let helper = new ManifestHelper(manifest, manifestURL);
send(helper.name, helper.iconURLForSize(kNotificationIconSize));
});
},

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6548f8a89654727eb3a65200ef14298e106eb099"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="04ea7e1a4034a50d4a7a4f5b95a04a2ed8313908"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d61daef8fca7d6f335f659a8967bad423770e634"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="6548f8a89654727eb3a65200ef14298e106eb099"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="04ea7e1a4034a50d4a7a4f5b95a04a2ed8313908"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d61daef8fca7d6f335f659a8967bad423770e634"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="71f5a35e3bc1801847413cff1f14fc3b5cd991ca"/>
@ -131,7 +131,7 @@
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="683623c76338dccd65e698bfb5c4cfee8808d799"/>
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9f28c4faea3b2f01db227b2467b08aeba96d9bec"/>
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="1adbb04024c6181f1089f3a12ee46f26663ea1db"/>
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="b6f025acd8ead1c3531e1ad62e70849161ed9340"/>
<project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
<project name="darwinstreamingserver" path="system/darwinstreamingserver" remote="b2g" revision="cf85968c7f85e0ec36e72c87ceb4837a943b8af6"/>
</manifest>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="999e945b85c578c503ad445c2285940f16aacdae">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="6548f8a89654727eb3a65200ef14298e106eb099"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="04ea7e1a4034a50d4a7a4f5b95a04a2ed8313908"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d61daef8fca7d6f335f659a8967bad423770e634"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6548f8a89654727eb3a65200ef14298e106eb099"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="04ea7e1a4034a50d4a7a4f5b95a04a2ed8313908"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d61daef8fca7d6f335f659a8967bad423770e634"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="6548f8a89654727eb3a65200ef14298e106eb099"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="04ea7e1a4034a50d4a7a4f5b95a04a2ed8313908"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d61daef8fca7d6f335f659a8967bad423770e634"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="71f5a35e3bc1801847413cff1f14fc3b5cd991ca"/>
@ -143,7 +143,7 @@
<project name="platform/hardware/ril" path="hardware/ril" revision="c4e2ac95907a5519a0e09f01a0d8e27fec101af0"/>
<project name="platform/system/bluetooth" path="system/bluetooth" revision="e1eb226fa3ad3874ea7b63c56a9dc7012d7ff3c2"/>
<project name="platform/system/core" path="system/core" revision="bbf7212289fc8311e43f9d11e10788e310d36a08"/>
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="1adbb04024c6181f1089f3a12ee46f26663ea1db"/>
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="b6f025acd8ead1c3531e1ad62e70849161ed9340"/>
<project name="platform/system/qcom" path="system/qcom" revision="1cdab258b15258b7f9657da70e6f06ebd5a2fc25"/>
<project name="platform/vendor/qcom/msm8610" path="device/qcom/msm8610" revision="4ae5df252123591d5b941191790e7abed1bce5a4"/>
<project name="platform/vendor/qcom-opensource/wlan/prima" path="vendor/qcom/opensource/wlan/prima" revision="ce18b47b4a4f93a581d672bbd5cb6d12fe796ca9"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "2d20953fd97b5df7e2450ef558b9eeb9962ca1b4",
"revision": "9f3b6b772b7b8b45634f9911c873f59a8036e9a9",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6548f8a89654727eb3a65200ef14298e106eb099"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="04ea7e1a4034a50d4a7a4f5b95a04a2ed8313908"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d61daef8fca7d6f335f659a8967bad423770e634"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6548f8a89654727eb3a65200ef14298e106eb099"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="04ea7e1a4034a50d4a7a4f5b95a04a2ed8313908"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d61daef8fca7d6f335f659a8967bad423770e634"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="6548f8a89654727eb3a65200ef14298e106eb099"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="04ea7e1a4034a50d4a7a4f5b95a04a2ed8313908"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d61daef8fca7d6f335f659a8967bad423770e634"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="71f5a35e3bc1801847413cff1f14fc3b5cd991ca"/>
@ -127,7 +127,7 @@
<project name="device-mako" path="device/lge/mako" remote="b2g" revision="78d17f0c117f0c66dd55ee8d5c5dde8ccc93ecba"/>
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
<project name="device/lge/mako-kernel" path="device/lge/mako-kernel" revision="d1729e53d71d711c8fde25eab8728ff2b9b4df0e"/>
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="1adbb04024c6181f1089f3a12ee46f26663ea1db"/>
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="b6f025acd8ead1c3531e1ad62e70849161ed9340"/>
<project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
<project name="platform/hardware/broadcom/wlan" path="hardware/broadcom/wlan" revision="0e1929fa3aa38bf9d40e9e953d619fab8164c82e"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6548f8a89654727eb3a65200ef14298e106eb099"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="04ea7e1a4034a50d4a7a4f5b95a04a2ed8313908"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d61daef8fca7d6f335f659a8967bad423770e634"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -722,6 +722,11 @@
@BINPATH@/chrome/pippki@JAREXT@
@BINPATH@/chrome/pippki.manifest
; For process sandboxing
#if defined(XP_WIN)
@BINPATH@/@DLL_PREFIX@sandboxbroker@DLL_SUFFIX@
#endif
; for Solaris SPARC
#ifdef SOLARIS
bin/libfreebl_32fpu_3.chk

View File

@ -1293,6 +1293,7 @@ pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","t
pref("devtools.toolbox.sideEnabled", true);
pref("devtools.toolbox.zoomValue", "1");
pref("devtools.toolbox.splitconsoleEnabled", false);
pref("devtools.toolbox.splitconsoleHeight", 100);
// Toolbox Button preferences
pref("devtools.command-button-pick.enabled", true);

View File

@ -74,6 +74,13 @@ function test() {
url = engine.getSubmission("foo", "application/x-suggestions+json").uri.spec;
is(url, "https://www.google.com/complete/search?client=firefox&q=foo", "Check search suggestion URL for 'foo'");
// Check result parsing and alternate domains.
let alternateBase = base.replace("www.google.com", "www.google.fr");
is(Services.search.parseSubmissionURL(base).terms, "foo",
"Check result parsing");
is(Services.search.parseSubmissionURL(alternateBase).terms, "foo",
"Check alternate domain");
// Check all other engine properties.
const EXPECTED_ENGINE = {
name: "Google",

View File

@ -3,6 +3,8 @@
"use strict";
requestLongerTimeout(2);
/**
* This test ensures that form data collection respects the privacy level as
* set by the user.
@ -230,6 +232,76 @@ add_task(function test_design_mode() {
gBrowser.removeTab(tab);
});
/**
* This test ensures that credit card numbers in form data will not be
* collected, while numbers that don't look like credit card numbers will
* still be collected.
*/
add_task(function test_ccNumbers() {
const validCCNumbers = [
// 15 digits
"930771457288760", "474915027480942",
"924894781317325", "714816113937185",
"790466087343106", "474320195408363",
"219211148122351", "633038472250799",
"354236732906484", "095347810189325",
// 16 digits
"3091269135815020", "5471839082338112",
"0580828863575793", "5015290610002932",
"9465714503078607", "4302068493801686",
"2721398408985465", "6160334316984331",
"8643619970075142", "0218246069710785"
];
const invalidCCNumbers = [
// 15 digits
"526931005800649", "724952425140686",
"379761391174135", "030551436468583",
"947377014076746", "254848023655752",
"226871580283345", "708025346034339",
"917585839076788", "918632588027666",
// 16 digits
"9946177098017064", "4081194386488872",
"3095975979578034", "3662215692222536",
"6723210018630429", "4411962856225025",
"8276996369036686", "4449796938248871",
"3350852696538147", "5011802870046957"
];
const URL = "http://mochi.test:8888/browser/browser/components/" +
"sessionstore/test/browser_formdata_sample.html";
// Creates a tab, loads a page with a form field, sets the value of the
// field, and then removes the tab to trigger data collection.
function* createAndRemoveTab(formValue) {
// Create a new tab.
let tab = gBrowser.addTab(URL);
let browser = tab.linkedBrowser;
yield promiseBrowserLoaded(browser);
// Set form value.
yield setInputValue(browser, {id: "txt", value: formValue});
// Remove the tab.
gBrowser.removeTab(tab);
}
// Test that valid CC numbers are not collected.
for (let number of validCCNumbers) {
yield createAndRemoveTab(number);
let [{state}] = JSON.parse(ss.getClosedTabData(window));
ok(!("formdata" in state), "valid CC numbers are not collected");
}
// Test that non-CC numbers are still collected.
for (let number of invalidCCNumbers) {
yield createAndRemoveTab(number);
let [{state: {formdata}}] = JSON.parse(ss.getClosedTabData(window));
is(formdata.id.txt, number,
"numbers that are not valid CC numbers are still collected");
}
});
function getInputValue(browser, id) {
return sendMessage(browser, "ss-test:getInputValue", {id: id});
}

View File

@ -7,6 +7,7 @@
const MAX_ORDINAL = 99;
const ZOOM_PREF = "devtools.toolbox.zoomValue";
const SPLITCONSOLE_ENABLED_PREF = "devtools.toolbox.splitconsoleEnabled";
const SPLITCONSOLE_HEIGHT_PREF = "devtools.toolbox.splitconsoleHeight";
const MIN_ZOOM = 0.5;
const MAX_ZOOM = 2;
@ -74,6 +75,7 @@ function Toolbox(target, selectedTool, hostType, hostOptions) {
this._highlighterReady = this._highlighterReady.bind(this);
this._highlighterHidden = this._highlighterHidden.bind(this);
this._prefChanged = this._prefChanged.bind(this);
this._saveSplitConsoleHeight = this._saveSplitConsoleHeight.bind(this);
this._target.on("close", this.destroy);
@ -240,8 +242,8 @@ Toolbox.prototype = {
let domReady = () => {
this.isReady = true;
let closeButton = this.doc.getElementById("toolbox-close");
closeButton.addEventListener("command", this.destroy, true);
this.closeButton = this.doc.getElementById("toolbox-close");
this.closeButton.addEventListener("command", this.destroy, true);
gDevTools.on("pref-changed", this._prefChanged);
@ -255,10 +257,17 @@ Toolbox.prototype = {
this._addZoomKeys();
this._loadInitialZoom();
this.webconsolePanel = this.doc.querySelector("#toolbox-panel-webconsole");
this.webconsolePanel.height =
Services.prefs.getIntPref(SPLITCONSOLE_HEIGHT_PREF);
this.webconsolePanel.addEventListener("resize",
this._saveSplitConsoleHeight);
let splitConsolePromise = promise.resolve();
if (Services.prefs.getBoolPref(SPLITCONSOLE_ENABLED_PREF)) {
splitConsolePromise = this.openSplitConsole();
}
let buttonsPromise = this._buildButtons();
this._telemetry.toolOpened("toolbox");
@ -347,6 +356,11 @@ Toolbox.prototype = {
this.doc.addEventListener("keypress", this._splitConsoleOnKeypress, false);
},
_saveSplitConsoleHeight: function() {
Services.prefs.setIntPref(SPLITCONSOLE_HEIGHT_PREF,
this.webconsolePanel.height);
},
/**
* Make sure that the console is showing up properly based on all the
* possible conditions.
@ -361,7 +375,7 @@ Toolbox.prototype = {
*/
_refreshConsoleDisplay: function() {
let deck = this.doc.getElementById("toolbox-deck");
let webconsolePanel = this.doc.getElementById("toolbox-panel-webconsole");
let webconsolePanel = this.webconsolePanel;
let splitter = this.doc.getElementById("toolbox-console-splitter");
let openedConsolePanel = this.currentToolId === "webconsole";
@ -524,11 +538,10 @@ Toolbox.prototype = {
return;
}
let closeButton = this.doc.getElementById("toolbox-close");
if (this.hostType == Toolbox.HostType.WINDOW) {
closeButton.setAttribute("hidden", "true");
this.closeButton.setAttribute("hidden", "true");
} else {
closeButton.removeAttribute("hidden");
this.closeButton.removeAttribute("hidden");
}
let sideEnabled = Services.prefs.getBoolPref(this._prefs.SIDE_ENABLED);
@ -1321,6 +1334,11 @@ Toolbox.prototype = {
gDevTools.off("pref-changed", this._prefChanged);
this._saveSplitConsoleHeight();
this.webconsolePanel.removeEventListener("resize",
this._saveSplitConsoleHeight);
this.closeButton.removeEventListener("command", this.destroy, true);
let outstanding = [];
for (let [id, panel] of this._toolPanels) {
try {

View File

@ -80,7 +80,11 @@
</hbox>
</toolbar>
<vbox flex="1">
<deck id="toolbox-deck" flex="1" minheight="75" />
<!-- Set large flex to allow the toolbox-panel-webconsole to have a
height set to a small value without flexing to fill up extra
space. There must be a flex on both to ensure that the console
panel itself is sized properly -->
<deck id="toolbox-deck" flex="1000" minheight="75" />
<splitter id="toolbox-console-splitter" class="devtools-horizontal-splitter" hidden="true" />
<box minheight="75" flex="1" id="toolbox-panel-webconsole" collapsed="true" />
</vbox>

View File

@ -580,6 +580,30 @@ WebConsole.prototype = {
return null;
},
/**
* Retrieves the current selection from the Inspector, if such a selection
* exists. This is used to pass the ID of the selected actor to the Web
* Console server for the $0 helper.
*
* @return object|null
* A Selection referring to the currently selected node in the
* Inspector.
* If the inspector was never opened, or no node was ever selected,
* then |null| is returned.
*/
getInspectorSelection: function WC_getInspectorSelection()
{
let toolbox = gDevTools.getToolbox(this.target);
if (!toolbox) {
return null;
}
let panel = toolbox.getPanel("inspector");
if (!panel || !panel.selection) {
return null;
}
return panel.selection;
},
/**
* Destroy the object. Call this method to avoid memory leaks when the Web
* Console is closed.

View File

@ -52,7 +52,6 @@ WebConsolePanel.prototype = {
{
let parentDoc = this._toolbox.doc;
let iframe = parentDoc.getElementById("toolbox-panel-iframe-webconsole");
iframe.className = "web-console-frame";
// Make sure the iframe content window is ready.
let deferredIframe = promise.defer();

View File

@ -67,7 +67,7 @@ function test()
{
let win = toolbox.doc.defaultView;
let deck = toolbox.doc.querySelector("#toolbox-deck");
let webconsolePanel = toolbox.doc.querySelector("#toolbox-panel-webconsole");
let webconsolePanel = toolbox.webconsolePanel;
let splitter = toolbox.doc.querySelector("#toolbox-console-splitter");
let containerHeight = parseFloat(win.getComputedStyle(deck.parentNode).getPropertyValue("height"));
@ -181,7 +181,7 @@ function test()
ok (currentUIState.splitterVisibility, "Splitter is visible when console is split");
ok (currentUIState.deckHeight > 0, "Deck has a height > 0 when console is split");
ok (currentUIState.webconsoleHeight > 0, "Web console has a height > 0 when console is split");
is (currentUIState.deckHeight + currentUIState.webconsoleHeight,
is (Math.round(currentUIState.deckHeight + currentUIState.webconsoleHeight),
currentUIState.containerHeight,
"Everything adds up to container height");
ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");

View File

@ -20,7 +20,10 @@ function test() {
ok(!toolbox.splitConsole, "Split console is hidden by default.");
yield toggleSplitConsoleWithEscape();
ok(toolbox.splitConsole, "Split console is now visible.");
ok(getPrefValue(), "Pref is true");
ok(getVisiblePrefValue(), "Visibility pref is true");
is(getHeightPrefValue(), toolbox.webconsolePanel.height, "Panel height matches the pref");
toolbox.webconsolePanel.height = 200;
yield toolbox.destroy();
@ -30,27 +33,44 @@ function test() {
toolbox = yield gDevTools.showToolbox(target, "inspector");
ok(toolbox.splitConsole, "Split console is visible by default.");
is(getHeightPrefValue(), 200, "Height is set based on panel height after closing");
toolbox.webconsolePanel.height = 1;
ok (toolbox.webconsolePanel.clientHeight > 1,
"The actual height of the console is bound with a min height");
toolbox.webconsolePanel.height = 10000;
ok (toolbox.webconsolePanel.clientHeight < 10000,
"The actual height of the console is bound with a max height");
yield toggleSplitConsoleWithEscape();
ok(!toolbox.splitConsole, "Split console is now hidden.");
ok(!getPrefValue(), "Pref is false");
ok(!getVisiblePrefValue(), "Visibility pref is false");
yield toolbox.destroy();
is(getHeightPrefValue(), 10000, "Height is set based on panel height after closing");
info("Opening a tab while there is a false user setting on split console pref");
let {tab} = yield loadTab(TEST_URI);
let target = TargetFactory.forTab(tab);
toolbox = yield gDevTools.showToolbox(target, "inspector");
ok(!toolbox.splitConsole, "Split console is hidden by default.");
ok(!getPrefValue(), "Pref is false");
ok(!getVisiblePrefValue(), "Visibility pref is false");
yield toolbox.destroy();
}
function getPrefValue() {
function getVisiblePrefValue() {
return Services.prefs.getBoolPref("devtools.toolbox.splitconsoleEnabled");
}
function getHeightPrefValue() {
return Services.prefs.getIntPref("devtools.toolbox.splitconsoleHeight");
}
function toggleSplitConsoleWithEscape() {
let onceSplitConsole = toolbox.once("split-console");
let contentWindow = toolbox.frame.contentWindow;
@ -62,6 +82,7 @@ function test() {
function finish() {
toolbox = TEST_URI = null;
Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
Services.prefs.clearUserPref("devtools.toolbox.splitconsoleHeight");
finishTest();
}
}

View File

@ -3264,6 +3264,12 @@ JSTerm.prototype = {
return;
}
let selectedNodeActor = null;
let inspectorSelection = this.hud.owner.getInspectorSelection();
if (inspectorSelection) {
selectedNodeActor = inspectorSelection.nodeFront.actorID;
}
let message = new Messages.Simple(aExecuteString, {
category: "input",
severity: "log",
@ -3271,7 +3277,11 @@ JSTerm.prototype = {
this.hud.output.addMessage(message);
let onResult = this._executeResultCallback.bind(this, message, aCallback);
let options = { frame: this.SELECTED_FRAME };
let options = {
frame: this.SELECTED_FRAME,
selectedNodeActor: selectedNodeActor,
};
this.requestEvaluation(aExecuteString, options).then(onResult, onResult);
// Append a new value in the history of executed code, or overwrite the most
@ -3301,6 +3311,9 @@ JSTerm.prototype = {
* user-selected stackframe.
* If you do not provide a |frame| the string will be evaluated in the
* global content window.
* - selectedNodeActor: tells the NodeActor ID of the current selection in
* the Inspector, if such a selection exists. This is used by helper
* functions that can evaluate on the current selection.
* @return object
* A promise object that is resolved when the server response is
* received.
@ -3326,6 +3339,7 @@ JSTerm.prototype = {
let evalOptions = {
bindObjectActor: aOptions.bindObjectActor,
frameActor: frameActor,
selectedNodeActor: aOptions.selectedNodeActor,
};
this.webConsoleClient.evaluateJS(aString, onResult, evalOptions);

View File

@ -66,9 +66,7 @@ this.WebappManager = {
}
} else if (aMessage.name == "Webapps:Install:Return:OK" &&
!data.isPackage) {
let manifest = new ManifestHelper(data.app.manifest,
data.app.origin,
data.app.manifestURL);
let manifest = new ManifestHelper(data.app.manifest, data.app.origin);
if (!manifest.appcache_path) {
this.installations[manifestURL].resolve();
}
@ -174,8 +172,7 @@ this.WebappManager = {
};
let requestingURI = chromeWin.makeURI(aData.from);
let app = aData.app;
let manifest = new ManifestHelper(jsonManifest, app.origin, app.manifestURL);
let manifest = new ManifestHelper(jsonManifest, aData.app.origin);
let host;
try {

View File

@ -294,10 +294,7 @@ let Activities = {
let results = [];
aManifests.forEach((aManifest, i) => {
let manifestURL = aResults.options[i].manifest;
// Not passing the origin is fine here since we only need
// helper.name which doesn't rely on url resolution.
let helper =
new ManifestHelper(aManifest.manifest, manifestURL, manifestURL);
let helper = new ManifestHelper(aManifest.manifest, manifestURL);
results.push({
manifestURL: manifestURL,
iconURL: aResults.options[i].icon,

View File

@ -24,8 +24,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
// Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js
this.EXPORTED_SYMBOLS =
["AppsUtils", "ManifestHelper", "isAbsoluteURI", "mozIApplication"];
this.EXPORTED_SYMBOLS = ["AppsUtils", "ManifestHelper", "isAbsoluteURI", "mozIApplication"];
function debug(s) {
//dump("-*- AppsUtils.jsm: " + s + "\n");
@ -374,8 +373,7 @@ this.AppsUtils = {
// Nor through localized names
if ('locales' in aNewManifest) {
let defaultName =
new ManifestHelper(aOldManifest, aApp.origin, aApp.manifestURL).name;
let defaultName = new ManifestHelper(aOldManifest, aApp.origin).name;
for (let locale in aNewManifest.locales) {
let entry = aNewManifest.locales[locale];
if (!entry.name) {
@ -595,25 +593,11 @@ this.AppsUtils = {
/**
* Helper object to access manifest information with locale support
*/
this.ManifestHelper = function(aManifest, aOrigin, aManifestURL) {
// If the app is packaged, we resolve uris against the origin.
// If it's not, against the manifest url.
if (!aOrigin || !aManifestURL) {
throw Error("ManifestHelper needs both origin and manifestURL");
}
this._baseURI = Services.io.newURI(
aOrigin.startsWith("app://") ? aOrigin : aManifestURL, null, null);
// We keep the manifest url in all cases since we need it to
// resolve the package path for packaged apps.
this._manifestURL = Services.io.newURI(aManifestURL, null, null);
this.ManifestHelper = function(aManifest, aOrigin) {
this._origin = Services.io.newURI(aOrigin, null, null);
this._manifest = aManifest;
let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"]
.getService(Ci.nsIXULChromeRegistry)
.QueryInterface(Ci.nsIToolkitChromeRegistry);
let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry)
.QueryInterface(Ci.nsIToolkitChromeRegistry);
let locale = chrome.getSelectedLocale("global").toLowerCase();
this._localeRoot = this._manifest;
@ -702,7 +686,7 @@ ManifestHelper.prototype = {
iconSizes.sort((a, b) => a - b);
let biggestIconSize = iconSizes.pop();
let biggestIcon = icons[biggestIconSize];
let biggestIconURL = this._baseURI.resolve(biggestIcon);
let biggestIconURL = this._origin.resolve(biggestIcon);
return biggestIconURL;
},
@ -716,7 +700,7 @@ ManifestHelper.prototype = {
for (let size in icons) {
let iSize = parseInt(size);
if (Math.abs(iSize - aSize) < dist) {
icon = this._baseURI.resolve(icons[size]);
icon = this._origin.resolve(icons[size]);
dist = Math.abs(iSize - aSize);
}
}
@ -727,7 +711,7 @@ ManifestHelper.prototype = {
// If no start point is specified, we use the root launch path.
// In all error cases, we just return null.
if ((aStartPoint || "") === "") {
return this._baseURI.resolve(this._localeProp("launch_path") || "");
return this._origin.resolve(this._localeProp("launch_path") || "");
}
// Search for the l10n entry_points property.
@ -737,28 +721,28 @@ ManifestHelper.prototype = {
}
if (entryPoints[aStartPoint]) {
return this._baseURI.resolve(entryPoints[aStartPoint].launch_path || "");
return this._origin.resolve(entryPoints[aStartPoint].launch_path || "");
}
return null;
},
resolveURL: function(aURI) {
resolveFromOrigin: function(aURI) {
// This should be enforced higher up, but check it here just in case.
if (isAbsoluteURI(aURI)) {
throw new Error("Webapps.jsm: non-relative URI passed to resolve");
throw new Error("Webapps.jsm: non-relative URI passed to resolveFromOrigin");
}
return this._baseURI.resolve(aURI);
return this._origin.resolve(aURI);
},
fullAppcachePath: function() {
let appcachePath = this._localeProp("appcache_path");
return this._baseURI.resolve(appcachePath ? appcachePath : "");
return this._origin.resolve(appcachePath ? appcachePath : "");
},
fullPackagePath: function() {
let packagePath = this._localeProp("package_path");
return this._manifestURL.resolve(packagePath ? packagePath : "");
return this._origin.resolve(packagePath ? packagePath : "");
},
get role() {

View File

@ -59,8 +59,7 @@ this.PermissionsInstaller = {
installPermissions: function installPermissions(aApp, aIsReinstall, aOnError,
aIsSystemUpdate) {
try {
let newManifest =
new ManifestHelper(aApp.manifest, aApp.origin, aApp.manifestURL);
let newManifest = new ManifestHelper(aApp.manifest, aApp.origin);
if (!newManifest.permissions && !aIsReinstall) {
return;
}

View File

@ -389,8 +389,7 @@ this.DOMApplicationRegistry = {
updateOfflineCacheForApp: function(aId) {
let app = this.webapps[aId];
this._readManifests([{ id: aId }]).then((aResult) => {
let manifest =
new ManifestHelper(aResult[0].manifest, app.origin, app.manifestURL);
let manifest = new ManifestHelper(aResult[0].manifest, app.origin);
OfflineCacheInstaller.installCache({
cachePath: app.cachePath,
appId: aId,
@ -676,7 +675,7 @@ this.DOMApplicationRegistry = {
return;
}
let manifest = new ManifestHelper(aManifest, aApp.origin, aApp.manifestURL);
let manifest = new ManifestHelper(aManifest, aApp.origin);
let launchPathURI = Services.io.newURI(manifest.fullLaunchPath(aEntryPoint), null, null);
let manifestURI = Services.io.newURI(aApp.manifestURL, null, null);
root.messages.forEach(function registerPages(aMessage) {
@ -690,7 +689,7 @@ this.DOMApplicationRegistry = {
let fullHandlerPath;
try {
if (handlerPath && handlerPath.trim()) {
fullHandlerPath = manifest.resolveURL(handlerPath);
fullHandlerPath = manifest.resolveFromOrigin(handlerPath);
} else {
throw new Error("Empty or blank handler path.");
}
@ -706,7 +705,7 @@ this.DOMApplicationRegistry = {
if (SystemMessagePermissionsChecker
.isSystemMessagePermittedToRegister(messageName,
aApp.manifestURL,
aApp.origin,
aManifest)) {
msgmgr.registerPage(messageName, handlerPageURI, manifestURI);
}
@ -734,7 +733,7 @@ this.DOMApplicationRegistry = {
return;
}
let manifest = new ManifestHelper(aManifest, aApp.origin, aApp.manifestURL);
let manifest = new ManifestHelper(aManifest, aApp.origin);
let launchPathURI = Services.io.newURI(manifest.fullLaunchPath(aEntryPoint),
null, null);
let manifestURI = Services.io.newURI(aApp.manifestURL, null, null);
@ -748,7 +747,7 @@ this.DOMApplicationRegistry = {
let handlerPath = connection.handler_path;
if (handlerPath) {
try {
fullHandlerPath = manifest.resolveURL(handlerPath);
fullHandlerPath = manifest.resolveFromOrigin(handlerPath);
} catch(e) {
debug("Connection's handler path is invalid. Skipping: keyword: " +
keyword + " handler_path: " + handlerPath);
@ -761,7 +760,7 @@ this.DOMApplicationRegistry = {
if (SystemMessagePermissionsChecker
.isSystemMessagePermittedToRegister("connection",
aApp.manifestURL,
aApp.origin,
aManifest)) {
msgmgr.registerPage("connection", handlerPageURI, manifestURI);
}
@ -813,7 +812,7 @@ this.DOMApplicationRegistry = {
return activitiesToRegister;
}
let manifest = new ManifestHelper(aManifest, aApp.origin, aApp.manifestURL);
let manifest = new ManifestHelper(aManifest, aApp.origin);
for (let activity in root.activities) {
let description = root.activities[activity];
let href = description.href;
@ -822,7 +821,7 @@ this.DOMApplicationRegistry = {
}
try {
href = manifest.resolveURL(href);
href = manifest.resolveFromOrigin(href);
} catch (e) {
debug("Activity href (" + href + ") is invalid, skipping. " +
"Error is: " + e);
@ -852,7 +851,7 @@ this.DOMApplicationRegistry = {
if (SystemMessagePermissionsChecker
.isSystemMessagePermittedToRegister("activity",
aApp.manifestURL,
aApp.origin,
aManifest)) {
msgmgr.registerPage("activity", launchPathURI, manifestURI);
}
@ -962,8 +961,7 @@ this.DOMApplicationRegistry = {
return;
}
let localeManifest =
new ManifestHelper(manifest, app.origin, app.manifestURL);
let localeManifest = new ManifestHelper(manifest, app.origin);
app.name = manifest.name;
app.csp = manifest.csp || "";
@ -1465,8 +1463,7 @@ this.DOMApplicationRegistry = {
let results = yield this._readManifests([{ id: id }]);
let jsonManifest = results[0].manifest;
let manifest =
new ManifestHelper(jsonManifest, app.origin, app.manifestURL);
let manifest = new ManifestHelper(jsonManifest, app.origin);
if (manifest.appcache_path) {
debug("appcache found");
@ -1500,7 +1497,7 @@ this.DOMApplicationRegistry = {
throw new Error("MISSING_UPDATE_MANIFEST");
}
let manifest = new ManifestHelper(json, app.origin, app.manifestURL);
let manifest = new ManifestHelper(json, app.manifestURL);
let newApp = {
manifestURL: aManifestURL,
origin: app.origin,
@ -1811,8 +1808,7 @@ this.DOMApplicationRegistry = {
}
}
};
let helper =
new ManifestHelper(manifest, aData.origin, aData.manifestURL);
let helper = new ManifestHelper(manifest, aData.manifestURL);
debug("onlyCheckAppCache - launch updateSvc.checkForUpdate for " +
helper.fullAppcachePath());
updateSvc.checkForUpdate(Services.io.newURI(helper.fullAppcachePath(), null, null),
@ -1994,8 +1990,7 @@ this.DOMApplicationRegistry = {
let manFile = OS.Path.join(dir, "staged-update.webapp");
yield this._writeFile(manFile, JSON.stringify(aNewManifest));
let manifest =
new ManifestHelper(aNewManifest, aApp.origin, aApp.manifestURL);
let manifest = new ManifestHelper(aNewManifest, aApp.manifestURL);
// A package is available: set downloadAvailable to fire the matching
// event.
aApp.downloadAvailable = true;
@ -2041,8 +2036,7 @@ this.DOMApplicationRegistry = {
let manFile = OS.Path.join(dir, "manifest.webapp");
yield this._writeFile(manFile, JSON.stringify(aNewManifest));
manifest =
new ManifestHelper(aNewManifest, aApp.origin, aApp.manifestURL);
manifest = new ManifestHelper(aNewManifest, aApp.origin);
if (supportUseCurrentProfile()) {
// Update the permissions for this app.
@ -2061,8 +2055,7 @@ this.DOMApplicationRegistry = {
aApp.role = manifest.role || "";
aApp.updateTime = Date.now();
} else {
manifest =
new ManifestHelper(aOldManifest, aApp.origin, aApp.manifestURL);
manifest = new ManifestHelper(aOldManifest, aApp.origin);
}
// Update the registry.
@ -2571,8 +2564,7 @@ this.DOMApplicationRegistry = {
yield this._writeManifestFile(id, aData.isPackage, jsonManifest);
debug("app.origin: " + app.origin);
let manifest =
new ManifestHelper(jsonManifest, app.origin, app.manifestURL);
let manifest = new ManifestHelper(jsonManifest, app.origin);
let appObject = this._cloneApp(aData, app, manifest, jsonManifest, id, localId);
@ -2626,7 +2618,7 @@ this.DOMApplicationRegistry = {
// origin for install apps is meaningless here, since it's app:// and this
// can't be used to resolve package paths.
manifest = new ManifestHelper(jsonManifest, app.origin, app.manifestURL);
manifest = new ManifestHelper(jsonManifest, app.manifestURL);
this.queuedPackageDownload[app.manifestURL] = {
manifest: manifest,

View File

@ -170,20 +170,18 @@ this.SystemMessagePermissionsChecker = {
* app at start-up based on the permissions claimed in the app's manifest.
* @param string aSysMsgName
* The system messsage name.
* @param string aManifestURL
* The app's manifest URL.
* @param string aOrigin
* The app's origin.
* @param object aManifest
* The app's manifest.
* @returns bool
* Is permitted or not.
**/
isSystemMessagePermittedToRegister:
function isSystemMessagePermittedToRegister(aSysMsgName,
aManifestURL,
aManifest) {
function isSystemMessagePermittedToRegister(aSysMsgName, aOrigin, aManifest) {
debug("isSystemMessagePermittedToRegister(): " +
"aSysMsgName: " + aSysMsgName + ", " +
"aManifestURL: " + aManifestURL + ", " +
"aOrigin: " + aOrigin + ", " +
"aManifest: " + JSON.stringify(aManifest));
let permNames = this.getSystemMessagePermissions(aSysMsgName);
@ -209,22 +207,20 @@ this.SystemMessagePermissionsChecker = {
break;
}
// It's ok here to not pass the origin to ManifestHelper since we only
// need the permission property and that doesn't depend on uri resolution.
let newManifest = new ManifestHelper(aManifest, aManifestURL, aManifestURL);
let newManifest = new ManifestHelper(aManifest, aOrigin);
for (let permName in permNames) {
// The app doesn't claim valid permissions for this sytem message.
if (!newManifest.permissions || !newManifest.permissions[permName]) {
debug("'" + aSysMsgName + "' isn't permitted by '" + permName + "'. " +
"Please add the permission for app: '" + aManifestURL + "'.");
"Please add the permission for app: '" + aOrigin + "'.");
return false;
}
let permValue = PermissionsTable[permName][appStatus];
if (permValue != Ci.nsIPermissionManager.PROMPT_ACTION &&
permValue != Ci.nsIPermissionManager.ALLOW_ACTION) {
debug("'" + aSysMsgName + "' isn't permitted by '" + permName + "'. " +
"Please add the permission for app: '" + aManifestURL + "'.");
"Please add the permission for app: '" + aOrigin + "'.");
return false;
}

View File

@ -334,6 +334,8 @@ private:
SwitchHandlerArray mHandler;
bool mHeadphonesFromInputDev;
// This function might also get called on the main thread
// (from IsHeadphoneEventFromInputDev)
void Init()
{
RefPtr<SwitchHandlerHeadphone> switchHeadPhone =
@ -458,12 +460,12 @@ GetCurrentSwitchState(SwitchDevice aDevice)
static void
NotifySwitchStateIOThread(SwitchDevice aDevice, SwitchState aState)
{
InitializeResourceIfNeed();
sSwitchObserver->Notify(aDevice, aState);
}
void NotifySwitchStateFromInputDevice(SwitchDevice aDevice, SwitchState aState)
{
InitializeResourceIfNeed();
XRE_GetIOMessageLoop()->PostTask(
FROM_HERE,
NewRunnableFunction(NotifySwitchStateIOThread, aDevice, aState));
@ -471,8 +473,10 @@ void NotifySwitchStateFromInputDevice(SwitchDevice aDevice, SwitchState aState)
bool IsHeadphoneEventFromInputDev()
{
InitializeResourceIfNeed();
return sSwitchObserver->GetHeadphonesFromInputDev();
// Instead of calling InitializeResourceIfNeed, create new SwitchEventObserver
// to prevent calling RegisterUeventListener in main thread.
RefPtr<SwitchEventObserver> switchObserver = new SwitchEventObserver();
return switchObserver->GetHeadphonesFromInputDev();
}
} // hal_impl

View File

@ -0,0 +1,155 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include "SocketBase.h"
#include <string.h>
#include "nsThreadUtils.h"
namespace mozilla {
namespace ipc {
//
// UnixSocketRawData
//
UnixSocketRawData::UnixSocketRawData(size_t aSize)
: mSize(aSize)
, mCurrentWriteOffset(0)
{
mData = new uint8_t[mSize];
}
UnixSocketRawData::UnixSocketRawData(const void* aData, size_t aSize)
: mSize(aSize)
, mCurrentWriteOffset(0)
{
MOZ_ASSERT(aData || !mSize);
mData = new uint8_t[mSize];
memcpy(mData, aData, mSize);
}
//
// SocketConsumerBase
//
SocketConsumerBase::~SocketConsumerBase()
{
MOZ_ASSERT(mConnectionStatus == SOCKET_DISCONNECTED);
}
SocketConnectionStatus
SocketConsumerBase::GetConnectionStatus() const
{
MOZ_ASSERT(NS_IsMainThread());
return mConnectionStatus;
}
int
SocketConsumerBase::GetSuggestedConnectDelayMs() const
{
MOZ_ASSERT(NS_IsMainThread());
return mConnectDelayMs;
}
void
SocketConsumerBase::NotifySuccess()
{
MOZ_ASSERT(NS_IsMainThread());
mConnectionStatus = SOCKET_CONNECTED;
mConnectTimestamp = PR_IntervalNow();
OnConnectSuccess();
}
void
SocketConsumerBase::NotifyError()
{
MOZ_ASSERT(NS_IsMainThread());
mConnectionStatus = SOCKET_DISCONNECTED;
mConnectDelayMs = CalculateConnectDelayMs();
OnConnectError();
}
void
SocketConsumerBase::NotifyDisconnect()
{
MOZ_ASSERT(NS_IsMainThread());
mConnectionStatus = SOCKET_DISCONNECTED;
mConnectDelayMs = CalculateConnectDelayMs();
OnDisconnect();
}
uint32_t
SocketConsumerBase::CalculateConnectDelayMs() const
{
MOZ_ASSERT(NS_IsMainThread());
uint32_t connectDelayMs = mConnectDelayMs;
if ((PR_IntervalNow()-mConnectTimestamp) > connectDelayMs) {
// reset delay if connection has been opened for a while, or...
connectDelayMs = 0;
} else if (!connectDelayMs) {
// ...start with a delay of ~1 sec, or...
connectDelayMs = 1<<10;
} else if (connectDelayMs < (1<<16)) {
// ...otherwise increase delay by a factor of 2
connectDelayMs <<= 1;
}
return connectDelayMs;
}
SocketConsumerBase::SocketConsumerBase()
: mConnectionStatus(SOCKET_DISCONNECTED)
, mConnectTimestamp(0)
, mConnectDelayMs(0)
{ }
void
SocketConsumerBase::SetConnectionStatus(
SocketConnectionStatus aConnectionStatus)
{
mConnectionStatus = aConnectionStatus;
}
//
// SocketIOBase
//
SocketIOBase::~SocketIOBase()
{ }
void
SocketIOBase::EnqueueData(UnixSocketRawData* aData)
{
if (!aData->mSize) {
delete aData; // delete empty data immediately
return;
}
mOutgoingQ.AppendElement(aData);
}
bool
SocketIOBase::HasPendingData() const
{
return !mOutgoingQ.IsEmpty();
}
SocketIOBase::SocketIOBase(size_t aMaxReadSize)
: mMaxReadSize(aMaxReadSize)
{
MOZ_ASSERT(mMaxReadSize);
}
}
}

524
ipc/unixsocket/SocketBase.h Normal file
View File

@ -0,0 +1,524 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef mozilla_ipc_SocketBase_h
#define mozilla_ipc_SocketBase_h
#include <errno.h>
#include <unistd.h>
#include "base/message_loop.h"
#include "nsAutoPtr.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"
#ifdef MOZ_TASK_TRACER
#include "GeckoTaskTracer.h"
using namespace mozilla::tasktracer;
#endif
namespace mozilla {
namespace ipc {
//
// UnixSocketRawData
//
class UnixSocketRawData
{
public:
// Number of octets in mData.
size_t mSize;
size_t mCurrentWriteOffset;
nsAutoArrayPtr<uint8_t> mData;
/**
* Constructor for situations where only size is known beforehand
* (for example, when being assigned strings)
*/
UnixSocketRawData(size_t aSize);
/**
* Constructor for situations where size and data is known
* beforehand (for example, when being assigned strings)
*/
UnixSocketRawData(const void* aData, size_t aSize);
};
enum SocketConnectionStatus {
SOCKET_DISCONNECTED = 0,
SOCKET_LISTENING = 1,
SOCKET_CONNECTING = 2,
SOCKET_CONNECTED = 3
};
//
// SocketConsumerBase
//
class SocketConsumerBase
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SocketConsumerBase)
virtual ~SocketConsumerBase();
SocketConnectionStatus GetConnectionStatus() const;
int GetSuggestedConnectDelayMs() const;
/**
* Queues the internal representation of socket for deletion. Can be called
* from main thread.
*/
virtual void CloseSocket() = 0;
/**
* Function to be called whenever data is received. This is only called on the
* main thread.
*
* @param aMessage Data received from the socket.
*/
virtual void ReceiveSocketData(nsAutoPtr<UnixSocketRawData>& aMessage) = 0;
/**
* Queue data to be sent to the socket on the IO thread. Can only be called on
* originating thread.
*
* @param aMessage Data to be sent to socket
*
* @return true if data is queued, false otherwise (i.e. not connected)
*/
virtual bool SendSocketData(UnixSocketRawData* aMessage) = 0;
/**
* Callback for socket connect/accept success. Called after connect/accept has
* finished. Will be run on main thread, before any reads take place.
*/
virtual void OnConnectSuccess() = 0;
/**
* Callback for socket connect/accept error. Will be run on main thread.
*/
virtual void OnConnectError() = 0;
/**
* Callback for socket disconnect. Will be run on main thread.
*/
virtual void OnDisconnect() = 0;
/**
* Called by implementation to notify consumer of success.
*/
void NotifySuccess();
/**
* Called by implementation to notify consumer of error.
*/
void NotifyError();
/**
* Called by implementation to notify consumer of disconnect.
*/
void NotifyDisconnect();
protected:
SocketConsumerBase();
void SetConnectionStatus(SocketConnectionStatus aConnectionStatus);
private:
uint32_t CalculateConnectDelayMs() const;
SocketConnectionStatus mConnectionStatus;
PRIntervalTime mConnectTimestamp;
uint32_t mConnectDelayMs;
};
//
// Socket I/O runnables
//
/* |SocketIORunnable| is a runnable for sending a message from
* the I/O thread to the main thread.
*/
template <typename T>
class SocketIORunnable : public nsRunnable
{
public:
virtual ~SocketIORunnable()
{ }
T* GetIO() const
{
return mIO;
}
protected:
SocketIORunnable(T* aIO)
: mIO(aIO)
{
MOZ_ASSERT(aIO);
}
private:
T* mIO;
};
/* |SocketIOEventRunnable| reports the connection state on the
* I/O thrad back to the main thread.
*/
template <typename T>
class SocketIOEventRunnable MOZ_FINAL : public SocketIORunnable<T>
{
public:
enum SocketEvent {
CONNECT_SUCCESS,
CONNECT_ERROR,
DISCONNECT
};
SocketIOEventRunnable(T* aIO, SocketEvent e)
: SocketIORunnable<T>(aIO)
, mEvent(e)
{ }
NS_IMETHOD Run() MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
T* io = SocketIORunnable<T>::GetIO();
if (io->IsShutdownOnMainThread()) {
NS_WARNING("I/O consumer has already been closed!");
// Since we've already explicitly closed and the close happened before
// this, this isn't really an error. Since we've warned, return OK.
return NS_OK;
}
SocketConsumerBase* consumer = io->GetConsumer();
MOZ_ASSERT(consumer);
if (mEvent == CONNECT_SUCCESS) {
consumer->NotifySuccess();
} else if (mEvent == CONNECT_ERROR) {
consumer->NotifyError();
} else if (mEvent == DISCONNECT) {
consumer->NotifyDisconnect();
}
return NS_OK;
}
private:
SocketEvent mEvent;
};
/* |SocketReceiveRunnable| transfers data received on the I/O thread
* to the consumer on the main thread.
*/
template <typename T>
class SocketIOReceiveRunnable MOZ_FINAL : public SocketIORunnable<T>
{
public:
SocketIOReceiveRunnable(T* aIO, UnixSocketRawData* aData)
: SocketIORunnable<T>(aIO)
, mData(aData)
{ }
NS_IMETHOD Run() MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
T* io = SocketIORunnable<T>::GetIO();
if (io->IsShutdownOnMainThread()) {
NS_WARNING("mConsumer is null, aborting receive!");
// Since we've already explicitly closed and the close happened before
// this, this isn't really an error. Since we've warned, return OK.
return NS_OK;
}
SocketConsumerBase* consumer = io->GetConsumer();
MOZ_ASSERT(consumer);
consumer->ReceiveSocketData(mData);
return NS_OK;
}
private:
nsAutoPtr<UnixSocketRawData> mData;
};
template <typename T>
class SocketIORequestClosingRunnable MOZ_FINAL : public SocketIORunnable<T>
{
public:
SocketIORequestClosingRunnable(T* aImpl)
: SocketIORunnable<T>(aImpl)
{ }
NS_IMETHOD Run() MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
T* io = SocketIORunnable<T>::GetIO();
if (io->IsShutdownOnMainThread()) {
NS_WARNING("CloseSocket has already been called!");
// Since we've already explicitly closed and the close happened before
// this, this isn't really an error. Since we've warned, return OK.
return NS_OK;
}
SocketConsumerBase* consumer = io->GetConsumer();
MOZ_ASSERT(consumer);
consumer->CloseSocket();
return NS_OK;
}
};
/* |SocketIODeleteInstanceRunnable| deletes an object on the main thread.
*/
template<class T>
class SocketIODeleteInstanceRunnable MOZ_FINAL : public nsRunnable
{
public:
SocketIODeleteInstanceRunnable(T* aInstance)
: mInstance(aInstance)
{ }
NS_IMETHOD Run() MOZ_OVERRIDE
{
mInstance = nullptr; // delete instance
return NS_OK;
}
private:
nsAutoPtr<T> mInstance;
};
//
// SocketIOBase
//
/* |SocketIOBase| is a base class for Socket I/O classes that
* perform operations on the I/O thread. It provides methds
* for the most common read and write scenarios.
*/
class SocketIOBase
{
public:
virtual ~SocketIOBase();
void EnqueueData(UnixSocketRawData* aData);
bool HasPendingData() const;
template <typename T>
nsresult ReceiveData(int aFd, T* aIO)
{
MOZ_ASSERT(aFd >= 0);
MOZ_ASSERT(aIO);
do {
nsAutoPtr<UnixSocketRawData> incoming(
new UnixSocketRawData(mMaxReadSize));
ssize_t res =
TEMP_FAILURE_RETRY(read(aFd, incoming->mData, incoming->mSize));
if (res < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return NS_OK; /* no more data available */
}
/* an error occored */
nsRefPtr<nsRunnable> r = new SocketIORequestClosingRunnable<T>(aIO);
NS_DispatchToMainThread(r);
return NS_ERROR_FAILURE;
} else if (!res) {
/* EOF or peer shut down sending */
nsRefPtr<nsRunnable> r = new SocketIORequestClosingRunnable<T>(aIO);
NS_DispatchToMainThread(r);
return NS_OK;
}
#ifdef MOZ_TASK_TRACER
// Make unix socket creation events to be the source events of TaskTracer,
// and originate the rest correlation tasks from here.
AutoSourceEvent taskTracerEvent(SourceEventType::UNIXSOCKET);
#endif
incoming->mSize = res;
nsRefPtr<nsRunnable> r =
new SocketIOReceiveRunnable<T>(aIO, incoming.forget());
NS_DispatchToMainThread(r);
} while (true);
return NS_OK;
}
template <typename T>
nsresult SendPendingData(int aFd, T* aIO)
{
MOZ_ASSERT(aFd >= 0);
MOZ_ASSERT(aIO);
do {
if (!HasPendingData()) {
return NS_OK;
}
UnixSocketRawData* outgoing = mOutgoingQ.ElementAt(0);
MOZ_ASSERT(outgoing->mSize);
const uint8_t* data = outgoing->mData + outgoing->mCurrentWriteOffset;
size_t size = outgoing->mSize - outgoing->mCurrentWriteOffset;
ssize_t res = TEMP_FAILURE_RETRY(write(aFd, data, size));
if (res < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return NS_OK; /* no more data available */
}
/* an error occored */
nsRefPtr<nsRunnable> r = new SocketIORequestClosingRunnable<T>(aIO);
NS_DispatchToMainThread(r);
return NS_ERROR_FAILURE;
} else if (!res) {
return NS_OK; /* nothing written */
}
outgoing->mCurrentWriteOffset += res;
if (outgoing->mCurrentWriteOffset == outgoing->mSize) {
mOutgoingQ.RemoveElementAt(0);
delete data;
}
} while (true);
return NS_OK;
}
protected:
SocketIOBase(size_t aMaxReadSize);
private:
const size_t mMaxReadSize;
/**
* Raw data queue. Must be pushed/popped from I/O thread only.
*/
nsTArray<UnixSocketRawData*> mOutgoingQ;
};
//
// Socket I/O tasks
//
/* |SocketIOTask| holds a reference to a Socket I/O object. It's
* supposed to run on the I/O thread.
*/
template <typename T>
class SocketIOTask : public CancelableTask
{
public:
virtual ~SocketIOTask()
{ }
T* GetIO() const
{
return mIO;
}
void Cancel() MOZ_OVERRIDE
{
mIO = nullptr;
}
bool IsCanceled() const
{
return !mIO;
}
protected:
SocketIOTask(T* aIO)
: mIO(aIO)
{
MOZ_ASSERT(mIO);
}
private:
T* mIO;
};
/* |SocketIOSendTask| transfers an instance of |UnixSocketRawData| to
* the I/O thread and queues it up for sending the contained data.
*/
template <typename T>
class SocketIOSendTask MOZ_FINAL : public SocketIOTask<T>
{
public:
SocketIOSendTask(T* aIO, UnixSocketRawData* aData)
: SocketIOTask<T>(aIO)
, mData(aData)
{
MOZ_ASSERT(aData);
}
void Run() MOZ_OVERRIDE
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!SocketIOTask<T>::IsCanceled());
T* io = SocketIOTask<T>::GetIO();
MOZ_ASSERT(!io->IsShutdownOnIOThread());
io->Send(mData);
}
private:
UnixSocketRawData* mData;
};
/* |SocketIOShutdownTask| signals shutdown to the Socket I/O object on
* the I/O thread and sends it to the main thread for destruction.
*/
template <typename T>
class SocketIOShutdownTask MOZ_FINAL : public SocketIOTask<T>
{
public:
SocketIOShutdownTask(T* aIO)
: SocketIOTask<T>(aIO)
{ }
void Run() MOZ_OVERRIDE
{
MOZ_ASSERT(!NS_IsMainThread());
T* io = SocketIOTask<T>::GetIO();
// At this point, there should be no new events on the I/O thread
// after this one with the possible exception of an accept task,
// which ShutdownOnIOThread will cancel for us. We are now fully
// shut down, so we can send a message to the main thread to delete
// |io| safely knowing that it's not reference any longer.
io->ShutdownOnIOThread();
nsRefPtr<nsRunnable> r = new SocketIODeleteInstanceRunnable<T>(io);
nsresult rv = NS_DispatchToMainThread(r);
NS_ENSURE_SUCCESS_VOID(rv);
}
};
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -7,51 +7,18 @@
#ifndef mozilla_ipc_UnixSocket_h
#define mozilla_ipc_UnixSocket_h
#include <stdlib.h>
#include "nsAutoPtr.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "mozilla/ipc/UnixSocketWatcher.h"
#include "mozilla/ipc/SocketBase.h"
#include "mozilla/RefPtr.h"
namespace mozilla {
namespace ipc {
class UnixSocketRawData
{
public:
// Number of octets in mData.
size_t mSize;
size_t mCurrentWriteOffset;
nsAutoArrayPtr<uint8_t> mData;
/**
* Constructor for situations where only size is known beforehand
* (for example, when being assigned strings)
*/
UnixSocketRawData(size_t aSize) :
mSize(aSize),
mCurrentWriteOffset(0)
{
mData = new uint8_t[mSize];
}
/**
* Constructor for situations where size and data is known
* beforehand (for example, when being assigned strings)
*/
UnixSocketRawData(const void* aData, size_t aSize)
: mSize(aSize),
mCurrentWriteOffset(0)
{
MOZ_ASSERT(aData || !mSize);
mData = new uint8_t[mSize];
memcpy(mData, aData, mSize);
}
};
class UnixSocketImpl;
class UnixSocketConsumerIO;
/**
* UnixSocketConnector defines the socket creation and connection/listening
@ -128,43 +95,14 @@ public:
};
enum SocketConnectionStatus {
SOCKET_DISCONNECTED = 0,
SOCKET_LISTENING = 1,
SOCKET_CONNECTING = 2,
SOCKET_CONNECTED = 3
};
class UnixSocketConsumer
class UnixSocketConsumer : public SocketConsumerBase
{
protected:
virtual ~UnixSocketConsumer();
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UnixSocketConsumer)
UnixSocketConsumer();
SocketConnectionStatus GetConnectionStatus() const
{
MOZ_ASSERT(NS_IsMainThread());
return mConnectionStatus;
}
int GetSuggestedConnectDelayMs() const
{
MOZ_ASSERT(NS_IsMainThread());
return mConnectDelayMs;
}
/**
* Function to be called whenever data is received. This is only called on the
* main thread.
*
* @param aMessage Data received from the socket.
*/
virtual void ReceiveSocketData(nsAutoPtr<UnixSocketRawData>& aMessage) = 0;
/**
* Queue data to be sent to the socket on the IO thread. Can only be called on
* originating thread.
@ -216,49 +154,13 @@ public:
*/
void CloseSocket();
/**
* Callback for socket connect/accept success. Called after connect/accept has
* finished. Will be run on main thread, before any reads take place.
*/
virtual void OnConnectSuccess() = 0;
/**
* Callback for socket connect/accept error. Will be run on main thread.
*/
virtual void OnConnectError() = 0;
/**
* Callback for socket disconnect. Will be run on main thread.
*/
virtual void OnDisconnect() = 0;
/**
* Called by implementation to notify consumer of success.
*/
void NotifySuccess();
/**
* Called by implementation to notify consumer of error.
*/
void NotifyError();
/**
* Called by implementation to notify consumer of disconnect.
*/
void NotifyDisconnect();
/**
* Get the current sockaddr for the socket
*/
void GetSocketAddr(nsAString& aAddrStr);
private:
uint32_t CalculateConnectDelayMs() const;
UnixSocketImpl* mImpl;
SocketConnectionStatus mConnectionStatus;
PRIntervalTime mConnectTimestamp;
uint32_t mConnectDelayMs;
UnixSocketConsumerIO* mIO;
};
} // namespace ipc

View File

@ -5,11 +5,13 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXPORTS.mozilla.ipc += [
'UnixSocket.h',
'SocketBase.h',
'UnixSocket.h'
]
SOURCES += [
'UnixSocket.cpp',
'SocketBase.cpp',
'UnixSocket.cpp'
]
FAIL_ON_WARNINGS = True

View File

@ -349,14 +349,6 @@
</activity>
#endif
<activity android:name="org.mozilla.gecko.VideoPlayer"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|locale|layoutDirection"
android:theme="@android:style/Theme.NoTitleBar">
<intent-filter>
<action android:name="org.mozilla.gecko.PLAY_VIDEO" />
</intent-filter>
</activity>
<activity android:name="org.mozilla.gecko.preferences.GeckoPreferences"
android:theme="@style/Gecko.Preferences"
android:configChanges="orientation|screenSize|locale|layoutDirection"

View File

@ -2545,6 +2545,7 @@ public class BrowserApp extends GeckoApp
tab.addBookmark();
getButtonToast().show(false,
getResources().getString(R.string.bookmark_added),
ButtonToast.LENGTH_SHORT,
getResources().getString(R.string.bookmark_options),
null,
new ButtonToast.ToastListener() {

View File

@ -605,14 +605,14 @@ public abstract class GeckoApp
} else if ("Toast:Show".equals(event)) {
final String msg = message.getString("message");
final String duration = message.getString("duration");
final NativeJSObject button = message.optObject("button", null);
if (button != null) {
final String label = button.optString("label", "");
final String icon = button.optString("icon", "");
final String id = button.optString("id", "");
showButtonToast(msg, label, icon, id);
showButtonToast(msg, duration, label, icon, id);
} else {
final String duration = message.getString("duration");
showNormalToast(msg, duration);
}
@ -788,12 +788,14 @@ public abstract class GeckoApp
return mToast;
}
void showButtonToast(final String message, final String buttonText,
final String buttonIcon, final String buttonId) {
void showButtonToast(final String message, final String duration,
final String buttonText, final String buttonIcon,
final String buttonId) {
BitmapUtils.getDrawable(GeckoApp.this, buttonIcon, new BitmapUtils.BitmapLoader() {
@Override
public void onBitmapFound(final Drawable d) {
getButtonToast().show(false, message, buttonText, d, new ButtonToast.ToastListener() {
final int toastDuration = duration.equals("long") ? ButtonToast.LENGTH_LONG : ButtonToast.LENGTH_SHORT;
getButtonToast().show(false, message, toastDuration ,buttonText, d, new ButtonToast.ToastListener() {
@Override
public void onButtonClicked() {
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Toast:Click", buttonId));

View File

@ -48,7 +48,6 @@ import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.NativeJSContainer;
import org.mozilla.gecko.util.ProxySelector;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.webapp.Allocator;
import android.app.Activity;
import android.app.ActivityManager;
@ -1142,24 +1141,36 @@ public class GeckoAppShell
final String scheme = uri.getScheme();
final Intent intent;
// Compute our most likely intent, then check to see if there are any
// custom handlers that would apply.
// Start with the original URI. If we end up modifying it, we'll
// overwrite it.
final Intent likelyIntent = getIntentForActionString(action);
likelyIntent.setData(uri);
final Intent intent = getIntentForActionString(action);
intent.setData(uri);
if ("vnd.youtube".equals(scheme) && !hasHandlersForIntent(likelyIntent)) {
// Special-case YouTube to use our own player if no system handler
// exists.
intent = new Intent(VideoPlayer.VIDEO_ACTION);
intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
"org.mozilla.gecko.VideoPlayer");
intent.setData(uri);
} else {
intent = likelyIntent;
if ("vnd.youtube".equals(scheme) &&
!hasHandlersForIntent(intent) &&
!TextUtils.isEmpty(uri.getSchemeSpecificPart())) {
// Return an intent with a URI that will open the YouTube page in the
// current Fennec instance.
final Class<?> c;
final String browserClassName = AppConstants.BROWSER_INTENT_CLASS_NAME;
try {
c = Class.forName(browserClassName);
} catch (ClassNotFoundException e) {
// This should never occur.
Log.wtf(LOGTAG, "Class " + browserClassName + " not found!");
return null;
}
final Uri youtubeURI = getYouTubeHTML5URI(uri);
if (youtubeURI != null) {
// Load it as a new URL in the current tab. Hitting 'back' will return
// the user to the YouTube overview page.
final Intent view = new Intent(GeckoApp.ACTION_LOAD, youtubeURI, context, c);
return view;
}
}
// Have a special handling for SMS, as the message body
@ -1202,6 +1213,30 @@ public class GeckoAppShell
return intent;
}
/**
* Input: vnd:youtube:3MWr19Dp2OU?foo=bar
* Output: https://www.youtube.com/embed/3MWr19Dp2OU?foo=bar
*
* Ideally this should include ?html5=1. However, YouTube seems to do a
* fine job of taking care of this on its own, and the Kindle Fire ships
* Flash, so...
*
* @param uri a vnd:youtube URI.
* @return an HTTPS URI for web player.
*/
private static Uri getYouTubeHTML5URI(final Uri uri) {
if (uri == null) {
return null;
}
final String ssp = uri.getSchemeSpecificPart();
if (TextUtils.isEmpty(ssp)) {
return null;
}
return Uri.parse("https://www.youtube.com/embed/" + ssp);
}
/**
* Only called from GeckoApp.
*/
@ -1642,7 +1677,7 @@ public class GeckoAppShell
try {
String filter = GeckoProfile.get(getContext()).getDir().toString();
Log.i(LOGTAG, "[OPENFILE] Filter: " + filter);
Log.d(LOGTAG, "[OPENFILE] Filter: " + filter);
// run lsof and parse its output
java.lang.Process lsof = Runtime.getRuntime().exec("lsof");
@ -1675,7 +1710,7 @@ public class GeckoAppShell
}
String file = split[nameColumn];
if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(file) && file.startsWith(filter))
Log.i(LOGTAG, "[OPENFILE] " + name + "(" + split[pidColumn] + ") : " + file);
Log.d(LOGTAG, "[OPENFILE] " + name + "(" + split[pidColumn] + ") : " + file);
}
in.close();
} catch (Exception e) { }

View File

@ -1,97 +0,0 @@
/* -*- 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;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.widget.MediaController;
import android.widget.VideoView;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
public final class VideoPlayer extends Activity {
public static final String VIDEO_ACTION = "org.mozilla.gecko.PLAY_VIDEO";
private VideoView mVideoView;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.videoplayer);
mVideoView = (VideoView) findViewById(R.id.VideoView);
MediaController mediaController = new MediaController(this);
mediaController.setAnchorView(mVideoView);
Intent intent = getIntent();
final Uri data = intent.getData();
if (data == null) {
return;
}
String spec = null;
if ("vnd.youtube".equals(data.getScheme())) {
String ssp = data.getSchemeSpecificPart();
int paramIndex = ssp.indexOf('?');
String id;
if (paramIndex == -1) {
id = ssp;
} else {
id = ssp.substring(0, paramIndex);
}
spec = getSpecFromYouTubeVideoID(id);
}
if (spec == null) {
return;
}
final Uri video = Uri.parse(spec);
mVideoView.setMediaController(mediaController);
mVideoView.setVideoURI(video);
mVideoView.start();
}
private String getSpecFromYouTubeVideoID(String id) {
String spec = null;
try {
String infoUri = "http://www.youtube.com/get_video_info?&video_id=" + id;
URL infoUrl = new URL(infoUri);
URLConnection urlConnection = infoUrl.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
try {
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null)
sb.append(line);
android.net.Uri fakeUri = android.net.Uri.parse("fake:/fake?" + sb);
String streamMap = fakeUri.getQueryParameter("url_encoded_fmt_stream_map");
if (streamMap == null)
return null;
String[] streams = streamMap.split(",");
for (int i = 0; i < streams.length; i++) {
fakeUri = android.net.Uri.parse("fake:/fake?" + streams[i]);
String url = fakeUri.getQueryParameter("url");
String type = fakeUri.getQueryParameter("type");
if (type != null && url != null &&
(type.startsWith("video/mp4") || type.startsWith("video/webm"))) {
spec = url;
}
}
} finally {
br.close();
}
} catch (Exception e) {
Log.e("VideoPlayer", "exception", e);
}
return spec;
}
}

View File

@ -472,14 +472,6 @@ sync_thirdparty_java_files = [
]
sync_java_files = [
'background/announcements/Announcement.java',
'background/announcements/AnnouncementPresenter.java',
'background/announcements/AnnouncementsBroadcastReceiver.java',
'background/announcements/AnnouncementsBroadcastService.java',
'background/announcements/AnnouncementsFetchDelegate.java',
'background/announcements/AnnouncementsFetcher.java',
'background/announcements/AnnouncementsFetchResourceDelegate.java',
'background/announcements/AnnouncementsService.java',
'background/BackgroundService.java',
'background/bagheera/BagheeraClient.java',
'background/bagheera/BagheeraRequestDelegate.java',
@ -853,7 +845,6 @@ sync_java_files = [
]
sync_generated_java_files = [
'org/mozilla/gecko/background/announcements/AnnouncementsConstants.java',
'org/mozilla/gecko/background/common/GlobalConstants.java',
'org/mozilla/gecko/background/healthreport/HealthReportConstants.java',
'org/mozilla/gecko/fxa/FxAccountConstants.java',

View File

@ -1,111 +0,0 @@
/* 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.background.announcements;
import java.net.URI;
import java.net.URISyntaxException;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.sync.ExtendedJSONObject;
/**
* Represents a retrieved product announcement.
*
* Instances of this class are immutable.
*/
public class Announcement {
private static final String LOG_TAG = "Announcement";
private static final String KEY_ID = "id";
private static final String KEY_TITLE = "title";
private static final String KEY_URL = "url";
private static final String KEY_TEXT = "text";
private final int id;
private final String title;
private final URI uri;
private final String text;
public Announcement(int id, String title, String text, URI uri) {
this.id = id;
this.title = title;
this.uri = uri;
this.text = text;
}
public static Announcement parseAnnouncement(ExtendedJSONObject body) throws URISyntaxException, IllegalArgumentException {
final Integer id = body.getIntegerSafely(KEY_ID);
if (id == null) {
throw new IllegalArgumentException("No id provided in JSON.");
}
final String title = body.getString(KEY_TITLE);
if (title == null || title.trim().length() == 0) {
throw new IllegalArgumentException("Missing or empty announcement title.");
}
final String uri = body.getString(KEY_URL);
if (uri == null) {
// Empty or otherwise unhappy URI will throw a URISyntaxException.
throw new IllegalArgumentException("Missing announcement URI.");
}
final String text = body.getString(KEY_TEXT);
if (text == null) {
throw new IllegalArgumentException("Missing announcement body.");
}
return new Announcement(id, title, text, new URI(uri));
}
public int getId() {
return id;
}
public String getTitle() {
return title;
}
public String getText() {
return text;
}
public URI getUri() {
return uri;
}
public ExtendedJSONObject asJSON() {
ExtendedJSONObject out = new ExtendedJSONObject();
out.put(KEY_ID, id);
out.put(KEY_TITLE, title);
out.put(KEY_URL, uri.toASCIIString());
out.put(KEY_TEXT, text);
return out;
}
/**
* Return false if the provided Announcement is in some way invalid,
* regardless of being well-formed.
*/
public static boolean isValidAnnouncement(final Announcement an) {
final URI uri = an.getUri();
if (uri == null) {
Logger.warn(LOG_TAG, "No URI: announcement not valid.");
return false;
}
final String scheme = uri.getScheme();
if (scheme == null) {
Logger.warn(LOG_TAG, "Null scheme: announcement not valid.");
return false;
}
// Only allow HTTP and HTTPS URLs.
if (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https")) {
Logger.warn(LOG_TAG, "Scheme '" + scheme + "' forbidden: announcement not valid.");
return false;
}
return true;
}
}

View File

@ -1,83 +0,0 @@
/* 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.background.announcements;
import java.net.URI;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.common.GlobalConstants;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
/**
* Handle requests to display a fetched announcement.
*/
public class AnnouncementPresenter {
/**
* Display the provided snippet.
* @param context
* The context instance to use when obtaining the NotificationManager.
* @param notificationID
* A unique ID for this notification.
* @param title
* The *already localized* String title. Must not be null.
* @param body
* The *already localized* String body. Must not be null.
* @param uri
* The URL to open when the notification is tapped.
*/
@SuppressWarnings("deprecation")
public static void displayAnnouncement(final Context context,
final int notificationID,
final String title,
final String body,
final URI uri) {
final String ns = Context.NOTIFICATION_SERVICE;
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(ns);
// Set pending intent associated with the notification.
Uri u = Uri.parse(uri.toASCIIString());
Intent intent = new Intent(Intent.ACTION_VIEW, u);
// Always open the link with Fennec.
intent.setClassName(GlobalConstants.BROWSER_INTENT_PACKAGE, GlobalConstants.BROWSER_INTENT_CLASS);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, intent, 0);
final int icon = R.drawable.ic_status_logo;
// Deprecated approach to building a notification.
final long when = System.currentTimeMillis();
Notification notification = new Notification(icon, title, when);
notification.flags = Notification.FLAG_AUTO_CANCEL;
notification.setLatestEventInfo(context, title, body, contentIntent);
// Notification.Builder since API 11.
/*
Notification notification = new Notification.Builder(context)
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true)
.setContentIntent(contentIntent).getNotification();
*/
// Send notification.
notificationManager.notify(notificationID, notification);
}
public static void displayAnnouncement(final Context context,
final Announcement snippet) {
final int notificationID = snippet.getId();
final String title = snippet.getTitle();
final String body = snippet.getText();
final URI uri = snippet.getUri();
AnnouncementPresenter.displayAnnouncement(context, notificationID, title, body, uri);
}
}

View File

@ -1,35 +0,0 @@
/* 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.background.announcements;
import org.mozilla.gecko.background.BackgroundService;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* Watch for notifications to start the announcements service.
*
* Some observations:
*
* "Also note that as of Android 3.0 the user needs to have started the
* application at least once before your application can receive
* android.intent.action.BOOT_COMPLETED events."
*/
public class AnnouncementsBroadcastReceiver extends BroadcastReceiver {
/**
* Forward the intent to an IntentService to do background processing.
*/
@Override
public void onReceive(Context context, Intent intent) {
if (AnnouncementsConstants.DISABLED) {
return;
}
BackgroundService.runIntentInService(context, intent, AnnouncementsBroadcastService.class);
}
}

View File

@ -1,179 +0,0 @@
/* 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.background.announcements;
import org.mozilla.gecko.background.BackgroundService;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.common.log.Logger;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
/**
* A service which listens to broadcast intents from the system and from the
* browser, registering or unregistering the main
* {@link AnnouncementsService} with the {@link AlarmManager}.
*/
public class AnnouncementsBroadcastService extends BackgroundService {
private static final String WORKER_THREAD_NAME = "AnnouncementsBroadcastServiceWorker";
private static final String LOG_TAG = "AnnounceBrSvc";
public AnnouncementsBroadcastService() {
super(WORKER_THREAD_NAME);
}
protected static SharedPreferences getSharedPreferences(Context context) {
return context.getSharedPreferences(AnnouncementsConstants.PREFS_BRANCH,
GlobalConstants.SHARED_PREFERENCES_MODE);
}
protected SharedPreferences getSharedPreferences() {
return this.getSharedPreferences(AnnouncementsConstants.PREFS_BRANCH,
GlobalConstants.SHARED_PREFERENCES_MODE);
}
private void toggleAlarm(final Context context, boolean enabled) {
final Class<?> serviceClass = AnnouncementsService.class;
Logger.info(LOG_TAG, (enabled ? "R" : "Unr") + "egistering " + serviceClass.getSimpleName() +
".");
final Intent service = new Intent(context, serviceClass);
final PendingIntent pending = PendingIntent.getService(context, 0, service,
PendingIntent.FLAG_CANCEL_CURRENT);
if (!enabled) {
cancelAlarm(pending);
return;
}
final long pollInterval = getPollInterval(context);
scheduleAlarm(pollInterval, pending);
}
/**
* Record the last launch time of our version of Fennec.
*
* @param context
* the <code>Context</code> to use to gain access to
* <code>SharedPreferences</code>.
*/
public static void recordLastLaunch(final Context context) {
final long now = System.currentTimeMillis();
final SharedPreferences preferences = getSharedPreferences(context);
// One of several things might be true, according to our logs:
//
// * The new current time is older than the last
// * or way in the future
// * or way in the distant past
// * or it's reasonable.
//
// Furthermore, when we come to calculate idle we might find that the clock
// is dramatically different that the current time is thirteen years older
// than our saved timestamp (system clock resets to 2000 on battery change),
// or it's thirty years in the future (previous timestamp was saved as 0).
//
// We should try to do something vaguely sane in these situations.
long previous = preferences.getLong(AnnouncementsConstants.PREF_LAST_LAUNCH, -1);
if (previous == -1) {
Logger.debug(LOG_TAG, "No previous launch recorded.");
}
if (now < GlobalConstants.BUILD_TIMESTAMP_MSEC) {
Logger.warn(LOG_TAG, "Current time " + now + " is older than build date " +
GlobalConstants.BUILD_TIMESTAMP_MSEC + ". Ignoring until clock is corrected.");
return;
}
if (now > AnnouncementsConstants.LATEST_ACCEPTED_LAUNCH_TIMESTAMP_MSEC) {
Logger.warn(LOG_TAG, "Launch time " + now + " is later than max sane launch timestamp " +
AnnouncementsConstants.LATEST_ACCEPTED_LAUNCH_TIMESTAMP_MSEC +
". Ignoring until clock is corrected.");
return;
}
if (previous > now) {
Logger.debug(LOG_TAG, "Previous launch " + previous + " later than current time " +
now + ", but new time is sane. Accepting new time.");
}
preferences.edit().putLong(AnnouncementsConstants.PREF_LAST_LAUNCH, now).commit();
}
public static long getPollInterval(final Context context) {
final SharedPreferences preferences = getSharedPreferences(context);
return preferences.getLong(AnnouncementsConstants.PREF_ANNOUNCE_FETCH_INTERVAL_MSEC, AnnouncementsConstants.DEFAULT_ANNOUNCE_FETCH_INTERVAL_MSEC);
}
public static void setPollInterval(final Context context, long interval) {
final SharedPreferences preferences = getSharedPreferences(context);
preferences.edit().putLong(AnnouncementsConstants.PREF_ANNOUNCE_FETCH_INTERVAL_MSEC, interval).commit();
}
@Override
protected void onHandleIntent(Intent intent) {
Logger.setThreadLogTag(AnnouncementsConstants.GLOBAL_LOG_TAG);
// Intent can be null. Bug 1025937.
if (intent == null) {
Logger.debug(LOG_TAG, "Short-circuiting on null intent.");
return;
}
final String action = intent.getAction();
Logger.debug(LOG_TAG, "Broadcast onReceive. Intent is " + action);
if (AnnouncementsConstants.ACTION_ANNOUNCEMENTS_PREF.equals(action)) {
handlePrefIntent(intent);
return;
}
if (Intent.ACTION_BOOT_COMPLETED.equals(action) ||
Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
BackgroundService.reflectContextToFennec(this,
GlobalConstants.GECKO_PREFERENCES_CLASS,
GlobalConstants.GECKO_BROADCAST_ANNOUNCEMENTS_PREF_METHOD);
return;
}
// Failure case.
Logger.warn(LOG_TAG, "Unknown intent " + action);
}
/**
* Handle the intent sent by the browser when it wishes to notify us
* of the value of the user preference. Look at the value and toggle the
* alarm service accordingly.
*
* @param intent must be non-null.
*/
private void handlePrefIntent(Intent intent) {
if (!intent.hasExtra("enabled")) {
Logger.warn(LOG_TAG, "Got ANNOUNCEMENTS_PREF intent without enabled. Ignoring.");
return;
}
final boolean enabled = intent.getBooleanExtra("enabled", true);
Logger.debug(LOG_TAG, intent.getStringExtra("branch") + "/" +
intent.getStringExtra("pref") + " = " +
(intent.hasExtra("enabled") ? enabled : ""));
toggleAlarm(this, enabled);
// Primarily intended for debugging and testing, but this doesn't do any harm.
if (!enabled) {
Logger.info(LOG_TAG, "!enabled: clearing last fetch.");
final SharedPreferences sharedPreferences = getSharedPreferences();
final Editor editor = sharedPreferences.edit();
editor.remove(AnnouncementsConstants.PREF_LAST_FETCH_LOCAL_TIME);
editor.remove(AnnouncementsConstants.PREF_EARLIEST_NEXT_ANNOUNCE_FETCH);
editor.commit();
}
}
}

View File

@ -1,50 +0,0 @@
#filter substitution
/* 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.background.announcements;
import org.mozilla.gecko.background.common.GlobalConstants;
import android.app.AlarmManager;
public class AnnouncementsConstants {
// Not `final` so we have the option to turn this on at runtime with a magic addon.
public static boolean DISABLED = false;
public static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
public static final String GLOBAL_LOG_TAG = "GeckoAnnounce";
public static final String ACTION_ANNOUNCEMENTS_PREF = "@ANDROID_PACKAGE_NAME@.ANNOUNCEMENTS_PREF";
static final String PREFS_BRANCH = "background";
static final String PREF_LAST_FETCH_LOCAL_TIME = "last_fetch";
static final String PREF_LAST_FETCH_SERVER_DATE = "last_announce_date";
static final String PREF_LAST_LAUNCH = "last_firefox_launch";
static final String PREF_ANNOUNCE_SERVER_BASE_URL = "announce_server_base_url";
static final String PREF_EARLIEST_NEXT_ANNOUNCE_FETCH = "earliest_next_announce_fetch";
static final String PREF_ANNOUNCE_FETCH_INTERVAL_MSEC = "announce_fetch_interval_msec";
public static String DEFAULT_ANNOUNCE_SERVER_BASE_URL = "https://campaigns.services.mozilla.com/announce/";
public static final String ANNOUNCE_PROTOCOL_VERSION = "1";
public static final String ANNOUNCE_APPLICATION = "android";
public static String ANNOUNCE_PATH_SUFFIX = AnnouncementsConstants.ANNOUNCE_PROTOCOL_VERSION + "/" +
AnnouncementsConstants.ANNOUNCE_APPLICATION + "/";
public static long DEFAULT_ANNOUNCE_FETCH_INTERVAL_MSEC = AlarmManager.INTERVAL_HALF_DAY;
public static long DEFAULT_BACKOFF_MSEC = 2 * 24 * 60 * 60 * 1000; // Two days. Used if no Retry-After header.
public static long MINIMUM_FETCH_INTERVAL_MSEC = 60 * 60 * 1000; // 1 hour.
// Stop reporting idle counts once they hit one year.
public static long MAX_SANE_IDLE_DAYS = 365;
// Don't track last launch if the timestamp is ridiculously out of range:
// four years after build.
public static long LATEST_ACCEPTED_LAUNCH_TIMESTAMP_MSEC = GlobalConstants.BUILD_TIMESTAMP_MSEC +
4 * 365 * MILLISECONDS_PER_DAY;
public static String USER_AGENT = "Firefox Announcements " + GlobalConstants.MOZ_APP_VERSION;
public static String ANNOUNCE_CHANNEL = GlobalConstants.MOZ_UPDATE_CHANNEL.replace("default", GlobalConstants.MOZ_OFFICIAL_BRANDING ? "release" : "dev");
}

View File

@ -1,48 +0,0 @@
/* 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.background.announcements;
import java.util.List;
import java.util.Locale;
public interface AnnouncementsFetchDelegate {
/**
* @return the timestamp of the last fetch in milliseconds.
*/
public long getLastFetch();
/**
* @return the Date header string of the last response, or null if not present.
*/
public String getLastDate();
/**
* @return the current system locale (e.g., en_us).
*/
public Locale getLocale();
/**
* @return the User-Agent header to use for the request.
*/
public String getUserAgent();
/**
* @return the server URL to interrogate, including path.
*/
public String getServiceURL();
/*
* Callback methods.
* Note that we provide both a local fetch time and a server date here.
* This is so we can track how long we've waited (local), and supply the
* date back to the server for If-Modified-Since.
*/
public void onNoNewAnnouncements(long localFetchTime, String serverDate);
public void onNewAnnouncements(List<Announcement> snippets, long localFetchTime, String serverDate);
public void onLocalError(Exception e);
public void onRemoteError(Exception e);
public void onRemoteFailure(int status);
public void onBackoff(int retryAfterInSeconds);
}

View File

@ -1,183 +0,0 @@
/* 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.background.announcements;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.NonArrayJSONException;
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.net.BaseResourceDelegate;
import org.mozilla.gecko.sync.net.Resource;
import org.mozilla.gecko.sync.net.SyncResponse;
import ch.boye.httpclientandroidlib.Header;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
import ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
import ch.boye.httpclientandroidlib.protocol.HTTP;
/**
* Converts HTTP resource callbacks into AnnouncementsFetchDelegate callbacks.
*/
public class AnnouncementsFetchResourceDelegate extends BaseResourceDelegate {
private static final String ACCEPT_HEADER = "application/json;charset=utf-8";
private static final String LOG_TAG = "AnnounceFetchRD";
protected final long startTime;
protected AnnouncementsFetchDelegate delegate;
public AnnouncementsFetchResourceDelegate(Resource resource, AnnouncementsFetchDelegate delegate) {
super(resource);
this.startTime = System.currentTimeMillis();
this.delegate = delegate;
}
@Override
public String getUserAgent() {
return delegate.getUserAgent();
}
@Override
public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
super.addHeaders(request, client);
// The basics.
request.addHeader("Accept-Language", delegate.getLocale().toString());
request.addHeader("Accept", ACCEPT_HEADER);
// We never want to keep connections alive.
request.addHeader("Connection", "close");
// Set If-Modified-Since to avoid re-fetching content.
final String ifModifiedSince = delegate.getLastDate();
if (ifModifiedSince != null) {
Logger.info(LOG_TAG, "If-Modified-Since: " + ifModifiedSince);
request.addHeader("If-Modified-Since", ifModifiedSince);
}
// Just in case.
request.removeHeaders("Cookie");
}
private List<Announcement> parseBody(ExtendedJSONObject body) throws NonArrayJSONException {
List<Announcement> out = new ArrayList<Announcement>(1);
JSONArray snippets = body.getArray("announcements");
if (snippets == null) {
Logger.warn(LOG_TAG, "Missing announcements body. Returning empty.");
return out;
}
for (Object s : snippets) {
try {
out.add(Announcement.parseAnnouncement(new ExtendedJSONObject((JSONObject) s)));
} catch (Exception e) {
Logger.warn(LOG_TAG, "Malformed announcement or display failed. Skipping.", e);
}
}
return out;
}
@Override
public void handleHttpResponse(HttpResponse response) {
final Header dateHeader = response.getFirstHeader(HTTP.DATE_HEADER);
String date = null;
if (dateHeader != null) {
// Note that we are deliberately not validating the server time here.
// We pass it directly back to the server; we don't care about the
// contents, and if we reject a value we essentially re-initialize
// the client, which will cause stale announcements to be re-fetched.
date = dateHeader.getValue();
}
if (date == null) {
// Use local clock, because skipping is better than re-fetching.
date = DateUtils.formatDate(new Date());
Logger.warn(LOG_TAG, "No fetch date; using local time " + date);
}
final SyncResponse r = new SyncResponse(response); // For convenience.
try {
final int statusCode = r.getStatusCode();
Logger.debug(LOG_TAG, "Got announcements response: " + statusCode);
if (statusCode == 204 || statusCode == 304) {
BaseResource.consumeEntity(response);
delegate.onNoNewAnnouncements(startTime, date);
return;
}
if (statusCode == 200) {
final List<Announcement> snippets;
try {
snippets = parseBody(r.jsonObjectBody());
} catch (Exception e) {
delegate.onRemoteError(e);
return;
}
delegate.onNewAnnouncements(snippets, startTime, date);
return;
}
if (statusCode == 400 || statusCode == 405) {
// We did something wrong.
Logger.warn(LOG_TAG, "We did something wrong. Oh dear.");
// Fall through.
}
if (statusCode == 503 || statusCode == 500) {
Logger.warn(LOG_TAG, "Server issue: " + r.body());
delegate.onBackoff(r.retryAfterInSeconds());
return;
}
// Otherwise, clean up.
delegate.onRemoteFailure(statusCode);
} catch (Exception e) {
Logger.warn(LOG_TAG, "Failed to extract body.", e);
delegate.onRemoteError(e);
}
}
@Override
public void handleHttpProtocolException(ClientProtocolException e) {
Logger.warn(LOG_TAG, "Protocol exception.", e);
delegate.onLocalError(e);
}
@Override
public void handleHttpIOException(IOException e) {
Logger.warn(LOG_TAG, "IO exception.", e);
delegate.onLocalError(e);
}
@Override
public void handleTransportException(GeneralSecurityException e) {
Logger.warn(LOG_TAG, "Transport exception.", e);
// Class this as a remote error, because it's probably something odd
// with SSL negotiation.
delegate.onRemoteError(e);
}
/**
* Be very thorough in case the superclass implementation changes.
* We never want this to be an authenticated request.
*/
@Override
public AuthHeaderProvider getAuthHeaderProvider() {
return null;
}
}

View File

@ -1,134 +0,0 @@
/* 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.background.announcements;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.sync.net.BaseResource;
public class AnnouncementsFetcher {
private static final String LOG_TAG = "AnnounceFetch";
private static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
public static URI getSnippetURI(String base, String channel,
String version, String platform,
int idleDays)
throws URISyntaxException {
try {
final String c = URLEncoder.encode(channel, "UTF-8");
final String v = URLEncoder.encode(version, "UTF-8");
final String p = URLEncoder.encode(platform, "UTF-8");
final String s = base + c + "/" + v + "/" + p + ((idleDays == -1) ? "" : ("?idle=" + idleDays));
return new URI(s);
} catch (UnsupportedEncodingException e) {
// Nonsense.
return null;
}
}
public static URI getAnnounceURI(final String baseURL, final long lastLaunch) throws URISyntaxException {
final String channel = getChannel();
final String version = getVersion();
final String platform = getPlatform();
final int idleDays = getIdleDays(lastLaunch);
Logger.debug(LOG_TAG, "Fetch URI: idle for " + idleDays + " days.");
return getSnippetURI(baseURL, channel, version, platform, idleDays);
}
protected static String getChannel() {
return AnnouncementsConstants.ANNOUNCE_CHANNEL;
}
protected static String getVersion() {
return GlobalConstants.MOZ_APP_VERSION;
}
protected static String getPlatform() {
return GlobalConstants.ANDROID_CPU_ARCH;
}
/**
* Return the number of days that we've been idle, assuming that we have a
* sane last launch time and the current time is within range. If no sane idle
* time can be returned, we return -1.
*
* @param lastLaunch
* Time at which the browser was last launched, in milliseconds since epoch.
* @param now
* Milliseconds since epoch for which idle time should be calculated.
* @return number of idle days, or -1 if out of range.
*/
protected static int getIdleDays(final long lastLaunch, final long now) {
if (lastLaunch <= 0) {
return -1;
}
if (now < GlobalConstants.BUILD_TIMESTAMP_MSEC) {
Logger.warn(LOG_TAG, "Current time " + now + " earlier than build date. Not calculating idle.");
return -1;
}
if (now < lastLaunch) {
Logger.warn(LOG_TAG, "Current time " + now + " earlier than last launch! Not calculating idle.");
return -1;
}
final long idleMillis = now - lastLaunch;
final int idleDays = (int) (idleMillis / MILLISECONDS_PER_DAY);
if (idleDays > AnnouncementsConstants.MAX_SANE_IDLE_DAYS) {
Logger.warn(LOG_TAG, "Idle from " + lastLaunch + " until " + now +
", which is insane. Not calculating idle.");
return -1;
}
return idleDays;
}
/**
* Return the number of days that we've been idle, assuming that we have a
* sane last launch time and the current time is within range. If no sane idle
* time can be returned, we return -1.
* The current time will be calculated from {@link System#currentTimeMillis()}.
*
* @param lastLaunch
* Unix timestamp at which the browser was last launched.
* @return number of idle days, or -1 if out of range.
*/
protected static int getIdleDays(final long lastLaunch) {
final long now = System.currentTimeMillis();
return getIdleDays(lastLaunch, now);
}
public static void fetchAnnouncements(URI uri, AnnouncementsFetchDelegate delegate) {
BaseResource r = new BaseResource(uri);
r.delegate = new AnnouncementsFetchResourceDelegate(r, delegate);
r.getBlocking();
}
/**
* Synchronous.
*/
public static void fetchAndProcessAnnouncements(long lastLaunch,
AnnouncementsFetchDelegate delegate) {
final long now = System.currentTimeMillis();
Logger.debug(LOG_TAG, "Fetching announcements. Last launch: " + lastLaunch + "; now: " + now);
try {
final String base = delegate.getServiceURL();
final URI uri = getAnnounceURI(base, lastLaunch);
Logger.info(LOG_TAG, "Fetching announcements from " + uri.toASCIIString());
fetchAnnouncements(uri, delegate);
} catch (URISyntaxException e) {
Logger.warn(LOG_TAG, "Couldn't create URL.", e);
return;
}
}
}

View File

@ -1,292 +0,0 @@
/* 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.background.announcements;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.net.URI;
import java.util.List;
import java.util.Locale;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.background.BackgroundService;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.common.log.Logger;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
/**
* A Service to periodically check for new published announcements,
* presenting them to the user if local conditions permit.
*
* We extend IntentService, rather than just Service, because this gives us
* a worker thread to avoid main-thread networking.
*
* Yes, even though we're in an alarm-triggered service, it still counts
* as main-thread.
*
* The operation of this service is as follows:
*
* 0. Decide if a request should be made.
* 1. Compute the arguments to the request. This includes enough
* pertinent details to allow the server to pre-filter a message
* set, recording enough tracking details to compute statistics.
* 2. Issue the request. If this succeeds with a 200 or 204, great;
* track that timestamp for the next run through Step 0.
* 3. Process any received messages.
*
* Message processing is as follows:
*
* 0. Decide if message display should occur. This might involve
* user preference or other kinds of environmental factors.
* 1. Use the AnnouncementPresenter to open the announcement.
*
* Future:
* * Persisting of multiple announcements.
* * Prioritization.
*/
public class AnnouncementsService extends BackgroundService implements AnnouncementsFetchDelegate {
private static final String WORKER_THREAD_NAME = "AnnouncementsServiceWorker";
private static final String LOG_TAG = "AnnounceService";
public AnnouncementsService() {
super(WORKER_THREAD_NAME);
Logger.setThreadLogTag(AnnouncementsConstants.GLOBAL_LOG_TAG);
Logger.debug(LOG_TAG, "Creating AnnouncementsService.");
}
public boolean shouldFetchAnnouncements() {
final long now = System.currentTimeMillis();
if (!backgroundDataIsEnabled()) {
Logger.debug(LOG_TAG, "Background data not possible. Skipping.");
return false;
}
// Don't fetch if we were told to back off.
if (getEarliestNextFetch() > now) {
return false;
}
// Don't do anything if we haven't waited long enough.
final long lastFetch = getLastFetch();
// Just in case the alarm manager schedules us more frequently, or something
// goes awry with relaunches.
if ((now - lastFetch) < AnnouncementsConstants.MINIMUM_FETCH_INTERVAL_MSEC) {
Logger.debug(LOG_TAG, "Returning: minimum fetch interval of " + AnnouncementsConstants.MINIMUM_FETCH_INTERVAL_MSEC + "ms not met.");
return false;
}
return true;
}
/**
* Display the first valid announcement in the list.
*/
protected void processAnnouncements(final List<Announcement> announcements) {
if (announcements == null) {
Logger.warn(LOG_TAG, "No announcements to present.");
return;
}
boolean presented = false;
for (Announcement an : announcements) {
// Do this so we at least log, rather than just returning.
if (presented) {
Logger.warn(LOG_TAG, "Skipping announcement \"" + an.getTitle() + "\": one already shown.");
continue;
}
if (Announcement.isValidAnnouncement(an)) {
presented = true;
AnnouncementPresenter.displayAnnouncement(this, an);
}
}
}
/**
* If it's time to do a fetch -- we've waited long enough,
* we're allowed to use background data, etc. -- then issue
* a fetch. The subsequent background check is handled implicitly
* by the AlarmManager.
*/
@Override
public void onHandleIntent(Intent intent) {
Logger.setThreadLogTag(AnnouncementsConstants.GLOBAL_LOG_TAG);
// Intent can be null. Bug 1025937.
if (intent == null) {
Logger.debug(LOG_TAG, "Short-circuiting on null intent.");
return;
}
Logger.debug(LOG_TAG, "Running AnnouncementsService.");
if (AnnouncementsConstants.DISABLED) {
Logger.debug(LOG_TAG, "Announcements disabled. Returning from AnnouncementsService.");
return;
}
if (!shouldFetchAnnouncements()) {
Logger.debug(LOG_TAG, "Not fetching.");
return;
}
// Ensure that our locale is up to date, so that the fetcher's
// Accept-Language header is, too.
BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(getApplicationContext());
// Otherwise, grab our announcements URL and process the contents.
AnnouncementsFetcher.fetchAndProcessAnnouncements(getLastLaunch(), this);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
protected long getLastLaunch() {
return getSharedPreferences().getLong(AnnouncementsConstants.PREF_LAST_LAUNCH, 0);
}
protected SharedPreferences getSharedPreferences() {
return this.getSharedPreferences(AnnouncementsConstants.PREFS_BRANCH, GlobalConstants.SHARED_PREFERENCES_MODE);
}
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
super.dump(fd, writer, args);
final long lastFetch = getLastFetch();
final long lastLaunch = getLastLaunch();
writer.write("AnnouncementsService: last fetch " + lastFetch +
", last Firefox activity: " + lastLaunch + "\n");
}
protected void setEarliestNextFetch(final long earliestInMsec) {
this.getSharedPreferences().edit().putLong(AnnouncementsConstants.PREF_EARLIEST_NEXT_ANNOUNCE_FETCH, earliestInMsec).commit();
}
protected long getEarliestNextFetch() {
return this.getSharedPreferences().getLong(AnnouncementsConstants.PREF_EARLIEST_NEXT_ANNOUNCE_FETCH, 0L);
}
protected void setLastFetch(final long fetch) {
this.getSharedPreferences().edit().putLong(AnnouncementsConstants.PREF_LAST_FETCH_LOCAL_TIME, fetch).commit();
}
@Override
public long getLastFetch() {
return this.getSharedPreferences().getLong(AnnouncementsConstants.PREF_LAST_FETCH_LOCAL_TIME, 0L);
}
protected String setLastDate(final String fetch) {
if (fetch == null) {
this.getSharedPreferences().edit().remove(AnnouncementsConstants.PREF_LAST_FETCH_SERVER_DATE).commit();
return null;
}
this.getSharedPreferences().edit().putString(AnnouncementsConstants.PREF_LAST_FETCH_SERVER_DATE, fetch).commit();
return fetch;
}
@Override
public String getLastDate() {
return this.getSharedPreferences().getString(AnnouncementsConstants.PREF_LAST_FETCH_SERVER_DATE, null);
}
/**
* Use this to write the persisted server URL, overriding
* the default value.
* @param url a URI identifying the full request path, e.g.,
* "http://foo.com:1234/announce/"
*/
public void setAnnouncementsServerBaseURL(final URI url) {
if (url == null) {
throw new IllegalArgumentException("url cannot be null.");
}
final String scheme = url.getScheme();
if (scheme == null) {
throw new IllegalArgumentException("url must have a scheme.");
}
if (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https")) {
throw new IllegalArgumentException("url must be http or https.");
}
SharedPreferences p = this.getSharedPreferences();
p.edit().putString(AnnouncementsConstants.PREF_ANNOUNCE_SERVER_BASE_URL, url.toASCIIString()).commit();
}
/**
* Return the service URL, including protocol version and application identifier. E.g.,
*
* "https://campaigns.services.mozilla.com/announce/1/android/"
*/
@Override
public String getServiceURL() {
SharedPreferences p = this.getSharedPreferences();
String base = p.getString(AnnouncementsConstants.PREF_ANNOUNCE_SERVER_BASE_URL, AnnouncementsConstants.DEFAULT_ANNOUNCE_SERVER_BASE_URL);
return base + AnnouncementsConstants.ANNOUNCE_PATH_SUFFIX;
}
@Override
public Locale getLocale() {
return Locale.getDefault();
}
@Override
public String getUserAgent() {
return AnnouncementsConstants.USER_AGENT;
}
protected void persistTimes(long fetched, String date) {
setLastFetch(fetched);
if (date != null) {
setLastDate(date);
}
}
@Override
public void onNoNewAnnouncements(long fetched, String date) {
Logger.info(LOG_TAG, "No new announcements to display.");
persistTimes(fetched, date);
}
@Override
public void onNewAnnouncements(List<Announcement> announcements, long fetched, String date) {
Logger.info(LOG_TAG, "Processing announcements: " + announcements.size());
persistTimes(fetched, date);
processAnnouncements(announcements);
}
@Override
public void onRemoteFailure(int status) {
// Bump our fetch timestamp.
Logger.warn(LOG_TAG, "Got remote fetch status " + status + "; bumping fetch time.");
setLastFetch(System.currentTimeMillis());
}
@Override
public void onRemoteError(Exception e) {
// Bump our fetch timestamp.
Logger.warn(LOG_TAG, "Error processing response.", e);
setLastFetch(System.currentTimeMillis());
}
@Override
public void onLocalError(Exception e) {
Logger.error(LOG_TAG, "Got exception in fetch.", e);
// Do nothing yet, so we'll retry.
}
@Override
public void onBackoff(int retryAfterInSeconds) {
Logger.info(LOG_TAG, "Got retry after: " + retryAfterInSeconds);
final long delayInMsec = Math.max(retryAfterInSeconds * 1000, AnnouncementsConstants.DEFAULT_BACKOFF_MSEC);
final long fuzzedBackoffInMsec = delayInMsec + Math.round(((double) delayInMsec * 0.25d * Math.random()));
Logger.debug(LOG_TAG, "Fuzzed backoff: " + fuzzedBackoffInMsec + "ms.");
setEarliestNextFetch(fuzzedBackoffInMsec + System.currentTimeMillis());
}
}

View File

@ -45,7 +45,6 @@ public class GlobalConstants {
// Eventually Fennec might listen to startup notifications and
// do this automatically, but this will do for now. See Bug 800244.
public static String GECKO_PREFERENCES_CLASS = "org.mozilla.gecko.preferences.GeckoPreferences";
public static String GECKO_BROADCAST_ANNOUNCEMENTS_PREF_METHOD = "broadcastAnnouncementsPref";
public static String GECKO_BROADCAST_HEALTHREPORT_UPLOAD_PREF_METHOD = "broadcastHealthReportUploadPref";
public static String GECKO_BROADCAST_HEALTHREPORT_PRUNE_METHOD = "broadcastHealthReportPrune";

View File

@ -203,6 +203,7 @@ abstract class HomeFragment extends Fragment {
final GeckoApp geckoApp = (GeckoApp) context;
geckoApp.getButtonToast().show(false,
message,
ButtonToast.LENGTH_SHORT,
buttonMessage,
R.drawable.switch_button_icon,
new ButtonToast.ToastListener() {

View File

@ -411,7 +411,6 @@ gbjar.sources += [
'TouchEventInterceptor.java',
'updater/UpdateService.java',
'updater/UpdateServiceHelper.java',
'VideoPlayer.java',
'Webapp.java',
'webapp/Allocator.java',
'webapp/ApkResources.java',

View File

@ -32,7 +32,8 @@ public class ButtonToast {
@SuppressWarnings("unused")
private final static String LOGTAG = "GeckoButtonToast";
private final static int TOAST_DURATION = 5000;
public static int LENGTH_SHORT = 3000;
public static int LENGTH_LONG = 5000;
private final View mView;
private final TextView mMessageView;
@ -53,11 +54,14 @@ public class ButtonToast {
public final CharSequence buttonMessage;
public Drawable buttonDrawable;
public final CharSequence message;
public final int duration;
public ToastListener listener;
public Toast(CharSequence aMessage, CharSequence aButtonMessage,
Drawable aDrawable, ToastListener aListener) {
public Toast(CharSequence aMessage, int aDuration,
CharSequence aButtonMessage, Drawable aDrawable,
ToastListener aListener) {
message = aMessage;
duration = aDuration;
buttonMessage = aButtonMessage;
buttonDrawable = aDrawable;
listener = aListener;
@ -91,16 +95,16 @@ public class ButtonToast {
}
public void show(boolean immediate, CharSequence message,
CharSequence buttonMessage, int buttonDrawableId,
ToastListener listener) {
int duration, CharSequence buttonMessage,
int buttonDrawableId, ToastListener listener) {
final Drawable d = mView.getContext().getResources().getDrawable(buttonDrawableId);
show(false, message, buttonMessage, d, listener);
show(false, message, duration, buttonMessage, d, listener);
}
public void show(boolean immediate, CharSequence message,
CharSequence buttonMessage, Drawable buttonDrawable,
ToastListener listener) {
show(new Toast(message, buttonMessage, buttonDrawable, listener), immediate);
int duration, CharSequence buttonMessage,
Drawable buttonDrawable, ToastListener listener) {
show(new Toast(message, duration, buttonMessage, buttonDrawable, listener), immediate);
}
private void show(Toast t, boolean immediate) {
@ -119,7 +123,7 @@ public class ButtonToast {
mButton.setCompoundDrawablesWithIntrinsicBounds(t.buttonDrawable, null, null, null);
mHideHandler.removeCallbacks(mHideRunnable);
mHideHandler.postDelayed(mHideRunnable, TOAST_DURATION);
mHideHandler.postDelayed(mHideRunnable, t.duration);
mView.setVisibility(View.VISIBLE);
int duration = immediate ? 0 : mView.getResources().getInteger(android.R.integer.config_longAnimTime);

View File

@ -83,7 +83,7 @@ let WebappRT = {
let apps = request.result;
for (let i = 0; i < apps.length; i++) {
let app = apps[i];
let manifest = new ManifestHelper(app.manifest, app.origin, app.manifestURL);
let manifest = new ManifestHelper(app.manifest, app.origin);
// if this is a path to the manifest, or the launch path, then we have a hit.
if (app.manifestURL == aUrl || manifest.fullLaunchPath() == aUrl) {

View File

@ -105,7 +105,7 @@ function updateList() {
function addApplication(aApp) {
let list = document.getElementById("appgrid");
let manifest = new ManifestHelper(aApp.manifest, aApp.origin, aApp.manifestURL);
let manifest = new ManifestHelper(aApp.manifest, aApp.origin);
let container = document.createElement("div");
container.className = "app list-item";

View File

@ -179,13 +179,12 @@ this.WebappManager = {
DOMApplicationRegistry.registryReady.then(() => {
DOMApplicationRegistry.confirmInstall(aData, file, (function(aApp, aManifest) {
this._postInstall(aData.profilePath, aManifest, aData.app.origin,
aData.app.apkPackageName, aData.app.manifestURL);
this._postInstall(aData.profilePath, aManifest, aData.app.origin, aData.app.apkPackageName);
}).bind(this));
});
},
_postInstall: function(aProfilePath, aNewManifest, aOrigin, aApkPackageName, aManifestURL) {
_postInstall: function(aProfilePath, aNewManifest, aOrigin, aApkPackageName) {
// aOrigin may now point to the app: url that hosts this app.
sendMessageToJava({
type: "Webapps:Postinstall",
@ -195,7 +194,7 @@ this.WebappManager = {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
file.initWithPath(aProfilePath);
let localeManifest = new ManifestHelper(aNewManifest, aOrigin, aManifestUrl);
let localeManifest = new ManifestHelper(aNewManifest, aOrigin);
this.writeDefaultPrefs(file, localeManifest);
},
@ -334,7 +333,7 @@ this.WebappManager = {
yield this._autoUpdatePackagedApp(aData, aOldApp);
}
this._postInstall(aData.profilePath, aData.manifest, aOldApp.origin, aOldApp.apkPackageName, aOldApp.manifestURL);
this._postInstall(aData.profilePath, aData.manifest, aOldApp.origin, aOldApp.apkPackageName);
}).bind(this)); },
_autoUpdatePackagedApp: Task.async(function*(aData, aOldApp) {

View File

@ -0,0 +1,25 @@
<!-- 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/. -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<padding
android:bottom="@dimen/card_background_padding_y"
android:top="@dimen/card_background_padding_y"
android:left="@dimen/card_background_padding_x"
android:right="@dimen/card_background_padding_x"/>
<solid android:color="@android:color/transparent"/>
</shape>
</item>
<!-- Background -->
<item>
<shape>
<solid android:color="@color/card_background"/>
<corners android:radius="5dp"/>
<stroke android:width="1dp" android:color="@color/card_border" />
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,25 @@
<!-- 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/. -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<padding
android:bottom="@dimen/card_background_padding_y"
android:top="@dimen/card_background_padding_y"
android:left="@dimen/card_background_padding_x"
android:right="@dimen/card_background_padding_x"/>
<solid android:color="@android:color/transparent"/>
</shape>
</item>
<!-- Background -->
<item>
<shape>
<solid android:color="@color/card_background_pressed"/>
<corners android:radius="5dp"/>
<stroke android:width="1dp" android:color="@color/card_border" />
</shape>
</item>
</layer-list>

View File

@ -1,17 +0,0 @@
<!--
As well as these system actions, we also listen for pref notifications
sent by Fennec: @ANDROID_PACKAGE_NAME@.ANNOUNCEMENTS_PREF.
-->
<receiver android:name="org.mozilla.gecko.background.announcements.AnnouncementsBroadcastReceiver" >
<intent-filter>
<!-- Startup. -->
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<!-- SD card remounted. -->
<action android:name="android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE" />
</intent-filter>
<intent-filter >
<action android:name="@ANDROID_PACKAGE_NAME@.ANNOUNCEMENTS_PREF" />
</intent-filter>
</receiver>

View File

@ -1,5 +0,0 @@
<!-- So we can start our service. -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- So we can receive messages from Fennec. -->
<uses-permission android:name="@ANDROID_PACKAGE_NAME@.permission.PER_ANDROID_PACKAGE" />

View File

@ -1,8 +0,0 @@
<service
android:exported="false"
android:name="org.mozilla.gecko.background.announcements.AnnouncementsService" >
</service>
<service
android:exported="false"
android:name="org.mozilla.gecko.background.announcements.AnnouncementsBroadcastService" >
</service>

View File

@ -5,7 +5,6 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
background_junit3_sources = [
'src/announcements/TestAnnouncementsBroadcastService.java',
'src/common/TestAndroidLogWriters.java',
'src/common/TestBrowserContractHelpers.java',
'src/common/TestDateUtils.java',

View File

@ -1,99 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.announcements;
import java.util.concurrent.BrokenBarrierException;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.helpers.BackgroundServiceTestCase;
import android.content.Intent;
import android.content.SharedPreferences;
public class TestAnnouncementsBroadcastService
extends BackgroundServiceTestCase<TestAnnouncementsBroadcastService.MockAnnouncementsBroadcastService> {
public static class MockAnnouncementsBroadcastService extends AnnouncementsBroadcastService {
@Override
protected SharedPreferences getSharedPreferences() {
return this.getSharedPreferences(sharedPrefsName,
GlobalConstants.SHARED_PREFERENCES_MODE);
}
@Override
protected void onHandleIntent(Intent intent) {
super.onHandleIntent(intent);
try {
barrier.await();
} catch (InterruptedException e) {
fail("Awaiting thread should not be interrupted.");
} catch (BrokenBarrierException e) {
// This will happen on timeout - do nothing.
}
}
}
public TestAnnouncementsBroadcastService() {
super(MockAnnouncementsBroadcastService.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
// We can't mock AlarmManager since it has a package-private constructor, so instead we reset
// the alarm by hand.
cancelAlarm(getServiceIntent());
}
@Override
public void tearDown() throws Exception {
cancelAlarm(getServiceIntent());
AnnouncementsConstants.DISABLED = false;
super.tearDown();
}
protected Intent getServiceIntent() {
final Intent intent = new Intent(getContext(), AnnouncementsService.class);
return intent;
}
public void testIgnoredServicePrefIntents() throws Exception {
// Intent without "enabled" extra is ignored.
intent.setAction(AnnouncementsConstants.ACTION_ANNOUNCEMENTS_PREF);
startService(intent);
await();
assertFalse(isServiceAlarmSet(getServiceIntent()));
}
public void testServicePrefIntentDisabled() throws Exception {
intent.setAction(AnnouncementsConstants.ACTION_ANNOUNCEMENTS_PREF)
.putExtra("enabled", false);
startService(intent);
await();
assertFalse(isServiceAlarmSet(getServiceIntent()));
}
public void testServicePrefIntentEnabled() throws Exception {
intent.setAction(AnnouncementsConstants.ACTION_ANNOUNCEMENTS_PREF)
.putExtra("enabled", true);
startService(intent);
await();
assertTrue(isServiceAlarmSet(getServiceIntent()));
}
public void testServicePrefCancelled() throws Exception {
intent.setAction(AnnouncementsConstants.ACTION_ANNOUNCEMENTS_PREF)
.putExtra("enabled", true);
startService(intent);
await();
assertTrue(isServiceAlarmSet(getServiceIntent()));
barrier.reset();
intent.putExtra("enabled", false);
startService(intent);
await();
assertFalse(isServiceAlarmSet(getServiceIntent()));
}
}

View File

@ -184,6 +184,23 @@ interface nsISearchEngine : nsISupports
AString getResultDomain([optional] in AString responseType);
};
[scriptable, uuid(856a31ff-b451-4101-b12e-ff399485ac8a)]
interface nsISearchParseSubmissionResult : nsISupports
{
/**
* The search engine associated with the URL passed in to
* nsISearchEngine::parseSubmissionURL, or null if the URL does not represent
* a search submission.
*/
readonly attribute nsISearchEngine engine;
/**
* String containing the sought terms. This can be an empty string in case no
* terms were specified or the URL does not represent a search submission.
*/
readonly attribute AString terms;
};
[scriptable, uuid(9fc39136-f08b-46d3-b232-96f4b7b0e235)]
interface nsISearchInstallCallback : nsISupports
{
@ -409,6 +426,22 @@ interface nsIBrowserSearchService : nsISupports
* search engines.
*/
attribute nsISearchEngine currentEngine;
/**
* Determines if the provided URL represents results from a search engine, and
* provides details about the match.
*
* The lookup mechanism checks whether the domain name and path of the
* provided HTTP or HTTPS URL matches one of the known values for the visible
* search engines. The match does not depend on which of the schemes is used.
* The expected URI parameter for the search terms must exist in the query
* string, but other parameters are ignored.
*
* @param url
* String containing the URL to parse, for example
* "https://www.google.com/search?q=terms".
*/
nsISearchParseSubmissionResult parseSubmissionURL(in AString url);
};
%{ C++

View File

@ -0,0 +1,43 @@
/* 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/. */
/*
* This module contains additional data about default search engines that is the
* same across all languages. This information is defined outside of the actual
* search engine definition files, so that localizers don't need to update them
* when a change is made.
*
* This separate module is also easily overridable, in case a hotfix is needed.
* No high-level processing logic is applied here.
*/
"use strict";
this.EXPORTED_SYMBOLS = [
"SearchStaticData",
];
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
// To update this list of known alternate domains, just cut-and-paste from
// https://www.google.com/supported_domains
const gGoogleDomainsSource = ".google.com .google.ad .google.ae .google.com.af .google.com.ag .google.com.ai .google.al .google.am .google.co.ao .google.com.ar .google.as .google.at .google.com.au .google.az .google.ba .google.com.bd .google.be .google.bf .google.bg .google.com.bh .google.bi .google.bj .google.com.bn .google.com.bo .google.com.br .google.bs .google.bt .google.co.bw .google.by .google.com.bz .google.ca .google.cd .google.cf .google.cg .google.ch .google.ci .google.co.ck .google.cl .google.cm .google.cn .google.com.co .google.co.cr .google.com.cu .google.cv .google.com.cy .google.cz .google.de .google.dj .google.dk .google.dm .google.com.do .google.dz .google.com.ec .google.ee .google.com.eg .google.es .google.com.et .google.fi .google.com.fj .google.fm .google.fr .google.ga .google.ge .google.gg .google.com.gh .google.com.gi .google.gl .google.gm .google.gp .google.gr .google.com.gt .google.gy .google.com.hk .google.hn .google.hr .google.ht .google.hu .google.co.id .google.ie .google.co.il .google.im .google.co.in .google.iq .google.is .google.it .google.je .google.com.jm .google.jo .google.co.jp .google.co.ke .google.com.kh .google.ki .google.kg .google.co.kr .google.com.kw .google.kz .google.la .google.com.lb .google.li .google.lk .google.co.ls .google.lt .google.lu .google.lv .google.com.ly .google.co.ma .google.md .google.me .google.mg .google.mk .google.ml .google.com.mm .google.mn .google.ms .google.com.mt .google.mu .google.mv .google.mw .google.com.mx .google.com.my .google.co.mz .google.com.na .google.com.nf .google.com.ng .google.com.ni .google.ne .google.nl .google.no .google.com.np .google.nr .google.nu .google.co.nz .google.com.om .google.com.pa .google.com.pe .google.com.pg .google.com.ph .google.com.pk .google.pl .google.pn .google.com.pr .google.ps .google.pt .google.com.py .google.com.qa .google.ro .google.ru .google.rw .google.com.sa .google.com.sb .google.sc .google.se .google.com.sg .google.sh .google.si .google.sk .google.com.sl .google.sn .google.so .google.sm .google.sr .google.st .google.com.sv .google.td .google.tg .google.co.th .google.com.tj .google.tk .google.tl .google.tm .google.tn .google.to .google.com.tr .google.tt .google.com.tw .google.co.tz .google.com.ua .google.co.ug .google.co.uk .google.com.uy .google.co.uz .google.com.vc .google.co.ve .google.vg .google.co.vi .google.com.vn .google.vu .google.ws .google.rs .google.co.za .google.co.zm .google.co.zw .google.cat";
const gGoogleDomains = gGoogleDomainsSource.split(" ").map(d => "www" + d);
this.SearchStaticData = {
/**
* Returns a list of alternate domains for a given search engine domain.
*
* @param aDomain
* Lowercase host name to look up. For example, if this argument is
* "www.google.com" or "www.google.co.uk", the function returns the
* full list of supported Google domains.
*
* @return Array containing one entry for each alternate host name, or empty
* array if none is known. The returned array should not be modified.
*/
getAlternateDomains: function (aDomain) {
return gGoogleDomains.indexOf(aDomain) == -1 ? [] : gGoogleDomains;
},
};

View File

@ -19,6 +19,10 @@ EXTRA_PP_COMPONENTS += [
'nsSearchService.js',
]
EXTRA_JS_MODULES += [
'SearchStaticData.jsm',
]
DEFINES['MOZ_DISTRIBUTION_ID'] = CONFIG['MOZ_DISTRIBUTION_ID']
if CONFIG['MOZ_BUILD_APP'] == 'mobile/android':

View File

@ -23,6 +23,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
"resource://gre/modules/TelemetryStopwatch.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
"resource://gre/modules/Deprecated.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SearchStaticData",
"resource://gre/modules/SearchStaticData.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gTextToSubURI",
"@mozilla.org/intl/texttosuburi;1",
"nsITextToSubURI");
// A text encoder to UTF8, used whenever we commit the
// engine metadata to disk.
@ -904,6 +910,11 @@ EngineURL.prototype = {
return new Submission(makeURI(url), postData);
},
_getTermsParameterName: function SRCH_EURL__getTermsParameterName() {
let queryParam = this.params.find(p => p.value == USER_DEFINED);
return queryParam ? queryParam.name : "";
},
_hasRelation: function SRC_EURL__hasRelation(aRel)
this.rels.some(function(e) e == aRel.toLowerCase()),
@ -2459,7 +2470,7 @@ Engine.prototype = {
}
return null;
},
// The file that the plugin is loaded from is a unique identifier for it. We
// use this as the identifier to store data in the sqlite database
__id: null,
@ -2641,14 +2652,12 @@ Engine.prototype = {
}
LOG("getSubmission: In data: \"" + aData + "\"; Purpose: \"" + aPurpose + "\"");
var textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
getService(Ci.nsITextToSubURI);
var data = "";
try {
data = textToSubURI.ConvertAndEscape(this.queryCharset, aData);
data = gTextToSubURI.ConvertAndEscape(this.queryCharset, aData);
} catch (ex) {
LOG("getSubmission: Falling back to default queryCharset!");
data = textToSubURI.ConvertAndEscape(DEFAULT_QUERY_CHARSET, aData);
data = gTextToSubURI.ConvertAndEscape(DEFAULT_QUERY_CHARSET, aData);
}
LOG("getSubmission: Out data: \"" + data + "\"");
return url.getSubmission(data, this, aPurpose);
@ -2678,6 +2687,36 @@ Engine.prototype = {
return "";
},
/**
* Returns URL parsing properties used by _buildParseSubmissionMap.
*/
getURLParsingInfo: function () {
#ifdef ANDROID
let responseType = this._defaultMobileResponseType;
#else
let responseType = URLTYPE_SEARCH_HTML;
#endif
LOG("getURLParsingInfo: responseType: \"" + responseType + "\"");
let url = this._getURLOfType(responseType);
if (!url || url.method != "GET") {
return null;
}
let termsParameterName = url._getTermsParameterName();
if (!termsParameterName) {
return null;
}
let templateUrl = NetUtil.newURI(url.template).QueryInterface(Ci.nsIURL);
return {
mainDomain: templateUrl.host,
path: templateUrl.filePath.toLowerCase(),
termsParameterName: termsParameterName,
};
},
// nsISupports
QueryInterface: function SRCH_ENG_QI(aIID) {
if (aIID.equals(Ci.nsISearchEngine) ||
@ -2789,6 +2828,24 @@ Submission.prototype = {
}
}
// nsISearchParseSubmissionResult
function ParseSubmissionResult(aEngine, aTerms) {
this._engine = aEngine;
this._terms = aTerms;
}
ParseSubmissionResult.prototype = {
get engine() {
return this._engine;
},
get terms() {
return this._terms;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsISearchParseSubmissionResult]),
}
const gEmptyParseSubmissionResult =
Object.freeze(new ParseSubmissionResult(null, ""));
function executeSoon(func) {
Services.tm.mainThread.dispatch(func, Ci.nsIThread.DISPATCH_NORMAL);
}
@ -3199,7 +3256,7 @@ SearchService.prototype = {
this.__sortedEngines = null;
// Typically we'll re-init as a result of a pref observer,
// so signal to 'callers' that we're done.
// so signal to 'callers' that we're done.
return this._asyncLoadEngines()
.then(() => {
Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "reinit-complete");
@ -3313,7 +3370,7 @@ SearchService.prototype = {
// Schedule the engine's next update, if it isn't already.
if (!engineMetadataService.getAttr(aEngine, "updateexpir"))
engineUpdateService.scheduleNextUpdate(aEngine);
// We need to save the engine's _dataType, if this is the first time the
// engine is added to the dataStore, since ._dataType isn't persisted
// and will change on the next startup (since the engine will then be
@ -3533,7 +3590,7 @@ SearchService.prototype = {
names.forEach(function (n) uris.push(root + n + ".xml"));
});
return [chromeFiles, uris];
},
@ -3642,7 +3699,7 @@ SearchService.prototype = {
if (getBoolPref(BROWSER_SEARCH_PREF + "useDBForOrder", false)) {
LOG("_buildSortedEngineList: using db for order");
// Flag to keep track of whether or not we need to call _saveSortedEngineList.
// Flag to keep track of whether or not we need to call _saveSortedEngineList.
let needToSaveEngineList = false;
for each (engine in this._engines) {
@ -3700,7 +3757,7 @@ SearchService.prototype = {
engine = this._engines[engineName];
if (!engine || engine.name in addedEngines)
continue;
this.__sortedEngines.push(engine);
addedEngines[engine.name] = engine;
}
@ -3889,8 +3946,6 @@ SearchService.prototype = {
engine._initFromMetadata(aName, aIconURL, aAlias, aDescription,
aMethod, aTemplate);
this._addEngineToStore(engine);
this.batchTask.disarm();
this.batchTask.arm();
},
addEngine: function SRCH_SVC_addEngine(aEngineURL, aDataType, aIconURL,
@ -4000,7 +4055,7 @@ SearchService.prototype = {
// need to adjust aNewIndex accordingly. To do this, we count the number
// of hidden engines in the list before the engine that we're taking the
// place of. We do this by first finding newIndexEngine (the engine that
// we were supposed to replace) and then iterating through the complete
// we were supposed to replace) and then iterating through the complete
// engine list until we reach it, increasing aNewIndex for each hidden
// engine we find on our way there.
//
@ -4141,6 +4196,151 @@ SearchService.prototype = {
notifyAction(this._currentEngine, SEARCH_ENGINE_CURRENT);
},
/**
* This map is built lazily after the available search engines change. It
* allows quick parsing of an URL representing a search submission into the
* search engine name and original terms.
*
* The keys are strings containing the domain name and lowercase path of the
* engine submission, for example "www.google.com/search".
*
* The values are objects with these properties:
* {
* engine: The associated nsISearchEngine.
* termsParameterName: Name of the URL parameter containing the search
* terms, for example "q".
* }
*/
_parseSubmissionMap: null,
_buildParseSubmissionMap: function SRCH_SVC__buildParseSubmissionMap() {
LOG("_buildParseSubmissionMap");
this._parseSubmissionMap = new Map();
// Used only while building the map, indicates which entries do not refer to
// the main domain of the engine but to an alternate domain, for example
// "www.google.fr" for the "www.google.com" search engine.
let keysOfAlternates = new Set();
for (let engine of this._sortedEngines) {
LOG("Processing engine: " + engine.name);
if (engine.hidden) {
LOG("Engine is hidden.");
continue;
}
let urlParsingInfo = engine.getURLParsingInfo();
if (!urlParsingInfo) {
LOG("Engine does not support URL parsing.");
continue;
}
// Store the same object on each matching map key, as an optimization.
let mapValueForEngine = {
engine: engine,
termsParameterName: urlParsingInfo.termsParameterName,
};
let processDomain = (domain, isAlternate) => {
let key = domain + urlParsingInfo.path;
// Apply the logic for which main domains take priority over alternate
// domains, even if they are found later in the ordered engine list.
let existingEntry = this._parseSubmissionMap.get(key);
if (!existingEntry) {
LOG("Adding new entry: " + key);
if (isAlternate) {
keysOfAlternates.add(key);
}
} else if (!isAlternate && keysOfAlternates.has(key)) {
LOG("Overriding alternate entry: " + key +
" (" + existingEntry.engine.name + ")");
keysOfAlternates.delete(key);
} else {
LOG("Keeping existing entry: " + key +
" (" + existingEntry.engine.name + ")");
return;
}
this._parseSubmissionMap.set(key, mapValueForEngine);
};
processDomain(urlParsingInfo.mainDomain, false);
SearchStaticData.getAlternateDomains(urlParsingInfo.mainDomain)
.forEach(d => processDomain(d, true));
}
},
parseSubmissionURL: function SRCH_SVC_parseSubmissionURL(aURL) {
this._ensureInitialized();
LOG("parseSubmissionURL: Parsing \"" + aURL + "\".");
if (!this._parseSubmissionMap) {
this._buildParseSubmissionMap();
}
// Extract the elements of the provided URL first.
let soughtKey, soughtQuery;
try {
let soughtUrl = NetUtil.newURI(aURL).QueryInterface(Ci.nsIURL);
// Exclude any URL that is not HTTP or HTTPS from the beginning.
if (soughtUrl.scheme != "http" && soughtUrl.scheme != "https") {
LOG("The URL scheme is not HTTP or HTTPS.");
return gEmptyParseSubmissionResult;
}
// Reading these URL properties may fail and raise an exception.
soughtKey = soughtUrl.host + soughtUrl.filePath.toLowerCase();
soughtQuery = soughtUrl.query;
} catch (ex) {
// Errors while parsing the URL or accessing the properties are not fatal.
LOG("The value does not look like a structured URL.");
return gEmptyParseSubmissionResult;
}
// Look up the domain and path in the map to identify the search engine.
let mapEntry = this._parseSubmissionMap.get(soughtKey);
if (!mapEntry) {
LOG("No engine associated with domain and path: " + soughtKey);
return gEmptyParseSubmissionResult;
}
// Extract the search terms from the parameter, for example "caff%C3%A8"
// from the URL "https://www.google.com/search?q=caff%C3%A8&client=firefox".
let encodedTerms = null;
for (let param of soughtQuery.split("&")) {
let equalPos = param.indexOf("=");
if (equalPos != -1 &&
param.substr(0, equalPos) == mapEntry.termsParameterName) {
// This is the parameter we are looking for.
encodedTerms = param.substr(equalPos + 1);
break;
}
}
if (encodedTerms === null) {
LOG("Missing terms parameter: " + mapEntry.termsParameterName);
return gEmptyParseSubmissionResult;
}
// Decode the terms using the charset defined in the search engine.
let terms;
try {
terms = gTextToSubURI.UnEscapeAndConvert(
mapEntry.engine.queryCharset,
encodedTerms.replace("+", " "));
} catch (ex) {
// Decoding errors will cause this match to be ignored.
LOG("Parameter decoding failed. Charset: " +
mapEntry.engine.queryCharset);
return gEmptyParseSubmissionResult;
}
LOG("Match found. Terms: " + terms);
return new ParseSubmissionResult(mapEntry.engine, terms);
},
// nsIObserver
observe: function SRCH_SVC_observe(aEngine, aTopic, aVerb) {
switch (aTopic) {
@ -4155,13 +4355,16 @@ SearchService.prototype = {
LOG("nsSearchService::observe: setting current");
this.currentEngine = aEngine;
}
this.batchTask.disarm();
this.batchTask.arm();
// The addition of the engine to the store always triggers an ADDED
// or a CHANGED notification, that will trigger the task below.
break;
case SEARCH_ENGINE_ADDED:
case SEARCH_ENGINE_CHANGED:
case SEARCH_ENGINE_REMOVED:
this.batchTask.disarm();
this.batchTask.arm();
// Invalidate the map used to parse URLs to search engines.
this._parseSubmissionMap = null;
break;
}
break;
@ -4591,7 +4794,7 @@ var engineUpdateService = {
let testEngine = null;
let updateURL = engine._getURLOfType(URLTYPE_OPENSEARCH);
let updateURI = (updateURL && updateURL._hasRelation("self")) ?
let updateURI = (updateURL && updateURL._hasRelation("self")) ?
updateURL.getSubmission("", engine).uri :
makeURI(engine._updateURL);
if (updateURI) {

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
<ShortName>Test search engine (fr)</ShortName>
<Description>A test search engine (based on Google search for a different locale)</Description>
<InputEncoding>ISO-8859-1</InputEncoding>
<Url type="text/html" method="GET" template="http://www.google.fr/search" resultdomain="google.fr">
<Param name="q" value="{searchTerms}"/>
<Param name="ie" value="iso-8859-1"/>
<Param name="oe" value="iso-8859-1"/>
</Url>
<SearchForm>http://www.google.fr/</SearchForm>
</SearchPlugin>

View File

@ -0,0 +1,20 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* Tests the SearchStaticData module.
*/
"use strict";
Cu.import("resource://gre/modules/SearchStaticData.jsm", this);
function run_test() {
do_check_true(SearchStaticData.getAlternateDomains("www.google.com")
.indexOf("www.google.fr") != -1);
do_check_true(SearchStaticData.getAlternateDomains("www.google.fr")
.indexOf("www.google.com") != -1);
do_check_true(SearchStaticData.getAlternateDomains("www.google.com")
.every(d => d.startsWith("www.google.")));
do_check_true(SearchStaticData.getAlternateDomains("google.com").length == 0);
}

View File

@ -0,0 +1,110 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* Tests getAlternateDomains API.
*/
"use strict";
function run_test() {
removeMetadata();
updateAppInfo();
useHttpServer();
run_next_test();
}
add_task(function* test_parseSubmissionURL() {
// Hide the default engines to prevent them from being used in the search.
for (let engine of Services.search.getEngines()) {
Services.search.removeEngine(engine);
}
let [engine1, engine2, engine3] = yield addTestEngines([
{ name: "Test search engine", xmlFileName: "engine.xml" },
{ name: "Test search engine (fr)", xmlFileName: "engine-fr.xml" },
{ name: "bacon_addParam", details: ["", "bacon_addParam", "Search Bacon",
"GET", "http://www.bacon.test/find"] },
// The following engines cannot identify the search parameter.
{ name: "A second test engine", xmlFileName: "engine2.xml" },
{ name: "Sherlock test search engine", srcFileName: "engine.src",
iconFileName: "ico-size-16x16-png.ico" },
{ name: "bacon", details: ["", "bacon", "Search Bacon", "GET",
"http://www.bacon.moz/search?q={searchTerms}"] },
]);
engine3.addParam("q", "{searchTerms}", null);
// Test the first engine, whose URLs use UTF-8 encoding.
let result = Services.search.parseSubmissionURL(
"http://www.google.com/search?q=caff%C3%A8");
do_check_eq(result.engine, engine1);
do_check_eq(result.terms, "caff\u00E8");
// The second engine uses a locale-specific domain that is an alternate domain
// of the first one, but the second engine should get priority when matching.
// The URL used with this engine uses ISO-8859-1 encoding instead.
let result = Services.search.parseSubmissionURL(
"http://www.google.fr/search?q=caff%E8");
do_check_eq(result.engine, engine2);
do_check_eq(result.terms, "caff\u00E8");
// Test a domain that is an alternate domain of those defined. In this case,
// the first matching engine from the ordered list should be returned.
let result = Services.search.parseSubmissionURL(
"http://www.google.co.uk/search?q=caff%C3%A8");
do_check_eq(result.engine, engine1);
do_check_eq(result.terms, "caff\u00E8");
// We support parsing URLs from a dynamically added engine. Those engines use
// windows-1252 encoding by default.
let result = Services.search.parseSubmissionURL(
"http://www.bacon.test/find?q=caff%E8");
do_check_eq(result.engine, engine3);
do_check_eq(result.terms, "caff\u00E8");
// Parsing of parameters from an engine template URL is not supported.
do_check_eq(Services.search.parseSubmissionURL(
"http://www.bacon.moz/search?q=").engine, null);
do_check_eq(Services.search.parseSubmissionURL(
"https://duckduckgo.com?q=test").engine, null);
do_check_eq(Services.search.parseSubmissionURL(
"https://duckduckgo.com/?q=test").engine, null);
// Sherlock engines are not supported.
do_check_eq(Services.search.parseSubmissionURL(
"http://getfirefox.com?q=test").engine, null);
do_check_eq(Services.search.parseSubmissionURL(
"http://getfirefox.com/?q=test").engine, null);
// HTTP and HTTPS schemes are interchangeable.
let result = Services.search.parseSubmissionURL(
"https://www.google.com/search?q=caff%C3%A8");
do_check_eq(result.engine, engine1);
do_check_eq(result.terms, "caff\u00E8");
// An empty query parameter should work the same.
let result = Services.search.parseSubmissionURL(
"http://www.google.com/search?q=");
do_check_eq(result.engine, engine1);
do_check_eq(result.terms, "");
// There should be no match when the path is different.
let result = Services.search.parseSubmissionURL(
"http://www.google.com/search/?q=test");
do_check_eq(result.engine, null);
do_check_eq(result.terms, "");
// There should be no match when the argument is different.
let result = Services.search.parseSubmissionURL(
"http://www.google.com/search?q2=test");
do_check_eq(result.engine, null);
do_check_eq(result.terms, "");
// There should be no match for URIs that are not HTTP or HTTPS.
let result = Services.search.parseSubmissionURL(
"file://localhost/search?q=test");
do_check_eq(result.engine, null);
do_check_eq(result.terms, "");
});

View File

@ -7,6 +7,7 @@ support-files =
data/engine.src
data/engine.xml
data/engine2.xml
data/engine-fr.xml
data/engineMaker.sjs
data/engine-rel-searchform.xml
data/engine-rel-searchform-post.xml
@ -33,6 +34,8 @@ support-files =
[test_defaultEngine.js]
[test_prefSync.js]
[test_notifications.js]
[test_parseSubmissionURL.js]
[test_SearchStaticData.js]
[test_addEngine_callback.js]
[test_multipleIcons.js]
[test_resultDomain.js]

View File

@ -283,8 +283,7 @@ WebappsActor.prototype = {
// We can't have appcache for packaged apps.
if (!aApp.origin.startsWith("app://")) {
reg.startOfflineCacheDownload(
new ManifestHelper(manifest, aApp.origin, aApp.manifestURL));
reg.startOfflineCacheDownload(new ManifestHelper(manifest, aApp.origin));
}
});
// Cleanup by removing the temporary directory.
@ -748,7 +747,7 @@ WebappsActor.prototype = {
let deferred = promise.defer();
this._findManifestByURL(manifestURL).then(jsonManifest => {
let manifest = new ManifestHelper(jsonManifest, app.origin, manifestURL);
let manifest = new ManifestHelper(jsonManifest, app.origin);
let iconURL = manifest.iconURLForSize(aRequest.size || 128);
if (!iconURL) {
deferred.resolve({

View File

@ -725,7 +725,9 @@ WebConsoleActor.prototype =
bindObjectActor: aRequest.bindObjectActor,
frameActor: aRequest.frameActor,
url: aRequest.url,
selectedNodeActor: aRequest.selectedNodeActor,
};
let evalInfo = this.evalWithDebugger(input, evalOptions);
let evalResult = evalInfo.result;
let helperResult = evalInfo.helperResult;
@ -967,6 +969,10 @@ WebConsoleActor.prototype =
* ObjectActor.
* - frameActor: the FrameActor ID to use for evaluation. The given
* debugger frame is used for evaluation, instead of the global window.
* - selectedNodeActor: the NodeActor ID of the currently selected node
* in the Inspector (or null, if there is no selection). This is used
* for helper functions that make reference to the currently selected
* node, like $0.
* @return object
* An object that holds the following properties:
* - dbg: the debugger where the string was evaluated.
@ -1031,6 +1037,13 @@ WebConsoleActor.prototype =
bindings._self = bindSelf;
}
if (aOptions.selectedNodeActor) {
let actor = this.conn.getActor(aOptions.selectedNodeActor);
if (actor) {
helpers.selectedNode = actor.rawNode;
}
}
// Check if the Debugger.Frame or Debugger.Object for the global include
// $ or $$. We will not overwrite these functions with the jsterm helpers.
let found$ = false, found$$ = false;
@ -1075,6 +1088,7 @@ WebConsoleActor.prototype =
let helperResult = helpers.helperResult;
delete helpers.evalInput;
delete helpers.helperResult;
delete helpers.selectedNode;
if ($) {
bindings.$ = $;

View File

@ -1107,6 +1107,11 @@ let Front = Class({
let deferred = this._requests.shift();
if (packet.error) {
// "Protocol error" is here to avoid TBPL heuristics. See also
// https://mxr.mozilla.org/webtools-central/source/tbpl/php/inc/GeneralErrorFilter.php
let message = (packet.error == "unknownError" && packet.message) ?
"Protocol error: " + packet.message :
packet.error;
deferred.reject(packet.error);
} else {
deferred.resolve(packet);

View File

@ -104,6 +104,11 @@ WebConsoleClient.prototype = {
*
* - url: the url to evaluate the script as. Defaults to
* "debugger eval code".
*
* - selectedNodeActor: the NodeActor ID of the current selection in the
* Inspector, if such a selection exists. This is used by helper functions
* that can reference the currently selected node in the Inspector, like
* $0.
*/
evaluateJS: function WCC_evaluateJS(aString, aOnResponse, aOptions = {})
{
@ -114,6 +119,7 @@ WebConsoleClient.prototype = {
bindObjectActor: aOptions.bindObjectActor,
frameActor: aOptions.frameActor,
url: aOptions.url,
selectedNodeActor: aOptions.selectedNodeActor,
};
this._client.request(packet, aOnResponse);
},

View File

@ -1563,38 +1563,12 @@ function JSTermHelpers(aOwner)
/**
* Returns the currently selected object in the highlighter.
*
* TODO: this implementation crosses the client/server boundaries! This is not
* usable within a remote browser. To implement this feature correctly we need
* support for remote inspection capabilities within the Inspector as well.
* See bug 787975.
*
* @return nsIDOMElement|null
* The DOM element currently selected in the highlighter.
* @return Object representing the current selection in the
* Inspector, or null if no selection exists.
*/
Object.defineProperty(aOwner.sandbox, "$0", {
Object.defineProperty(aOwner.sandbox, "$0", {
get: function() {
let window = aOwner.chromeWindow();
if (!window) {
return null;
}
let target = null;
try {
target = devtools.TargetFactory.forTab(window.gBrowser.selectedTab);
}
catch (ex) {
// If we report this exception the user will get it in the Browser
// Console every time when she evaluates any string.
}
if (!target) {
return null;
}
let toolbox = gDevTools.getToolbox(target);
let node = toolbox && toolbox.selection ? toolbox.selection.node : null;
return node ? aOwner.makeDebuggeeValue(node) : null;
return aOwner.makeDebuggeeValue(aOwner.selectedNode)
},
enumerable: true,
configurable: false

View File

@ -40,6 +40,44 @@ function getDocumentURI(doc) {
return doc.documentURI.replace(/#.*$/, "");
}
/**
* Returns whether the given value is a valid credit card number based on
* the Luhn algorithm. See https://en.wikipedia.org/wiki/Luhn_algorithm.
*/
function isValidCCNumber(value) {
// Remove dashes and whitespace.
let ccNumber = value.replace(/[-\s]+/g, "");
// Check for non-alphanumeric characters.
if (/[^0-9]/.test(ccNumber)) {
return false;
}
// Check for invalid length.
let length = ccNumber.length;
if (length != 9 && length != 15 && length != 16) {
return false;
}
let total = 0;
for (let i = 0; i < length; i++) {
let currentChar = ccNumber.charAt(length - i - 1);
let currentDigit = parseInt(currentChar, 10);
if (i % 2) {
// Double every other value.
total += currentDigit * 2;
// If the doubled value has two digits, add the digits together.
if (currentDigit > 4) {
total -= 9;
}
} else {
total += currentDigit;
}
}
return total % 10 == 0;
}
/**
* The public API exported by this module that allows to collect
* and restore form data for a document and its subframes.
@ -111,6 +149,12 @@ let FormDataInternal = {
continue;
}
// We do not want to collect credit card numbers.
if (node instanceof Ci.nsIDOMHTMLInputElement &&
isValidCCNumber(node.value)) {
continue;
}
if (node instanceof Ci.nsIDOMHTMLInputElement ||
node instanceof Ci.nsIDOMHTMLTextAreaElement ||
node instanceof Ci.nsIDOMXULTextBoxElement) {

View File

@ -57,8 +57,7 @@ function CommonNativeApp(aApp, aManifest, aCategories, aRegistryDir) {
aApp.name = aManifest.name;
this.uniqueName = WebappOSUtils.getUniqueName(aApp);
let localeManifest =
new ManifestHelper(aManifest, aApp.origin, aApp.manifestURL);
let localeManifest = new ManifestHelper(aManifest, aApp.origin);
this.appLocalizedName = localeManifest.name;
this.appNameAsFilename = stripStringForFilename(aApp.name);
@ -100,7 +99,7 @@ CommonNativeApp.prototype = {
*
*/
_setData: function(aApp, aManifest) {
let manifest = new ManifestHelper(aManifest, aApp.origin, aApp.manifestURL);
let manifest = new ManifestHelper(aManifest, aApp.origin);
let origin = Services.io.newURI(aApp.origin, null, null);
this.iconURI = Services.io.newURI(manifest.biggestIconURL || DEFAULT_ICON_URL,

View File

@ -47,8 +47,7 @@ this.WebappManager = {
doInstall: function(data, window) {
let jsonManifest = data.isPackage ? data.app.updateManifest : data.app.manifest;
let manifest =
new ManifestHelper(jsonManifest, data.app.origin, data.app.manifestURL);
let manifest = new ManifestHelper(jsonManifest, data.app.origin);
let name = manifest.name;
let bundle = Services.strings.createBundle("chrome://webapprt/locale/webapp.properties");

View File

@ -48,8 +48,7 @@ this.WebappRT = {
get localeManifest() {
return new ManifestHelper(this.config.app.manifest,
this.config.app.origin,
this.config.app.manifestURL);
this.config.app.origin);
},
get appID() {