--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -420,8 +420,9 @@ skip-if = (os == "win" && !debug) || e10
[browser_wyciwyg_urlbarCopying.js]
skip-if = e10s # Bug ?????? - test directly manipulates content (content.document.getElementById)
[browser_zbug569342.js]
skip-if = e10s # Bug 516755 - SessionStore disabled for e10s
[browser_registerProtocolHandler_notification.js]
skip-if = e10s # Bug 940206 - nsIWebContentHandlerRegistrar::registerProtocolHandler doesn't work in e10s
[browser_no_mcb_on_http_site.js]
skip-if = e10s # Bug 516755 - SessionStore disabled for e10s
+[browser_bug1003461-switchtab-override.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1003461-switchtab-override.js
@@ -0,0 +1,63 @@
+/* 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/. */
+
+add_task(function* test_switchtab_override() {
+ let testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+
+ info("Opening first tab");
+ let tab = gBrowser.addTab(testURL);
+ let deferred = Promise.defer();
+ whenTabLoaded(tab, deferred.resolve);
+ yield deferred.promise;
+
+ info("Opening and selecting second tab");
+ let secondTab = gBrowser.selectedTab = gBrowser.addTab();
+ registerCleanupFunction(() => {
+ try {
+ gBrowser.removeTab(tab);
+ gBrowser.removeTab(secondTab);
+ } catch(ex) { /* tabs may have already been closed in case of failure */ }
+ });
+
+ info("Wait for autocomplete")
+ deferred = Promise.defer();
+ let onSearchComplete = gURLBar.onSearchComplete;
+ registerCleanupFunction(() => {
+ gURLBar.onSearchComplete = onSearchComplete;
+ });
+ gURLBar.onSearchComplete = function () {
+ ok(gURLBar.popupOpen, "The autocomplete popup is correctly open");
+ onSearchComplete.apply(gURLBar);
+ deferred.resolve();
+ }
+
+ gURLBar.focus();
+ gURLBar.value = "dummy_pag";
+ EventUtils.synthesizeKey("e" , {});
+ yield deferred.promise;
+
+ info("Select first autocomplete popup entry");
+ EventUtils.synthesizeKey("VK_DOWN" , {});
+ ok(/moz-action:switchtab/.test(gURLBar.value), "switch to tab entry found");
+
+ info("Override switch-to-tab");
+ let deferred = Promise.defer();
+ // In case of failure this would switch tab.
+ let onTabSelect = event => {
+ deferred.reject(new Error("Should have overridden switch to tab"));
+ };
+ gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect, false);
+ registerCleanupFunction(() => {
+ gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect, false);
+ });
+ // Otherwise it would load the page.
+ whenTabLoaded(secondTab, deferred.resolve);
+
+ EventUtils.synthesizeKey("VK_SHIFT" , { type: "keydown" });
+ EventUtils.synthesizeKey("VK_RETURN" , { });
+ EventUtils.synthesizeKey("VK_SHIFT" , { type: "keyup" });
+ yield deferred.promise;
+
+ yield promiseClearHistory();
+});
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -288,16 +288,49 @@ function promiseHistoryClearedState(aURI
callbackDone();
});
});
return deferred.promise;
}
/**
+ * Allows waiting for an observer notification once.
+ *
+ * @param topic
+ * Notification topic to observe.
+ *
+ * @return {Promise}
+ * @resolves The array [subject, data] from the observed notification.
+ * @rejects Never.
+ */
+function promiseTopicObserved(topic)
+{
+ let deferred = Promise.defer();
+ Services.obs.addObserver(function PTO_observe(subject, topic, data) {
+ Services.obs.removeObserver(PTO_observe, topic);
+ deferred.resolve([subject, data]);
+ }, topic, false);
+ return deferred.promise;
+}
+
+/**
+ * Clears history asynchronously.
+ *
+ * @return {Promise}
+ * @resolves When history has been cleared.
+ * @rejects Never.
+ */
+function promiseClearHistory() {
+ let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
+ PlacesUtils.bhistory.removeAllPages();
+ return promise;
+}
+
+/**
* Waits for the next top-level document load in the current browser. The URI
* of the document is compared against aExpectedURL. The load is then stopped
* before it actually starts.
*
* @param aExpectedURL
* The URL of the document that is expected to load.
* @return promise
*/
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -138,17 +138,19 @@
It should return the value that the setter should use.
-->
<method name="onBeforeValueSet">
<parameter name="aValue"/>
<body><![CDATA[
this._value = aValue;
var returnValue = aValue;
var action = this._parseActionUrl(aValue);
- if (action) {
+ // Don't put back the action if we are invoked while override actions
+ // is active.
+ if (action && this._numNoActionsKeys <= 0) {
returnValue = action.param;
this.setAttribute("actiontype", action.type);
} else {
this.removeAttribute("actiontype");
}
return returnValue;
]]></body>
</method>
--- a/browser/components/customizableui/test/browser_962069_drag_to_overflow_chevron.js
+++ b/browser/components/customizableui/test/browser_962069_drag_to_overflow_chevron.js
@@ -19,20 +19,20 @@ add_task(function*() {
let widgetOverflowPanel = document.getElementById("widget-overflow");
let panelShownPromise = promisePanelElementShown(window, widgetOverflowPanel);
let identityBox = document.getElementById("identity-box");
let overflowChevron = document.getElementById("nav-bar-overflow-button");
ChromeUtils.synthesizeDrop(identityBox, overflowChevron, [], null);
yield panelShownPromise;
- ok(true, "Overflow panel is shown.");
+ info("Overflow panel is shown.");
let panelHiddenPromise = promisePanelElementHidden(window, widgetOverflowPanel);
- EventUtils.synthesizeKey("VK_ESCAPE", {});
+ widgetOverflowPanel.hidePopup();
yield panelHiddenPromise;
});
add_task(function*() {
window.resizeTo(originalWindowWidth, window.outerHeight);
let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar.");
--- a/browser/components/customizableui/test/browser_968447_bookmarks_toolbar_items_in_panel.js
+++ b/browser/components/customizableui/test/browser_968447_bookmarks_toolbar_items_in_panel.js
@@ -6,27 +6,47 @@
// Bug 968447 - The Bookmarks Toolbar Items doesn't appear as a
// normal menu panel button in new windows.
add_task(function() {
const buttonId = "bookmarks-toolbar-placeholder";
yield startCustomizing();
CustomizableUI.addWidgetToArea("personal-bookmarks", CustomizableUI.AREA_PANEL);
yield endCustomizing();
+
yield PanelUI.show();
+
let bookmarksToolbarPlaceholder = document.getElementById(buttonId);
ok(bookmarksToolbarPlaceholder.classList.contains("toolbarbutton-1"),
"Button should have toolbarbutton-1 class");
is(bookmarksToolbarPlaceholder.getAttribute("wrap"), "true",
"Button should have the 'wrap' attribute");
- yield PanelUI.hide();
- let newWin = yield openAndLoadWindow();
+ info("Waiting for panel to close");
+ let panelHiddenPromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHiddenPromise;
+
+ info("Waiting for window to open");
+ let newWin = yield openAndLoadWindow({}, true);
+
+ info("Waiting for panel in new window to open");
yield newWin.PanelUI.show();
let newWinBookmarksToolbarPlaceholder = newWin.document.getElementById(buttonId);
ok(newWinBookmarksToolbarPlaceholder.classList.contains("toolbarbutton-1"),
"Button in new window should have toolbarbutton-1 class");
is(newWinBookmarksToolbarPlaceholder.getAttribute("wrap"), "true",
"Button in new window should have 'wrap' attribute");
- yield newWin.PanelUI.hide();
+
+ info("Waiting for panel in new window to close");
+ panelHiddenPromise = promisePanelHidden(newWin);
+ newWin.PanelUI.hide();
+ yield panelHiddenPromise;
+
+ info("Waiting for new window to close");
yield promiseWindowClosed(newWin);
+});
+
+add_task(function asyncCleanUp() {
+ yield endCustomizing();
CustomizableUI.reset();
});
+
--- a/browser/components/sessionstore/test/browser_upgrade_backup.js
+++ b/browser/components/sessionstore/test/browser_upgrade_backup.js
@@ -11,35 +11,39 @@ function test() {
Task.spawn(function task() {
try {
// Wait until initialization is complete
yield SessionStore.promiseInitialized;
const PREF_UPGRADE = "browser.sessionstore.upgradeBackup.latestBuildID";
let buildID = Services.appinfo.platformBuildID;
+ // Write state once before starting the test to
+ // ensure sessionstore.js writes won't happen in between.
+ yield forceSaveState();
+
// Force backup to take place with a file decided by us
Services.prefs.setCharPref(PREF_UPGRADE, "");
let contents = "browser_upgrade_backup.js";
let pathStore = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js");
yield OS.File.writeAtomic(pathStore, contents, { tmpPath: pathStore + ".tmp" });
yield SessionStore._internal._performUpgradeBackup();
is(Services.prefs.getCharPref(PREF_UPGRADE), buildID, "upgrade backup should be set (again)");
let pathBackup = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak-" + Services.appinfo.platformBuildID);
is((yield OS.File.exists(pathBackup)), true, "upgrade backup file has been created");
let data = yield OS.File.read(pathBackup);
- is(contents, (new TextDecoder()).decode(data), "upgrade backup contains the expected contents");
+ is(new TextDecoder().decode(data), contents, "upgrade backup contains the expected contents");
// Ensure that we don't re-backup by accident
yield OS.File.writeAtomic(pathStore, "something else entirely", { tmpPath: pathStore + ".tmp" });
yield SessionStore._internal._performUpgradeBackup();
data = yield OS.File.read(pathBackup);
- is(contents, (new TextDecoder()).decode(data), "upgrade backup hasn't changed");
+ is(new TextDecoder().decode(data), contents, "upgrade backup hasn't changed");
} catch (ex) {
ok(false, "Uncaught error: " + ex + " at " + ex.stack);
} finally {
finish();
}
});
}
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -80,16 +80,19 @@ function MarkupView(aInspector, aFrame,
this.undo = new UndoStack();
this.undo.installController(aControllerWindow);
this._containers = new Map();
this._boundMutationObserver = this._mutationObserver.bind(this);
this.walker.on("mutations", this._boundMutationObserver);
+ this._boundOnDisplayChange = this._onDisplayChange.bind(this);
+ this.walker.on("display-change", this._boundOnDisplayChange);
+
this._boundOnNewSelection = this._onNewSelection.bind(this);
this._inspector.selection.on("new-node-front", this._boundOnNewSelection);
this._onNewSelection();
this._boundKeyDown = this._onKeyDown.bind(this);
this._frame.contentWindow.addEventListener("keydown", this._boundKeyDown, false);
this._boundFocus = this._onFocus.bind(this);
@@ -610,16 +613,29 @@ MarkupView.prototype = {
}
});
}
});
},
/**
+ * React to display-change events from the walker
+ * @param {Array} nodes An array of nodeFronts
+ */
+ _onDisplayChange: function(nodes) {
+ for (let node of nodes) {
+ let container = this._containers.get(node);
+ if (container) {
+ container.isDisplayed = node.isDisplayed;
+ }
+ }
+ },
+
+ /**
* Given a list of mutations returned by the mutation observer, flash the
* corresponding containers to attract attention.
*/
_flashMutatedNodes: function(aMutations) {
let addedOrEditedContainers = new Set();
let removedContainers = new Set();
for (let {type, target, added, removed} of aMutations) {
@@ -1105,16 +1121,19 @@ MarkupView.prototype = {
this._boundKeyDown = null;
this._inspector.selection.off("new-node-front", this._boundOnNewSelection);
this._boundOnNewSelection = null;
this.walker.off("mutations", this._boundMutationObserver)
this._boundMutationObserver = null;
+ this.walker.off("display-change", this._boundOnDisplayChange);
+ this._boundOnDisplayChange = null;
+
this._elt.removeEventListener("mousemove", this._onMouseMove, false);
this._elt.removeEventListener("mouseleave", this._onMouseLeave, false);
this._elt = null;
for (let [key, container] of this._containers) {
container.destroy();
}
this._containers = null;
@@ -1263,16 +1282,19 @@ function MarkupContainer(aMarkupView, aN
// Appending the editor element and attaching event listeners
this.tagLine.appendChild(this.editor.elt);
this._onMouseDown = this._onMouseDown.bind(this);
this.elt.addEventListener("mousedown", this._onMouseDown, false);
// Prepare the image preview tooltip data if any
this._prepareImagePreview();
+
+ // Marking the node as shown or hidden
+ this.isDisplayed = this.node.isDisplayed;
}
MarkupContainer.prototype = {
toString: function() {
return "[MarkupContainer for " + this.node + "]";
},
isPreviewable: function() {
@@ -1337,16 +1359,26 @@ MarkupContainer.prototype = {
return this.tooltipData.data.then(({data, size}) => {
tooltip.setImageContent(data, size);
}, () => {
tooltip.setBrokenImageContent();
});
},
+ /**
+ * Show the element has displayed or not
+ */
+ set isDisplayed(isDisplayed) {
+ this.elt.classList.remove("not-displayed");
+ if (!isDisplayed) {
+ this.elt.classList.add("not-displayed");
+ }
+ },
+
copyImageDataUri: function() {
// We need to send again a request to gettooltipData even if one was sent for
// the tooltip, because we want the full-size image
this.node.getImageData().then(data => {
data.data.string().then(str => {
clipboardHelper.copyString(str, this.markup.doc);
});
});
--- a/browser/devtools/markupview/test/browser.ini
+++ b/browser/devtools/markupview/test/browser.ini
@@ -1,16 +1,17 @@
[DEFAULT]
skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
subsuite = devtools
support-files =
doc_markup_edit.html
doc_markup_flashing.html
doc_markup_mutation.html
doc_markup_navigation.html
+ doc_markup_not_displayed.html
doc_markup_pagesize_01.html
doc_markup_pagesize_02.html
doc_markup_search.html
doc_markup_toggle.html
doc_markup_tooltip.png
head.js
helper_attributes_test_runner.js
helper_outerhtml_test_runner.js
@@ -21,16 +22,18 @@ support-files =
[browser_markupview_highlight_hover_02.js]
[browser_markupview_html_edit_01.js]
[browser_markupview_html_edit_02.js]
[browser_markupview_html_edit_03.js]
[browser_markupview_image_tooltip.js]
[browser_markupview_mutation_01.js]
[browser_markupview_mutation_02.js]
[browser_markupview_navigation.js]
+[browser_markupview_node_not_displayed_01.js]
+[browser_markupview_node_not_displayed_02.js]
[browser_markupview_pagesize_01.js]
[browser_markupview_pagesize_02.js]
[browser_markupview_search_01.js]
[browser_markupview_tag_edit_01.js]
[browser_markupview_tag_edit_02.js]
[browser_markupview_tag_edit_03.js]
[browser_markupview_tag_edit_04.js]
[browser_markupview_tag_edit_05.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_markupview_node_not_displayed_01.js
@@ -0,0 +1,33 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that nodes that are not displayed appear differently in the markup-view
+// when these nodes are imported in the view.
+
+// Note that nodes inside a display:none parent are obviously not displayed too
+// but the markup-view uses css inheritance to mark those as hidden instead of
+// having to visit each and every child of a hidden node. So there's no sense
+// testing children nodes.
+
+const TEST_URL = TEST_URL_ROOT + "doc_markup_not_displayed.html";
+const TEST_DATA = [
+ {selector: "#normal-div", isDisplayed: true},
+ {selector: "head", isDisplayed: false},
+ {selector: "#display-none", isDisplayed: false},
+ {selector: "#hidden-true", isDisplayed: false},
+ {selector: "#visibility-hidden", isDisplayed: true}
+];
+
+let test = asyncTest(function*() {
+ let {inspector} = yield addTab(TEST_URL).then(openInspector);
+
+ for (let {selector, isDisplayed} of TEST_DATA) {
+ info("Getting node " + selector);
+ let container = getContainerForRawNode(selector, inspector);
+ is(!container.elt.classList.contains("not-displayed"), isDisplayed,
+ "The container for " + selector + " is marked as displayed " + isDisplayed);
+ }
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_markupview_node_not_displayed_02.js
@@ -0,0 +1,132 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that nodes are marked as displayed and not-displayed dynamically, when
+// their display changes
+
+const TEST_URL = TEST_URL_ROOT + "doc_markup_not_displayed.html";
+const TEST_DATA = [
+ {
+ desc: "Hiding a node by creating a new stylesheet",
+ selector: "#normal-div",
+ before: true,
+ changeStyle: (doc, node) => {
+ let div = doc.createElement("div");
+ div.id = "new-style";
+ div.innerHTML = "<style>#normal-div {display:none;}</style>";
+ doc.body.appendChild(div);
+ },
+ after: false
+ },
+ {
+ desc: "Showing a node by deleting an existing stylesheet",
+ selector: "#normal-div",
+ before: false,
+ changeStyle: (doc, node) => {
+ doc.getElementById("new-style").remove();
+ },
+ after: true
+ },
+ {
+ desc: "Hiding a node by changing its style property",
+ selector: "#display-none",
+ before: false,
+ changeStyle: (doc, node) => {
+ node.style.display = "block";
+ },
+ after: true
+ },
+ {
+ desc: "Showing a node by removing its hidden attribute",
+ selector: "#hidden-true",
+ before: false,
+ changeStyle: (doc, node) => {
+ node.removeAttribute("hidden");
+ },
+ after: true
+ },
+ {
+ desc: "Hiding a node by adding a hidden attribute",
+ selector: "#hidden-true",
+ before: true,
+ changeStyle: (doc, node) => {
+ node.setAttribute("hidden", "true");
+ },
+ after: false
+ },
+ {
+ desc: "Showing a node by changin a stylesheet's rule",
+ selector: "#hidden-via-stylesheet",
+ before: false,
+ changeStyle: (doc, node) => {
+ doc.styleSheets[0].cssRules[0].style.setProperty("display", "inline");
+ },
+ after: true
+ },
+ {
+ desc: "Hiding a node by adding a new rule to a stylesheet",
+ selector: "#hidden-via-stylesheet",
+ before: true,
+ changeStyle: (doc, node) => {
+ doc.styleSheets[0].insertRule(
+ "#hidden-via-stylesheet {display: none;}", 1);
+ },
+ after: false
+ },
+ {
+ desc: "Hiding a node by adding a class that matches an existing rule",
+ selector: "#normal-div",
+ before: true,
+ changeStyle: (doc, node) => {
+ doc.styleSheets[0].insertRule(
+ ".a-new-class {display: none;}", 2);
+ node.classList.add("a-new-class");
+ },
+ after: false
+ }
+];
+
+let test = asyncTest(function*() {
+ let {inspector} = yield addTab(TEST_URL).then(openInspector);
+
+ for (let data of TEST_DATA) {
+ info("Running test case: " + data.desc);
+ yield runTestData(inspector, data);
+ }
+});
+
+function runTestData(inspector, {selector, before, changeStyle, after}) {
+ let def = promise.defer();
+
+ info("Getting the " + selector + " test node");
+ let container = getContainerForRawNode(selector, inspector);
+ is(!container.elt.classList.contains("not-displayed"), before,
+ "The container is marked as " + (before ? "shown" : "hidden"));
+
+ info("Listening for the display-change event");
+ inspector.markup.walker.once("display-change", nodes => {
+ info("Verifying that the list of changed nodes include our container");
+
+ ok(nodes.length, "The display-change event was received with a nodes");
+ let foundContainer = false;
+ for (let node of nodes) {
+ if (inspector.markup.getContainer(node) === container) {
+ foundContainer = true;
+ break;
+ }
+ }
+ ok(foundContainer, "Container is part of the list of changed nodes");
+
+ is(!container.elt.classList.contains("not-displayed"), after,
+ "The container is marked as " + (after ? "shown" : "hidden"));
+ def.resolve();
+ });
+
+ info("Making style changes");
+ changeStyle(content.document, getNode(selector));
+
+ return def.promise;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/doc_markup_not_displayed.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ #hidden-via-stylesheet {
+ display: none;
+ }
+ </style>
+</head>
+<body>
+ <div id="normal-div"></div>
+ <div id="display-none" style="display:none;"></div>
+ <div id="hidden-true" hidden="true"></div>
+ <div id="visibility-hidden" style="visibility:hidden;"></div>
+ <div id="hidden-via-stylesheet"></div>
+</body>
+</html>
\ No newline at end of file
--- a/browser/themes/shared/devtools/markup-view.css
+++ b/browser/themes/shared/devtools/markup-view.css
@@ -25,16 +25,22 @@
.theme-selected ~ .editor .theme-fg-color3,
.theme-selected ~ .editor .theme-fg-color4,
.theme-selected ~ .editor .theme-fg-color5,
.theme-selected ~ .editor .theme-fg-color6,
.theme-selected ~ .editor .theme-fg-color7 {
color: #f5f7fa; /* Light foreground text */
}
+/* In case a node isn't displayed in the page, we fade the syntax highlighting */
+.not-displayed .open,
+.not-displayed .close {
+ opacity: .7;
+}
+
.tag-line {
padding-left: 2px;
}
/* Preview */
#previewbar {
position: fixed;
--- a/build/annotationProcessors/AnnotationInfo.java
+++ b/build/annotationProcessors/AnnotationInfo.java
@@ -7,17 +7,19 @@ package org.mozilla.gecko.annotationProc
/**
* Object holding annotation data. Used by GeneratableElementIterator.
*/
public class AnnotationInfo {
public final String wrapperName;
public final boolean isStatic;
public final boolean isMultithreaded;
public final boolean noThrow;
+ public final boolean narrowChars;
public AnnotationInfo(String aWrapperName, boolean aIsStatic, boolean aIsMultithreaded,
- boolean aNoThrow) {
+ boolean aNoThrow, boolean aNarrowChars) {
wrapperName = aWrapperName;
isStatic = aIsStatic;
isMultithreaded = aIsMultithreaded;
noThrow = aNoThrow;
+ narrowChars = aNarrowChars;
}
}
--- a/build/annotationProcessors/CodeGenerator.java
+++ b/build/annotationProcessors/CodeGenerator.java
@@ -98,47 +98,48 @@ public class CodeGenerator {
boolean isFieldStatic = Utils.isMemberStatic(theMethod);
boolean shallGenerateStatic = isFieldStatic || aMethodTuple.mAnnotationInfo.isStatic;
Class<?>[] parameterTypes = theMethod.getParameterTypes();
Class<?> returnType = theMethod.getReturnType();
// Get the C++ method signature for this method.
- String implementationSignature = Utils.getCImplementationMethodSignature(parameterTypes, returnType, CMethodName, mCClassName);
- String headerSignature = Utils.getCHeaderMethodSignature(parameterTypes, theMethod.getParameterAnnotations(), returnType, CMethodName, mCClassName, shallGenerateStatic);
+ String implementationSignature = Utils.getCImplementationMethodSignature(parameterTypes, returnType, CMethodName, mCClassName, aMethodTuple.mAnnotationInfo.narrowChars);
+ String headerSignature = Utils.getCHeaderMethodSignature(parameterTypes, theMethod.getParameterAnnotations(), returnType, CMethodName, mCClassName, shallGenerateStatic, aMethodTuple.mAnnotationInfo.narrowChars);
// Add the header signature to the header file.
writeSignatureToHeader(headerSignature);
// Use the implementation signature to generate the method body...
writeMethodBody(implementationSignature, CMethodName, theMethod, mClassToWrap,
aMethodTuple.mAnnotationInfo.isStatic,
aMethodTuple.mAnnotationInfo.isMultithreaded,
- aMethodTuple.mAnnotationInfo.noThrow);
+ aMethodTuple.mAnnotationInfo.noThrow,
+ aMethodTuple.mAnnotationInfo.narrowChars);
}
- private void generateGetterOrSetterBody(Class<?> aFieldType, String aFieldName, boolean aIsFieldStatic, boolean isSetter) {
+ private void generateGetterOrSetterBody(Class<?> aFieldType, String aFieldName, boolean aIsFieldStatic, boolean isSetter, boolean aNarrowChars) {
StringBuilder argumentContent = null;
if (isSetter) {
Class<?>[] setterArguments = new Class<?>[]{aFieldType};
// Marshall the argument..
argumentContent = getArgumentMarshalling(setterArguments);
}
boolean isObjectReturningMethod = Utils.isObjectType(aFieldType);
wrapperMethodBodies.append(" ");
if (isSetter) {
wrapperMethodBodies.append("env->Set");
} else {
wrapperMethodBodies.append("return ");
if (isObjectReturningMethod) {
- wrapperMethodBodies.append("static_cast<").append(Utils.getCReturnType(aFieldType)).append(">(");
+ wrapperMethodBodies.append("static_cast<").append(Utils.getCReturnType(aFieldType, aNarrowChars)).append(">(");
}
wrapperMethodBodies.append("env->Get");
}
if (aIsFieldStatic) {
wrapperMethodBodies.append("Static");
}
@@ -179,51 +180,51 @@ public class CodeGenerator {
generateMemberCommon(theField, CFieldName, mClassToWrap);
boolean isFieldStatic = Utils.isMemberStatic(theField);
boolean isFieldFinal = Utils.isMemberFinal(theField);
boolean shallGenerateStatic = isFieldStatic || aFieldTuple.mAnnotationInfo.isStatic;
String getterName = "get" + CFieldName;
- String getterSignature = Utils.getCImplementationMethodSignature(EMPTY_CLASS_ARRAY, fieldType, getterName, mCClassName);
- String getterHeaderSignature = Utils.getCHeaderMethodSignature(EMPTY_CLASS_ARRAY, GETTER_ARGUMENT_ANNOTATIONS, fieldType, getterName, mCClassName, shallGenerateStatic);
+ String getterSignature = Utils.getCImplementationMethodSignature(EMPTY_CLASS_ARRAY, fieldType, getterName, mCClassName, aFieldTuple.mAnnotationInfo.narrowChars);
+ String getterHeaderSignature = Utils.getCHeaderMethodSignature(EMPTY_CLASS_ARRAY, GETTER_ARGUMENT_ANNOTATIONS, fieldType, getterName, mCClassName, shallGenerateStatic, aFieldTuple.mAnnotationInfo.narrowChars);
writeSignatureToHeader(getterHeaderSignature);
writeFunctionStartupBoilerPlate(getterSignature, fieldType, isFieldStatic, true);
- generateGetterOrSetterBody(fieldType, CFieldName, isFieldStatic, false);
+ generateGetterOrSetterBody(fieldType, CFieldName, isFieldStatic, false, aFieldTuple.mAnnotationInfo.narrowChars);
// If field not final, also generate a setter function.
if (!isFieldFinal) {
String setterName = "set" + CFieldName;
Class<?>[] setterArguments = new Class<?>[]{fieldType};
- String setterSignature = Utils.getCImplementationMethodSignature(setterArguments, Void.class, setterName, mCClassName);
- String setterHeaderSignature = Utils.getCHeaderMethodSignature(setterArguments, SETTER_ARGUMENT_ANNOTATIONS, Void.class, setterName, mCClassName, shallGenerateStatic);
+ String setterSignature = Utils.getCImplementationMethodSignature(setterArguments, Void.class, setterName, mCClassName, aFieldTuple.mAnnotationInfo.narrowChars);
+ String setterHeaderSignature = Utils.getCHeaderMethodSignature(setterArguments, SETTER_ARGUMENT_ANNOTATIONS, Void.class, setterName, mCClassName, shallGenerateStatic, aFieldTuple.mAnnotationInfo.narrowChars);
writeSignatureToHeader(setterHeaderSignature);
writeFunctionStartupBoilerPlate(setterSignature, Void.class, isFieldStatic, true);
- generateGetterOrSetterBody(fieldType, CFieldName, isFieldStatic, true);
+ generateGetterOrSetterBody(fieldType, CFieldName, isFieldStatic, true, aFieldTuple.mAnnotationInfo.narrowChars);
}
}
public void generateConstructor(AnnotatableEntity aCtorTuple) {
// Unpack the tuple and extract some useful fields from the Method..
Constructor theCtor = aCtorTuple.getConstructor();
String CMethodName = mCClassName;
generateMemberCommon(theCtor, mCClassName, mClassToWrap);
- String implementationSignature = Utils.getCImplementationMethodSignature(theCtor.getParameterTypes(), Void.class, CMethodName, mCClassName);
- String headerSignature = Utils.getCHeaderMethodSignature(theCtor.getParameterTypes(), theCtor.getParameterAnnotations(), Void.class, CMethodName, mCClassName, false);
+ String implementationSignature = Utils.getCImplementationMethodSignature(theCtor.getParameterTypes(), Void.class, CMethodName, mCClassName, aCtorTuple.mAnnotationInfo.narrowChars);
+ String headerSignature = Utils.getCHeaderMethodSignature(theCtor.getParameterTypes(), theCtor.getParameterAnnotations(), Void.class, CMethodName, mCClassName, false, aCtorTuple.mAnnotationInfo.narrowChars);
// Slice off the "void " from the start of the constructor declaration.
headerSignature = headerSignature.substring(5);
implementationSignature = implementationSignature.substring(5);
// Add the header signatures to the header file.
writeSignatureToHeader(headerSignature);
@@ -421,17 +422,17 @@ public class CodeGenerator {
* @param methodSignature The previously-generated C++ method signature for the method to be
* generated.
* @param aCMethodName The C++ method name for the method to be generated.
* @param aMethod The Java method to be wrapped by the C++ method being generated.
* @param aClass The Java class to which the method belongs.
*/
private void writeMethodBody(String methodSignature, String aCMethodName, Method aMethod,
Class<?> aClass, boolean aIsStaticBridgeMethod, boolean aIsMultithreaded,
- boolean aNoThrow) {
+ boolean aNoThrow, boolean aNarrowChars) {
Class<?>[] argumentTypes = aMethod.getParameterTypes();
Class<?> returnType = aMethod.getReturnType();
writeFunctionStartupBoilerPlate(methodSignature, returnType, aIsStaticBridgeMethod, aIsMultithreaded);
boolean isObjectReturningMethod = !returnType.getCanonicalName().equals("void") && Utils.isObjectType(returnType);
writeFramePushBoilerplate(aMethod, isObjectReturningMethod, aNoThrow);
@@ -450,17 +451,17 @@ public class CodeGenerator {
}
// Allocate a temporary variable to hold the return type from Java.
wrapperMethodBodies.append(" ");
if (!returnType.getCanonicalName().equals("void")) {
if (isObjectReturningMethod) {
wrapperMethodBodies.append("jobject");
} else {
- wrapperMethodBodies.append(Utils.getCReturnType(returnType));
+ wrapperMethodBodies.append(Utils.getCReturnType(returnType, aNarrowChars));
}
wrapperMethodBodies.append(" temp = ");
}
boolean isStaticJavaMethod = Utils.isMemberStatic(aMethod);
// The call into Java
wrapperMethodBodies.append("env->")
@@ -489,18 +490,18 @@ public class CodeGenerator {
if (!aNoThrow) {
wrapperMethodBodies.append(" AndroidBridge::HandleUncaughtException(env);\n");
}
// If we're returning an object, pop the callee's stack frame extracting our ref as the return
// value.
if (isObjectReturningMethod) {
wrapperMethodBodies.append(" ")
- .append(Utils.getCReturnType(returnType))
- .append(" ret = static_cast<").append(Utils.getCReturnType(returnType)).append(">(env->PopLocalFrame(temp));\n" +
+ .append(Utils.getCReturnType(returnType, aNarrowChars))
+ .append(" ret = static_cast<").append(Utils.getCReturnType(returnType, aNarrowChars)).append(">(env->PopLocalFrame(temp));\n" +
" return ret;\n");
} else if (!returnType.getCanonicalName().equals("void")) {
// If we're a primitive-returning function, just return the directly-obtained primative
// from the call to Java.
wrapperMethodBodies.append(" env->PopLocalFrame(nullptr);\n" +
" return temp;\n");
} else {
// If we don't return anything, just pop the stack frame and move on with life.
--- a/build/annotationProcessors/utils/GeneratableElementIterator.java
+++ b/build/annotationProcessors/utils/GeneratableElementIterator.java
@@ -71,16 +71,17 @@ public class GeneratableElementIterator
// WrappedJNIMethod has parameters. Use Reflection to obtain them.
Class<? extends Annotation> annotationType = annotation.annotationType();
final String annotationTypeName = annotationType.getName();
if (annotationTypeName.equals("org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI")) {
String stubName = null;
boolean isStaticStub = false;
boolean isMultithreadedStub = false;
boolean noThrow = false;
+ boolean narrowChars = false;
try {
// Determine the explicitly-given name of the stub to generate, if any.
final Method stubNameMethod = annotationType.getDeclaredMethod("stubName");
stubNameMethod.setAccessible(true);
stubName = (String) stubNameMethod.invoke(annotation);
// Detemine if the generated stub should be static.
final Method staticStubMethod = annotationType.getDeclaredMethod("generateStatic");
@@ -92,16 +93,21 @@ public class GeneratableElementIterator
multithreadedStubMethod.setAccessible(true);
isMultithreadedStub = (Boolean) multithreadedStubMethod.invoke(annotation);
// Determine if ignoring exceptions
final Method noThrowMethod = annotationType.getDeclaredMethod("noThrow");
noThrowMethod.setAccessible(true);
noThrow = (Boolean) noThrowMethod.invoke(annotation);
+ // Determine if strings should be wide or narrow
+ final Method narrowCharsMethod = annotationType.getDeclaredMethod("narrowChars");
+ narrowCharsMethod.setAccessible(true);
+ narrowChars = (Boolean) narrowCharsMethod.invoke(annotation);
+
} catch (NoSuchMethodException e) {
System.err.println("Unable to find expected field on WrapElementForJNI annotation. Did the signature change?");
e.printStackTrace(System.err);
System.exit(3);
} catch (IllegalAccessException e) {
System.err.println("IllegalAccessException reading fields on WrapElementForJNI annotation. Seems the semantics of Reflection have changed...");
e.printStackTrace(System.err);
System.exit(4);
@@ -113,27 +119,27 @@ public class GeneratableElementIterator
// If the method name was not explicitly given in the annotation generate one...
if (stubName.isEmpty()) {
String aMethodName = candidateElement.getName();
stubName = aMethodName.substring(0, 1).toUpperCase() + aMethodName.substring(1);
}
AnnotationInfo annotationInfo = new AnnotationInfo(
- stubName, isStaticStub, isMultithreadedStub, noThrow);
+ stubName, isStaticStub, isMultithreadedStub, noThrow, narrowChars);
mNextReturnValue = new AnnotatableEntity(candidateElement, annotationInfo);
return;
}
}
// If no annotation found, we might be expected to generate anyway using default arguments,
// thanks to the "Generate everything" annotation.
if (mIterateEveryEntry) {
AnnotationInfo annotationInfo = new AnnotationInfo(
- candidateElement.getName(), false, false, false);
+ candidateElement.getName(), false, false, false, false);
mNextReturnValue = new AnnotatableEntity(candidateElement, annotationInfo);
return;
}
}
mNextReturnValue = null;
}
@Override
--- a/build/annotationProcessors/utils/Utils.java
+++ b/build/annotationProcessors/utils/Utils.java
@@ -132,17 +132,17 @@ public class Utils {
/**
* Get the C type corresponding to the provided type parameter. Used for generating argument
* types for the wrapper method.
*
* @param type Class to determine the corresponding JNI type for.
* @return true if the type an object type, false otherwise.
*/
- public static String getCParameterType(Class<?> type) {
+ public static String getCParameterType(Class<?> type, boolean aNarrowChars) {
String name = type.getCanonicalName();
if (sBasicCTypes.containsKey(name)) {
return sBasicCTypes.get(name);
}
// Are we dealing with an array type?
int len = name.length();
if (name.endsWith("[]")) {
// Determine if it is a 2D array - these map to jobjectArrays
@@ -156,16 +156,19 @@ public class Utils {
}
return "jobjectArray";
}
}
// Not an array type, check the remaining possibilities before we fall back to jobject
// Check for CharSequences (Strings and things that are string-like)
if (isCharSequence(type)) {
+ if (aNarrowChars) {
+ return "const nsACString&";
+ }
return "const nsAString&";
}
if (name.equals("java.lang.Class")) {
// You're doing reflection on Java objects from inside C, returning Class objects
// to C, generating the corresponding code using this Java program. Really?!
return "jclass";
}
@@ -176,22 +179,22 @@ public class Utils {
}
/**
* For a given Java type, get the corresponding C++ type if we're returning it from a function.
*
* @param type The Java return type.
* @return A string representation of the C++ return type.
*/
- public static String getCReturnType(Class<?> type) {
+ public static String getCReturnType(Class<?> type, boolean aNarrowChars) {
if (type.getCanonicalName().equals("java.lang.Void")) {
return "void";
}
- String cParameterType = getCParameterType(type);
- if (cParameterType.equals("const nsAString&")) {
+ String cParameterType = getCParameterType(type, aNarrowChars);
+ if (cParameterType.equals("const nsAString&") || cParameterType.equals("const nsACString&")) {
return "jstring";
} else {
return cParameterType;
}
}
/**
* Gets the type-specific part of the JNI function to use to get or set a field of a given type.
@@ -376,29 +379,29 @@ public class Utils {
* generating header files and method bodies.
*
* @param aArgumentTypes Argument types of the Java method being wrapped.
* @param aReturnType Return type of the Java method being wrapped.
* @param aCMethodName Name of the method to generate in the C++ class.
* @param aCClassName Name of the C++ class into which the method is declared.
* @return The C++ method implementation signature for the method described.
*/
- public static String getCImplementationMethodSignature(Class<?>[] aArgumentTypes, Class<?> aReturnType, String aCMethodName, String aCClassName) {
+ public static String getCImplementationMethodSignature(Class<?>[] aArgumentTypes, Class<?> aReturnType, String aCMethodName, String aCClassName, boolean aNarrowChars) {
StringBuilder retBuffer = new StringBuilder();
- retBuffer.append(getCReturnType(aReturnType));
+ retBuffer.append(getCReturnType(aReturnType, aNarrowChars));
retBuffer.append(' ');
retBuffer.append(aCClassName);
retBuffer.append("::");
retBuffer.append(aCMethodName);
retBuffer.append('(');
// Write argument types...
for (int aT = 0; aT < aArgumentTypes.length; aT++) {
- retBuffer.append(getCParameterType(aArgumentTypes[aT]));
+ retBuffer.append(getCParameterType(aArgumentTypes[aT], aNarrowChars));
retBuffer.append(" a");
// We, imaginatively, call our arguments a1, a2, a3...
// The only way to preserve the names from Java would be to parse the
// Java source, which would be computationally hard.
retBuffer.append(aT);
if (aT != aArgumentTypes.length - 1) {
retBuffer.append(", ");
}
@@ -415,33 +418,33 @@ public class Utils {
* @param aArgumentAnnotations The annotations on the Java method arguments. Used to specify
* default values etc.
* @param aReturnType Return type of the Java method being wrapped.
* @param aCMethodName Name of the method to generate in the C++ class.
* @param aCClassName Name of the C++ class into which the method is declared.e
* @param aIsStaticStub true if the generated C++ method should be static, false otherwise.
* @return The generated C++ header method signature for the method described.
*/
- public static String getCHeaderMethodSignature(Class<?>[] aArgumentTypes, Annotation[][] aArgumentAnnotations, Class<?> aReturnType, String aCMethodName, String aCClassName, boolean aIsStaticStub) {
+ public static String getCHeaderMethodSignature(Class<?>[] aArgumentTypes, Annotation[][] aArgumentAnnotations, Class<?> aReturnType, String aCMethodName, String aCClassName, boolean aIsStaticStub, boolean aNarrowChars) {
StringBuilder retBuffer = new StringBuilder();
// Add the static keyword, if applicable.
if (aIsStaticStub) {
retBuffer.append("static ");
}
// Write return type..
- retBuffer.append(getCReturnType(aReturnType));
+ retBuffer.append(getCReturnType(aReturnType, aNarrowChars));
retBuffer.append(' ');
retBuffer.append(aCMethodName);
retBuffer.append('(');
// Write argument types...
for (int aT = 0; aT < aArgumentTypes.length; aT++) {
- retBuffer.append(getCParameterType(aArgumentTypes[aT]));
+ retBuffer.append(getCParameterType(aArgumentTypes[aT], aNarrowChars));
retBuffer.append(" a");
// We, imaginatively, call our arguments a1, a2, a3...
// The only way to preserve the names from Java would be to parse the
// Java source, which would be computationally hard.
retBuffer.append(aT);
// Append the default value, if there is one..
retBuffer.append(getDefaultValueString(aArgumentTypes[aT], aArgumentAnnotations[aT]));
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -2531,17 +2531,19 @@ this.DOMApplicationRegistry = {
}
// We notify about the successful installation via mgmt.oninstall and the
// corresponding DOMRequest.onsuccess event as soon as the app is properly
// saved in the registry.
yield this._saveApps();
this.broadcastMessage("Webapps:AddApp", { id: id, app: appObject });
- if (aData.isPackage && aData.autoInstall) {
+
+ // The presence of a requestID means that we have a page to update.
+ if (aData.isPackage && aData.apkInstall && !aData.requestID) {
// Skip directly to onInstallSuccessAck, since there isn't
// a WebappsRegistry to receive Webapps:Install:Return:OK and respond
// Webapps:Install:Return:Ack when an app is being auto-installed.
this.onInstallSuccessAck(app.manifestURL);
} else {
// Broadcast Webapps:Install:Return:OK so the WebappsRegistry can notify
// the installing page about the successful install, after which it'll
// respond Webapps:Install:Return:Ack, which calls onInstallSuccessAck.
--- a/dom/browser-element/BrowserElementParent.jsm
+++ b/dom/browser-element/BrowserElementParent.jsm
@@ -35,36 +35,16 @@ function getIntPref(prefName, def) {
try {
return Services.prefs.getIntPref(prefName);
}
catch(err) {
return def;
}
}
-function exposeAll(obj) {
- // Filter for Objects and Arrays.
- if (typeof obj !== "object" || !obj)
- return;
-
- // Recursively expose our children.
- Object.keys(obj).forEach(function(key) {
- exposeAll(obj[key]);
- });
-
- // If we're not an Array, generate an __exposedProps__ object for ourselves.
- if (obj instanceof Array)
- return;
- var exposed = {};
- Object.keys(obj).forEach(function(key) {
- exposed[key] = 'rw';
- });
- obj.__exposedProps__ = exposed;
-}
-
function defineAndExpose(obj, name, value) {
obj[name] = value;
if (!('__exposedProps__' in obj))
obj.__exposedProps__ = {};
obj.__exposedProps__[name] = 'r';
}
function visibilityChangeHandler(e) {
@@ -494,17 +474,17 @@ BrowserElementParent.prototype = {
sendUnblockMsg();
}
},
_createEvent: function(evtName, detail, cancelable) {
// This will have to change if we ever want to send a CustomEvent with null
// detail. For now, it's OK.
if (detail !== undefined && detail !== null) {
- exposeAll(detail);
+ detail = Cu.cloneInto(detail, this._window);
return new this._window.CustomEvent('mozbrowser' + evtName,
{ bubbles: true,
cancelable: cancelable,
detail: detail });
}
return new this._window.Event('mozbrowser' + evtName,
{ bubbles: true,
--- a/js/public/ProfilingStack.h
+++ b/js/public/ProfilingStack.h
@@ -42,31 +42,32 @@ class ProfileEntry
// Stack pointer for non-JS entries, the script pointer otherwise.
void * volatile spOrScript;
// Line number for non-JS entries, the bytecode offset otherwise.
int32_t volatile lineOrPc;
// General purpose storage describing this frame.
- uint32_t volatile flags;
+ uint32_t volatile flags_;
public:
- ProfileEntry(void) : flags(0) {}
-
// These traits are bit masks. Make sure they're powers of 2.
enum Flags {
// Indicate whether a profile entry represents a CPP frame. If not set,
// a JS frame is assumed by default. You're not allowed to publicly
// change the frame type. Instead, call `setJsFrame` or `setCppFrame`.
IS_CPP_ENTRY = 0x01,
// Indicate that copying the frame label is not necessary when taking a
// sample of the pseudostack.
- FRAME_LABEL_COPY = 0x02
+ FRAME_LABEL_COPY = 0x02,
+
+ // Mask for removing all flags except the category information.
+ CATEGORY_MASK = ~IS_CPP_ENTRY & ~FRAME_LABEL_COPY
};
MOZ_BEGIN_NESTED_ENUM_CLASS(Category, uint32_t)
OTHER = 0x04,
CSS = 0x08,
JS = 0x10,
GC = 0x20,
CC = 0x40,
@@ -88,36 +89,43 @@ class ProfileEntry
bool isJs() const volatile { return !isCpp(); }
bool isCopyLabel() const volatile { return hasFlag(FRAME_LABEL_COPY); };
void setLabel(const char *aString) volatile { string = aString; }
const char *label() const volatile { return string; }
void setJsFrame(JSScript *aScript, jsbytecode *aPc) volatile {
- flags &= ~IS_CPP_ENTRY;
+ flags_ = 0;
spOrScript = aScript;
setPC(aPc);
}
void setCppFrame(void *aSp, uint32_t aLine) volatile {
- flags |= IS_CPP_ENTRY;
+ flags_ = IS_CPP_ENTRY;
spOrScript = aSp;
lineOrPc = static_cast<int32_t>(aLine);
}
void setFlag(uint32_t flag) volatile {
MOZ_ASSERT(flag != IS_CPP_ENTRY);
- flags |= flag;
+ flags_ |= flag;
}
void unsetFlag(uint32_t flag) volatile {
MOZ_ASSERT(flag != IS_CPP_ENTRY);
- flags &= ~flag;
+ flags_ &= ~flag;
}
bool hasFlag(uint32_t flag) const volatile {
- return bool(flags & uint32_t(flag));
+ return bool(flags_ & flag);
+ }
+
+ uint32_t flags() const volatile {
+ return flags_;
+ }
+ uint32_t category() const volatile {
+ return flags_ & CATEGORY_MASK;
}
void *stackAddress() const volatile {
MOZ_ASSERT(!isJs());
return spOrScript;
}
JSScript *script() const volatile {
MOZ_ASSERT(isJs());
@@ -135,17 +143,17 @@ class ProfileEntry
// The offset of a pc into a script's code can actually be 0, so to
// signify a nullptr pc, use a -1 index. This is checked against in
// pc() and setPC() to set/get the right pc.
static const int32_t NullPCOffset = -1;
static size_t offsetOfLabel() { return offsetof(ProfileEntry, string); }
static size_t offsetOfSpOrScript() { return offsetof(ProfileEntry, spOrScript); }
static size_t offsetOfLineOrPc() { return offsetof(ProfileEntry, lineOrPc); }
- static size_t offsetOfFlags() { return offsetof(ProfileEntry, flags); }
+ static size_t offsetOfFlags() { return offsetof(ProfileEntry, flags_); }
};
JS_FRIEND_API(void)
SetRuntimeProfilingStack(JSRuntime *rt, ProfileEntry *stack, uint32_t *size,
uint32_t max);
JS_FRIEND_API(void)
EnableRuntimeProfilingStack(JSRuntime *rt, bool enabled);
--- a/js/src/vm/SPSProfiler.cpp
+++ b/js/src/vm/SPSProfiler.cpp
@@ -210,16 +210,17 @@ SPSProfiler::enterNative(const char *str
volatile ProfileEntry *stack = stack_;
volatile uint32_t *size = size_;
uint32_t current = *size;
JS_ASSERT(enabled());
if (current < max_) {
stack[current].setLabel(string);
stack[current].setCppFrame(sp, 0);
+ JS_ASSERT(stack[current].flags() == js::ProfileEntry::IS_CPP_ENTRY);
}
*size = current + 1;
}
void
SPSProfiler::push(const char *string, void *sp, JSScript *script, jsbytecode *pc, bool copy)
{
JS_ASSERT_IF(sp != nullptr, script == nullptr && pc == nullptr);
@@ -230,20 +231,24 @@ SPSProfiler::push(const char *string, vo
volatile uint32_t *size = size_;
uint32_t current = *size;
JS_ASSERT(installed());
if (current < max_) {
volatile ProfileEntry &entry = stack[current];
entry.setLabel(string);
- if (sp != nullptr)
+ if (sp != nullptr) {
entry.setCppFrame(sp, 0);
- else
+ JS_ASSERT(entry.flags() == js::ProfileEntry::IS_CPP_ENTRY);
+ }
+ else {
entry.setJsFrame(script, pc);
+ JS_ASSERT(entry.flags() == 0);
+ }
// Track if mLabel needs a copy.
if (copy)
entry.setFlag(js::ProfileEntry::FRAME_LABEL_COPY);
else
entry.unsetFlag(js::ProfileEntry::FRAME_LABEL_COPY);
}
*size = current + 1;
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -11,16 +11,18 @@ import java.io.File;
import java.io.FileReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.net.URLConnection;
+import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -2715,9 +2717,36 @@ public class GeckoAppShell
// Don't fail silently, tell the user that we weren't able to share the image
private static final void showImageShareFailureToast() {
Toast toast = Toast.makeText(getContext(),
getContext().getResources().getString(R.string.share_image_failed),
Toast.LENGTH_SHORT);
toast.show();
}
+ @WrapElementForJNI(allowMultithread = true)
+ static InputStream createInputStream(URLConnection connection) throws IOException {
+ return connection.getInputStream();
+ }
+
+ @WrapElementForJNI(allowMultithread = true, narrowChars = true)
+ static URLConnection getConnection(String url) throws MalformedURLException, IOException {
+ String spec;
+ if (url.startsWith("android://")) {
+ spec = url.substring(10);
+ } else {
+ spec = url.substring(8);
+ }
+
+ // if the colon got stripped, put it back
+ int colon = spec.indexOf(':');
+ if (colon == -1 || colon > spec.indexOf('/')) {
+ spec = spec.replaceFirst("/", ":/");
+ }
+ return new URL(spec).openConnection();
+ }
+
+ @WrapElementForJNI(allowMultithread = true, narrowChars = true)
+ static String connectionGetMimeType(URLConnection connection) {
+ return connection.getContentType();
+ }
+
}
--- a/mobile/android/base/TelemetryContract.java
+++ b/mobile/android/base/TelemetryContract.java
@@ -59,16 +59,20 @@ public interface TelemetryContract {
// Saving a resource (reader, bookmark, etc) for viewing later.
// Note: Only used in JavaScript for now, but here for completeness.
SAVE("save.1"),
// Sharing content.
SHARE("share.1"),
+ // Undoing a user action.
+ // Note: Only used in JavaScript for now, but here for completeness.
+ UNDO("undo.1"),
+
// Unpinning an item.
UNPIN("unpin.1"),
// Stop holding a resource (reader, bookmark, etc) for viewing later.
// Note: Only used in JavaScript for now, but here for completeness.
UNSAVE("unsave.1"),
// VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING.
@@ -132,16 +136,20 @@ public interface TelemetryContract {
// Action triggered from a pageaction in the URLBar.
// Note: Only used in JavaScript for now, but here for completeness.
PAGEACTION("pageaction"),
// Action triggered from a suggestion provided to the user.
SUGGESTION("suggestion"),
+ // Action triggered from a SuperToast.
+ // Note: Only used in JavaScript for now, but here for completeness.
+ TOAST("toast"),
+
// VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING.
_TEST1("_test_method_1"),
_TEST2("_test_method_2"),
;
private final String string;
Method(final String string) {
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -541,36 +541,40 @@ for var in ('ANDROID_PACKAGE_NAME', 'AND
DEFINES['MANGLED_ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME'].replace('fennec', 'f3nn3c')
DEFINES['MOZ_APP_ABI'] = CONFIG['TARGET_XPCOM_ABI']
if '-march=armv7' in CONFIG['OS_CFLAGS']:
DEFINES['MOZ_MIN_CPU_VERSION'] = 7
else:
DEFINES['MOZ_MIN_CPU_VERSION'] = 5
+generated_recursive_make_targets = ['.aapt.deps', '.locales.deps'] # Captures dependencies on Android manifest and all resources.
+
generated = add_android_eclipse_library_project('FennecResourcesGenerated')
generated.package_name = 'org.mozilla.fennec.resources.generated'
generated.res = OBJDIR + '/res'
+generated.recursive_make_targets += generated_recursive_make_targets
branding = add_android_eclipse_library_project('FennecResourcesBranding')
branding.package_name = 'org.mozilla.fennec.resources.branding'
branding.res = TOPSRCDIR + '/' + CONFIG['MOZ_BRANDING_DIRECTORY'] + '/res'
+branding.recursive_make_targets += generated_recursive_make_targets
main = add_android_eclipse_project('Fennec', OBJDIR + '/AndroidManifest.xml')
main.package_name = 'org.mozilla.gecko'
# These values were extracted from an existing Eclipse project. Use
# Project > Resource > Resource Filters and inspect the resulting
# .project file to modify this list.
main.filtered_resources += [
'1.0-projectRelativePath-matches-false-false-*org/mozilla/gecko/resources/**',
'1.0-projectRelativePath-matches-false-false-*org/mozilla/gecko/tests/**',
]
-main.recursive_make_targets += ['.aapt.deps'] # Captures dependencies on Android manifest and all resources.
+main.recursive_make_targets += generated_recursive_make_targets
main.recursive_make_targets += [OBJDIR + '/generated/' + f for f in mgjar.generated_sources]
main.recursive_make_targets += [OBJDIR + '/generated/' + f for f in gbjar.generated_sources]
main.included_projects += ['../' + generated.name, '../' + branding.name]
main.extra_jars += [CONFIG['ANDROID_COMPAT_LIB']]
main.assets = TOPOBJDIR + '/dist/' + CONFIG['MOZ_APP_NAME'] + '/assets'
main.libs = TOPOBJDIR + '/dist/' + CONFIG['MOZ_APP_NAME'] + '/lib'
main.res = None
@@ -586,16 +590,17 @@ main.add_classpathentry('generated', OBJ
main.add_classpathentry('thirdparty', TOPSRCDIR + '/mobile/android/thirdparty',
dstdir='thirdparty',
ignore_warnings=True)
resources = add_android_eclipse_library_project('FennecResources')
resources.package_name = 'org.mozilla.fennec.resources'
resources.res = SRCDIR + '/resources'
resources.included_projects += ['../' + generated.name, '../' + branding.name]
+resources.recursive_make_targets += generated_recursive_make_targets
main.included_projects += ['../' + resources.name]
main.referenced_projects += [resources.name]
if CONFIG['MOZ_CRASHREPORTER']:
crashreporter = add_android_eclipse_library_project('FennecResourcesCrashReporter')
crashreporter.package_name = 'org.mozilla.fennec.resources.crashreporter'
crashreporter.res = SRCDIR + '/crashreporter/res'
--- a/mobile/android/base/mozglue/generatorannotations/WrapElementForJNI.java
+++ b/mobile/android/base/mozglue/generatorannotations/WrapElementForJNI.java
@@ -39,9 +39,11 @@ public @interface WrapElementForJNI {
*/
boolean allowMultithread() default false;
/**
* If set, the generated stub will not handle uncaught exceptions.
* Any exception must be handled or cleared by the code calling the stub.
*/
boolean noThrow() default false;
+
+ boolean narrowChars() default false;
}
--- a/mobile/android/base/tests/testSimpleDiscovery.js
+++ b/mobile/android/base/tests/testSimpleDiscovery.js
@@ -7,35 +7,44 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/SimpleServiceDiscovery.jsm");
function discovery_observer(subject, topic, data) {
do_print("Observer: " + data);
- let service = SimpleServiceDiscovery.findServiceForLocation(data);
+ let service = SimpleServiceDiscovery.findServiceForID(data);
if (!service)
return;
do_check_eq(service.friendlyName, "Pretend Device");
do_check_eq(service.uuid, "uuid:5ec9ff92-e8b2-4a94-a72c-76b34e6dabb1");
do_check_eq(service.manufacturer, "Copy Cat Inc.");
do_check_eq(service.modelName, "Eureka Dongle");
run_next_test();
};
+var testTarget = {
+ target: "test:service",
+ factory: function(service) { /* dummy */ }
+};
+
add_test(function test_default() {
do_register_cleanup(function cleanup() {
+ SimpleServiceDiscovery.unregisterTarget(testTarget);
Services.obs.removeObserver(discovery_observer, "ssdp-service-found");
});
Services.obs.addObserver(discovery_observer, "ssdp-service-found", false);
+ // We need to register a target or processService will ignore us
+ SimpleServiceDiscovery.registerTarget(testTarget);
+
// Create a pretend service
let service = {
location: "http://mochi.test:8888/tests/robocop/simpleservice.xml",
target: "test:service"
};
do_print("Force a detailed ping from a pretend service");
--- a/mobile/android/base/toolbar/ToolbarEditText.java
+++ b/mobile/android/base/toolbar/ToolbarEditText.java
@@ -255,16 +255,21 @@ public class ToolbarEditText extends Cus
}
beginSettingAutocomplete();
// Replace the existing autocomplete text with new one.
// replace() preserves the autocomplete spans that we set before.
text.replace(autoCompleteStart, textLength, result, autoCompleteStart, resultLength);
+ // Reshow the cursor if there is no longer any autocomplete text.
+ if (autoCompleteStart == resultLength) {
+ setCursorVisible(true);
+ }
+
endSettingAutocomplete();
} else {
// No autocomplete text yet; we should add autocomplete text
// If the result prefix doesn't match the current text,
// the result is stale and we should wait for the another result to come in.
if (resultLength <= textLength ||
--- a/mobile/android/base/webapp/EventListener.java
+++ b/mobile/android/base/webapp/EventListener.java
@@ -3,47 +3,50 @@
* 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.webapp;
import org.mozilla.gecko.ActivityHandlerHelper;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.util.GeckoEventListener;
+import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.NativeEventListener;
+import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.WebappAllocator;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.net.Uri;
import android.util.Log;
import java.io.File;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
-public class EventListener implements GeckoEventListener {
+public class EventListener implements NativeEventListener {
private static final String LOGTAG = "GeckoWebappEventListener";
public void registerEvents() {
EventDispatcher.getInstance().registerGeckoThreadListener(this,
"Webapps:Preinstall",
"Webapps:InstallApk",
"Webapps:Postinstall",
@@ -58,20 +61,20 @@ public class EventListener implements Ge
"Webapps:InstallApk",
"Webapps:Postinstall",
"Webapps:Open",
"Webapps:Uninstall",
"Webapps:GetApkVersions");
}
@Override
- public void handleMessage(String event, JSONObject message) {
+ public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
try {
if (AppConstants.MOZ_ANDROID_SYNTHAPKS && event.equals("Webapps:InstallApk")) {
- installApk(GeckoAppShell.getGeckoInterface().getActivity(), message.getString("filePath"), message.getString("data"));
+ installApk(GeckoAppShell.getGeckoInterface().getActivity(), message, callback);
} else if (event.equals("Webapps:Postinstall")) {
if (AppConstants.MOZ_ANDROID_SYNTHAPKS) {
postInstallWebapp(message.getString("apkPackageName"), message.getString("origin"));
} else {
postInstallWebapp(message.getString("name"),
message.getString("manifestURL"),
message.getString("origin"),
message.getString("iconURL"),
@@ -89,22 +92,22 @@ public class EventListener implements Ge
uninstallWebapp(message.getString("origin"));
} else if (!AppConstants.MOZ_ANDROID_SYNTHAPKS && event.equals("Webapps:Preinstall")) {
String name = message.getString("name");
String manifestURL = message.getString("manifestURL");
String origin = message.getString("origin");
JSONObject obj = new JSONObject();
obj.put("profile", preInstallWebapp(name, manifestURL, origin).toString());
- EventDispatcher.sendResponse(message, obj);
+ callback.sendSuccess(obj);
} else if (event.equals("Webapps:GetApkVersions")) {
JSONObject obj = new JSONObject();
obj.put("versions", getApkVersions(GeckoAppShell.getGeckoInterface().getActivity(),
- message.getJSONArray("packageNames")));
- EventDispatcher.sendResponse(message, obj);
+ message.getStringArray("packageNames")));
+ callback.sendSuccess(obj);
}
} catch (Exception e) {
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
}
}
// Not used by MOZ_ANDROID_SYNTHAPKS.
public static File preInstallWebapp(String aTitle, String aURI, String aOrigin) {
@@ -175,45 +178,47 @@ public class EventListener implements Ge
if (proc.processName.equals(targetProcessName)) {
android.os.Process.killProcess(proc.pid);
break;
}
}
}
}
- public static void installApk(final Activity context, String filePath, String data) {
- // This is the data that mozApps.install sent to Webapps.jsm.
- JSONObject argsObj = null;
+ public static void installApk(final Activity context, NativeJSObject message, EventCallback callback) {
+ final JSONObject messageData;
// We get the manifest url out of javascript here so we can use it as a checksum
// in a minute, when a package has been installed.
String manifestUrl = null;
+ String filePath = null;
+
try {
- argsObj = new JSONObject(data);
- manifestUrl = argsObj.getJSONObject("app").getString("manifestURL");
+ filePath = message.getString("filePath");
+ messageData = new JSONObject(message.getObject("data").toString());
+ manifestUrl = messageData.getJSONObject("app").getString("manifestURL");
} catch (JSONException e) {
- Log.e(LOGTAG, "can't get manifest URL from JSON data", e);
- // TODO: propagate the error back to the mozApps.install caller.
+ Log.wtf(LOGTAG, "Error getting file path and data", e);
+ callback.sendError("Error getting file path and data: " + e.toString());
return;
}
// We will check the manifestUrl from the one in the APK.
// Thus, we can have a one-to-one mapping of apk to receiver.
- final InstallListener receiver = new InstallListener(manifestUrl, argsObj);
+ final InstallListener receiver = new InstallListener(manifestUrl, messageData);
// Listen for packages being installed.
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package");
context.registerReceiver(receiver, filter);
File file = new File(filePath);
if (!file.exists()) {
Log.wtf(LOGTAG, "APK file doesn't exist at path " + filePath);
- // TODO: propagate the error back to the mozApps.install caller.
+ callback.sendError("APK file doesn't exist at path " + filePath);
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
// Now call the package installer.
ActivityHandlerHelper.startIntentForActivity(context, intent, new ActivityResultHandler() {
@@ -234,25 +239,19 @@ public class EventListener implements Ge
}
}
}
});
}
private static final int DEFAULT_VERSION_CODE = -1;
- public static JSONObject getApkVersions(Activity context, JSONArray packageNames) {
+ public static JSONObject getApkVersions(Activity context, String[] packageNames) {
Set<String> packageNameSet = new HashSet<String>();
- for (int i = 0; i < packageNames.length(); i++) {
- try {
- packageNameSet.add(packageNames.getString(i));
- } catch (JSONException e) {
- Log.w(LOGTAG, "exception populating settings item", e);
- }
- }
+ packageNameSet.addAll(Arrays.asList(packageNames));
final PackageManager pm = context.getPackageManager();
List<ApplicationInfo> apps = pm.getInstalledApplications(0);
JSONObject jsonMessage = new JSONObject();
for (ApplicationInfo app : apps) {
if (packageNameSet.contains(app.packageName)) {
--- a/mobile/android/chrome/content/CastingApps.js
+++ b/mobile/android/chrome/content/CastingApps.js
@@ -1,26 +1,33 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
+// Define service targets. We should consider moving these to their respective
+// JSM files, but we left them here to allow for better lazy JSM loading.
+var rokuTarget = {
+ target: "roku:ecp",
+ factory: function(aService) {
+ Cu.import("resource://gre/modules/RokuApp.jsm");
+ return new RokuApp(aService);
+ }
+};
+
var CastingApps = {
_castMenuId: -1,
init: function ca_init() {
if (!this.isEnabled()) {
return;
}
- // Register a service target
- SimpleServiceDiscovery.registerTarget("roku:ecp", function(aService) {
- Cu.import("resource://gre/modules/RokuApp.jsm");
- return new RokuApp(aService);
- });
+ // Register targets
+ SimpleServiceDiscovery.registerTarget(rokuTarget);
// Search for devices continuously every 120 seconds
SimpleServiceDiscovery.search(120 * 1000);
this._castMenuId = NativeWindow.contextmenus.add(
Strings.browser.GetStringFromName("contextmenu.castToScreen"),
this.filterCast,
this.openExternal.bind(this)
@@ -247,16 +254,20 @@ var CastingApps = {
CastingApps.openExternal(video, 0, 0);
return;
}
}
}
},
_findCastableVideo: function _findCastableVideo(aBrowser) {
+ if (!aBrowser) {
+ return null;
+ }
+
// Scan for a <video> being actively cast. Also look for a castable <video>
// on the page.
let castableVideo = null;
let videos = aBrowser.contentDocument.querySelectorAll("video");
for (let video of videos) {
let unwrappedVideo = XPCNativeWrapper.unwrap(video);
if (unwrappedVideo.mozIsCasting) {
// This <video> is cast-active. Break out of loop.
@@ -446,9 +457,8 @@ var CastingApps = {
}
let status = aRemoteMedia.status;
if (status == "completed") {
this.closeExternal();
}
}
};
-
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -984,16 +984,17 @@ var BrowserApp = {
if (aShowUndoToast) {
let message = Strings.browser.formatStringFromName("undoCloseToast.message", [title], 1);
NativeWindow.toast.show(message, "short", {
button: {
icon: "drawable://undo_button_icon",
label: Strings.browser.GetStringFromName("undoCloseToast.action2"),
callback: function() {
+ UITelemetry.addEvent("undo.1", "toast", null, "closetab");
let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
ss.undoCloseTab(window, 0);
}
}
});
}
},
--- a/mobile/android/modules/SimpleServiceDiscovery.jsm
+++ b/mobile/android/modules/SimpleServiceDiscovery.jsm
@@ -7,19 +7,19 @@
this.EXPORTED_SYMBOLS = ["SimpleServiceDiscovery"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-function log(msg) {
- Services.console.logStringMessage("[SSDP] " + msg);
-}
+// Define the "log" function as a binding of the Log.d function so it specifies
+// the "debug" priority and a log tag.
+let log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "SSDP");
XPCOMUtils.defineLazyGetter(this, "converter", function () {
let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
conv.charset = "utf8";
return conv;
});
// Spec information:
@@ -64,48 +64,42 @@ var SimpleServiceDiscovery = {
return aURL;
},
// nsIUDPSocketListener implementation
onPacketReceived: function(aSocket, aMessage) {
// Listen for responses from specific targets. There could be more than one
// available.
let response = aMessage.data.split("\n");
- let location;
- let target;
- let valid = false;
- response.some(function(row) {
- let header = row.toUpperCase();
- if (header.startsWith("LOCATION")) {
- location = row.substr(10).trim();
- } else if (header.startsWith("ST")) {
- target = row.substr(4).trim();
- if (this._targets.has(target)) {
- valid = true;
- }
+ let service = {};
+ response.forEach(function(row) {
+ let name = row.toUpperCase();
+ if (name.startsWith("LOCATION")) {
+ service.location = row.substr(10).trim();
+ } else if (name.startsWith("ST")) {
+ service.target = row.substr(4).trim();
+ } else if (name.startsWith("SERVER")) {
+ service.server = row.substr(8).trim();
+ }
+ }.bind(this));
+
+ if (service.location && this._targets.has(service.target)) {
+ service.location = this._forceTrailingSlash(service.location);
+
+ // We add the server as an additional way to filter services
+ if (!("server" in service)) {
+ service.server = null;
}
- if (location && valid) {
- location = this._forceTrailingSlash(location);
-
- // When we find a valid response, package up the service information
- // and pass it on.
- let service = {
- location: location,
- target: target
- };
-
- try {
- this._processService(service);
- } catch (e) {}
-
- return true;
- }
- return false;
- }.bind(this));
+ // When we find a valid response, package up the service information
+ // and pass it on.
+ try {
+ this._processService(service);
+ } catch (e) {}
+ }
},
onStopListening: function(aSocket, aStatus) {
// This is fired when the socket is closed expectedly or unexpectedly.
// nsITimer.cancel() is a no-op if the timer is not active.
this._searchTimeout.cancel();
this._searchSocket = null;
},
@@ -213,58 +207,99 @@ var SimpleServiceDiscovery = {
_searchShutdown: function _searchShutdown() {
if (this._searchSocket) {
// This will call onStopListening.
this._searchSocket.close();
// Clean out any stale services
for (let [key, service] of this._services) {
if (service.lastPing != this._searchTimestamp) {
- Services.obs.notifyObservers(null, EVENT_SERVICE_LOST, service.location);
- this._services.delete(service.location);
+ Services.obs.notifyObservers(null, EVENT_SERVICE_LOST, service.uuid);
+ this._services.delete(service.uuid);
}
}
}
},
- registerTarget: function registerTarget(aTarget, aAppFactory) {
+ registerTarget: function registerTarget(aTarget) {
+ // We must have "target" and "factory" defined
+ if (!("target" in aTarget) || !("factory" in aTarget)) {
+ // Fatal for registration
+ throw "Registration requires a target and a location";
+ }
+
// Only add if we don't already know about this target
- if (!this._targets.has(aTarget)) {
- this._targets.set(aTarget, { target: aTarget, factory: aAppFactory });
+ if (!this._targets.has(aTarget.target)) {
+ this._targets.set(aTarget.target, aTarget);
+ }
+ },
+
+ unregisterTarget: function unregisterTarget(aTarget) {
+ // We must have "target" and "factory" defined
+ if (!("target" in aTarget) || !("factory" in aTarget)) {
+ return;
+ }
+
+ // Only remove if we know about this target
+ if (this._targets.has(aTarget.target)) {
+ this._targets.delete(aTarget.target);
}
},
findAppForService: function findAppForService(aService) {
if (!aService || !aService.target) {
return null;
}
// Find the registration for the target
if (this._targets.has(aService.target)) {
return this._targets.get(aService.target).factory(aService);
}
return null;
},
- findServiceForLocation: function findServiceForLocation(aLocation) {
- if (this._services.has(aLocation)) {
- return this._services.get(aLocation);
+ findServiceForID: function findServiceForID(aUUID) {
+ if (this._services.has(aUUID)) {
+ return this._services.get(aUUID);
}
return null;
},
// Returns an array copy of the active services
get services() {
let array = [];
for (let [key, service] of this._services) {
array.push(service);
}
return array;
},
+ // Returns false if the service does not match the target's filters
+ _filterService: function _filterService(aService) {
+ let target = this._targets.get(aService.target);
+ if (!target) {
+ return false;
+ }
+
+ // If we have no filter, everything passes
+ if (!("filters" in target)) {
+ return true;
+ }
+
+ // If any filter fails, the service fails
+ let filters = target.filters;
+ for (let filter in filters) {
+ if (filter in aService && aService[filter] != filters[filter]) {
+ return false;
+ }
+ }
+
+ return true;
+ },
+
_processService: function _processService(aService) {
// Use the REST api to request more information about this service
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
xhr.open("GET", aService.location, true);
xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
xhr.overrideMimeType("text/xml");
xhr.addEventListener("load", (function() {
@@ -273,22 +308,27 @@ var SimpleServiceDiscovery = {
aService.appsURL = xhr.getResponseHeader("Application-URL");
if (aService.appsURL && !aService.appsURL.endsWith("/"))
aService.appsURL += "/";
aService.friendlyName = doc.querySelector("friendlyName").textContent;
aService.uuid = doc.querySelector("UDN").textContent;
aService.manufacturer = doc.querySelector("manufacturer").textContent;
aService.modelName = doc.querySelector("modelName").textContent;
+ // Filter out services that do not match the target filter
+ if (!this._filterService(aService)) {
+ return;
+ }
+
// Only add and notify if we don't already know about this service
- if (!this._services.has(aService.location)) {
- this._services.set(aService.location, aService);
- Services.obs.notifyObservers(null, EVENT_SERVICE_FOUND, aService.location);
+ if (!this._services.has(aService.uuid)) {
+ this._services.set(aService.uuid, aService);
+ Services.obs.notifyObservers(null, EVENT_SERVICE_FOUND, aService.uuid);
}
// Make sure we remember this service is not stale
- this._services.get(aService.location).lastPing = this._searchTimestamp;
+ this._services.get(aService.uuid).lastPing = this._searchTimestamp;
}
}).bind(this), false);
xhr.send(null);
}
}
--- a/mobile/android/modules/WebappManager.jsm
+++ b/mobile/android/modules/WebappManager.jsm
@@ -98,17 +98,23 @@ this.WebappManager = {
aMessageManager.sendAsyncMessage("Webapps:Install:Return:KO", aMessage);
debug("error downloading APK: " + ex);
return;
}
sendMessageToJava({
type: "Webapps:InstallApk",
filePath: filePath,
- data: JSON.stringify(aMessage),
+ data: aMessage,
+ }, (data, error) => {
+ if (!!error) {
+ aMessage.error = error;
+ aMessageManager.sendAsyncMessage("Webapps:Install:Return:KO", aMessage);
+ debug("error downloading APK: " + error);
+ }
});
}).bind(this)); },
_downloadApk: function(aManifestUrl) {
debug("_downloadApk for " + aManifestUrl);
let deferred = Promise.defer();
// Get the endpoint URL and convert it to an nsIURI/nsIURL object.
@@ -237,18 +243,18 @@ this.WebappManager = {
// The manifest url may be subtly different between the
// time the APK was built and the APK being installed.
// Thus, we should take the APK as the source of truth.
message.app.manifestURL = aData.manifestURL;
message.app.manifest = aData.manifest;
message.app.apkPackageName = aData.apkPackageName;
message.profilePath = aData.profilePath;
- message.autoInstall = true;
message.mm = mm;
+ message.apkInstall = true;
DOMApplicationRegistry.registryReady.then(() => {
switch (aData.type) { // can be hosted or packaged.
case "hosted":
DOMApplicationRegistry.doInstall(message, mm);
break;
case "packaged":
@@ -488,17 +494,17 @@ this.WebappManager = {
let msg = {
app: apk.app,
// TODO: figure out why Webapps:InstallApk needs the "from" property.
from: apk.app.installOrigin,
};
sendMessageToJava({
type: "Webapps:InstallApk",
filePath: apk.filePath,
- data: JSON.stringify(msg),
+ data: msg,
});
}
} else {
// The user cancelled the notification, so remove the downloaded APKs.
for (let apk of downloadedApks) {
try {
yield OS.file.remove(apk.filePath);
} catch(ex) {
--- a/toolkit/components/social/FrameWorker.jsm
+++ b/toolkit/components/social/FrameWorker.jsm
@@ -52,16 +52,18 @@ this.getFrameWorkerHandle =
existingWorker = workerCache[url] = new _Worker(browserPromise, options);
}
// message the content so it can establish a new connection with the worker.
let portid = _nextPortId++;
existingWorker.browserPromise.then(browser => {
browser.messageManager.sendAsyncMessage("frameworker:connect",
{ portId: portid });
+ }).then(null, (ex) => {
+ Cu.reportError("Could not send frameworker:connect: " + ex);
});
// return the pseudo worker object.
let port = new ParentPort(portid, existingWorker.browserPromise, clientWindow);
existingWorker.ports.set(portid, port);
return new WorkerHandle(port, existingWorker);
};
// A "_Worker" is an internal representation of a worker. It's never returned
--- a/toolkit/devtools/LayoutHelpers.jsm
+++ b/toolkit/devtools/LayoutHelpers.jsm
@@ -468,16 +468,20 @@ LayoutHelpers.prototype = {
// If the boxType is border, no need to go any further, we're done
if (region === "border") {
return this._getBoxQuadsFromRect(borderRect, node);
}
// Else, need to get margin/padding/border distances
let style = node.ownerDocument.defaultView.getComputedStyle(node);
+ if (!style) {
+ return null;
+ }
+
let camel = s => s.substring(0, 1).toUpperCase() + s.substring(1);
let distances = {border:{}, padding:{}, margin: {}};
for (let side of ["top", "right", "bottom", "left"]) {
distances.border[side] = this._parseNb(style["border" + camel(side) + "Width"]);
distances.padding[side] = this._parseNb(style["padding" + camel(side)]);
distances.margin[side] = this._parseNb(style["margin" + camel(side)]);
}
--- a/toolkit/devtools/server/actors/inspector.js
+++ b/toolkit/devtools/server/actors/inspector.js
@@ -57,16 +57,18 @@ const {Arg, Option, method, RetVal, type
const {LongStringActor, ShortLongString} = require("devtools/server/actors/string");
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
const object = require("sdk/util/object");
const events = require("sdk/event/core");
const {Unknown} = require("sdk/platform/xpcom");
const {Class} = require("sdk/core/heritage");
const {PageStyleActor} = require("devtools/server/actors/styles");
const {HighlighterActor} = require("devtools/server/actors/highlighter");
+const {getLayoutChangesObserver, releaseLayoutChangesObserver} =
+ require("devtools/server/actors/layout");
const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog";
const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20;
const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
const XHTML_NS = "http://www.w3.org/1999/xhtml";
const IMAGE_FETCHING_TIMEOUT = 500;
// The possible completions to a ':' with added score to give certain values
@@ -172,16 +174,20 @@ exports.setValueSummaryLength = function
*/
var NodeActor = exports.NodeActor = protocol.ActorClass({
typeName: "domnode",
initialize: function(walker, node) {
protocol.Actor.prototype.initialize.call(this, null);
this.walker = walker;
this.rawNode = node;
+
+ // Storing the original display of the node, to track changes when reflows
+ // occur
+ this.wasDisplayed = this.isDisplayed;
},
toString: function() {
return "[NodeActor " + this.actorID + " for " + this.rawNode.toString() + "]";
},
/**
* Instead of storing a connection object, the NodeActor gets its connection
@@ -222,16 +228,18 @@ var NodeActor = exports.NodeActor = prot
// doctype attributes
name: this.rawNode.name,
publicId: this.rawNode.publicId,
systemId: this.rawNode.systemId,
attrs: this.writeAttrs(),
pseudoClassLocks: this.writePseudoClassLocks(),
+
+ isDisplayed: this.isDisplayed,
};
if (this.isDocumentElement()) {
form.isDocumentElement = true;
}
if (this.rawNode.nodeValue) {
// We only include a short version of the value if it's longer than
@@ -242,16 +250,38 @@ var NodeActor = exports.NodeActor = prot
} else {
form.shortValue = this.rawNode.nodeValue;
}
}
return form;
},
+ get computedStyle() {
+ if (Cu.isDeadWrapper(this.rawNode) ||
+ this.rawNode.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE ||
+ !this.rawNode.ownerDocument) {
+ return null;
+ }
+ return this.rawNode.ownerDocument.defaultView.getComputedStyle(this.rawNode);
+ },
+
+ /**
+ * Is the node's display computed style value other than "none"
+ */
+ get isDisplayed() {
+ let style = this.computedStyle;
+ if (!style) {
+ // Consider all non-element nodes as displayed
+ return true;
+ } else {
+ return style.display !== "none";
+ }
+ },
+
writeAttrs: function() {
if (!this.rawNode.attributes) {
return undefined;
}
return [{namespace: attr.namespace, name: attr.name, value: attr.value }
for (attr of this.rawNode.attributes)];
},
@@ -543,16 +573,22 @@ let NodeFront = protocol.FrontClass(Node
get attributes() this._form.attrs,
get pseudoClassLocks() this._form.pseudoClassLocks || [],
hasPseudoClassLock: function(pseudo) {
return this.pseudoClassLocks.some(locked => locked === pseudo);
},
+ get isDisplayed() {
+ // The NodeActor's form contains the isDisplayed information as a boolean
+ // starting from FF32. Before that, the property is missing
+ return "isDisplayed" in this._form ? this._form.isDisplayed : true;
+ },
+
getNodeValue: protocol.custom(function() {
if (!this.incompleteValue) {
return delayedResolve(new ShortLongString(this.shortValue));
} else {
return this._getNodeValue();
}
}, {
impl: "_getNodeValue"
@@ -831,16 +867,20 @@ var WalkerActor = protocol.ActorClass({
type: "pickerNodeHovered",
node: Arg(0, "disconnectedNode")
},
"highlighter-ready" : {
type: "highlighter-ready"
},
"highlighter-hide" : {
type: "highlighter-hide"
+ },
+ "display-change" : {
+ type: "display-change",
+ nodes: Arg(0, "array:domnode")
}
},
/**
* Create the WalkerActor
* @param DebuggerServerConnection conn
* The server connection.
*/
@@ -870,16 +910,20 @@ var WalkerActor = protocol.ActorClass({
this.onFrameUnload = this.onFrameUnload.bind(this);
events.on(tabActor, "will-navigate", this.onFrameUnload);
events.on(tabActor, "navigate", this.onFrameLoad);
// Ensure that the root document node actor is ready and
// managed.
this.rootNode = this.document();
+
+ this.reflowObserver = getLayoutChangesObserver(this.tabActor);
+ this._onReflows = this._onReflows.bind(this);
+ this.reflowObserver.on("reflows", this._onReflows);
},
// Returns the JSON representation of this object over the wire.
form: function() {
return {
actor: this.actorID,
root: this.rootNode.form()
}
@@ -889,27 +933,32 @@ var WalkerActor = protocol.ActorClass({
return "[WalkerActor " + this.actorID + "]";
},
destroy: function() {
this._hoveredNode = null;
this.clearPseudoClassLocks();
this._activePseudoClassLocks = null;
this.rootDoc = null;
+
+ this.reflowObserver.off("reflows", this._onReflows);
+ this.reflowObserver = null;
+ releaseLayoutChangesObserver(this.tabActor);
+
events.emit(this, "destroyed");
protocol.Actor.prototype.destroy.call(this);
},
release: method(function() {}, { release: true }),
unmanage: function(actor) {
if (actor instanceof NodeActor) {
if (this._activePseudoClassLocks &&
this._activePseudoClassLocks.has(actor)) {
- this.clearPsuedoClassLocks(actor);
+ this.clearPseudoClassLocks(actor);
}
this._refMap.delete(actor.rawNode);
}
protocol.Actor.prototype.unmanage.call(this, actor);
},
_ref: function(node) {
let actor = this._refMap.get(node);
@@ -923,16 +972,34 @@ var WalkerActor = protocol.ActorClass({
this._refMap.set(node, actor);
if (node.nodeType === Ci.nsIDOMNode.DOCUMENT_NODE) {
this._watchDocument(actor);
}
return actor;
},
+ _onReflows: function(reflows) {
+ // Going through the nodes the walker knows about, see which ones have
+ // had their display changed and send a display-change event if any
+ let changes = [];
+ for (let [node, actor] of this._refMap) {
+ let isDisplayed = actor.isDisplayed;
+ if (isDisplayed !== actor.wasDisplayed) {
+ changes.push(actor);
+ // Updating the original value
+ actor.wasDisplayed = isDisplayed;
+ }
+ }
+
+ if (changes.length) {
+ events.emit(this, "display-change", changes);
+ }
+ },
+
/**
* This is kept for backward-compatibility reasons with older remote targets.
* Targets prior to bug 916443.
*
* pick/cancelPick are used to pick a node on click on the content
* document. But in their implementation prior to bug 916443, they don't allow
* highlighting on hover.
* The client-side now uses the highlighter actor's pick and cancelPick
--- a/tools/profiler/ProfileEntry.cpp
+++ b/tools/profiler/ProfileEntry.cpp
@@ -54,18 +54,18 @@ ProfileEntry::ProfileEntry(char aTagName
, mTagName(aTagName)
{ }
ProfileEntry::ProfileEntry(char aTagName, Address aTagAddress)
: mTagAddress(aTagAddress)
, mTagName(aTagName)
{ }
-ProfileEntry::ProfileEntry(char aTagName, int aTagLine)
- : mTagLine(aTagLine)
+ProfileEntry::ProfileEntry(char aTagName, int aTagInt)
+ : mTagInt(aTagInt)
, mTagName(aTagName)
{ }
ProfileEntry::ProfileEntry(char aTagName, char aTagChar)
: mTagChar(aTagChar)
, mTagName(aTagName)
{ }
@@ -89,28 +89,28 @@ void* ProfileEntry::get_tagPtr() {
void ProfileEntry::log()
{
// There is no compiler enforced mapping between tag chars
// and union variant fields, so the following was derived
// by looking through all the use points of TableTicker.cpp.
// mTagMarker (ProfilerMarker*) m
// mTagData (const char*) c,s
// mTagPtr (void*) d,l,L,B (immediate backtrace), S(start-of-stack)
- // mTagLine (int) n,f
+ // mTagInt (int) n,f,y
// mTagChar (char) h
// mTagFloat (double) r,t,p,R (resident memory)
switch (mTagName) {
case 'm':
LOGF("%c \"%s\"", mTagName, mTagMarker->GetMarkerName()); break;
case 'c': case 's':
LOGF("%c \"%s\"", mTagName, mTagData); break;
case 'd': case 'l': case 'L': case 'B': case 'S':
LOGF("%c %p", mTagName, mTagPtr); break;
- case 'n': case 'f':
- LOGF("%c %d", mTagName, mTagLine); break;
+ case 'n': case 'f': case 'y':
+ LOGF("%c %d", mTagName, mTagInt); break;
case 'h':
LOGF("%c \'%c\'", mTagName, mTagChar); break;
case 'r': case 't': case 'p': case 'R':
LOGF("%c %f", mTagName, mTagFloat); break;
default:
LOGF("'%c' unknown_tag", mTagName); break;
}
}
@@ -357,17 +357,17 @@ void ThreadProfile::StreamJSObject(JSStr
if (sample) {
b.NameValue("rss", entry.mTagFloat);
}
}
break;
case 'f':
{
if (sample) {
- b.NameValue("frameNumber", entry.mTagLine);
+ b.NameValue("frameNumber", entry.mTagInt);
}
}
break;
case 't':
{
if (sample) {
b.NameValue("time", entry.mTagFloat);
}
@@ -394,47 +394,55 @@ void ThreadProfile::StreamJSObject(JSStr
b.NameValue("location", "(root)");
b.EndObject();
int framePos = (readPos + 1) % mEntrySize;
ProfileEntry frame = mEntries[framePos];
while (framePos != mLastFlushPos && frame.mTagName != 's') {
int incBy = 1;
frame = mEntries[framePos];
+
// Read ahead to the next tag, if it's a 'd' tag process it now
const char* tagStringData = frame.mTagData;
int readAheadPos = (framePos + 1) % mEntrySize;
char tagBuff[DYNAMIC_MAX_STRING];
// Make sure the string is always null terminated if it fills up
// DYNAMIC_MAX_STRING-2
tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
tagStringData = processDynamicTag(framePos, &incBy, tagBuff);
}
// Write one frame. It can have either
// 1. only location - 'l' containing a memory address
- // 2. location and line number - 'c' followed by 'd's and an optional 'n'
+ // 2. location and line number - 'c' followed by 'd's,
+ // an optional 'n' and an optional 'y'
if (frame.mTagName == 'l') {
b.BeginObject();
// Bug 753041
// We need a double cast here to tell GCC that we don't want to sign
// extend 32-bit addresses starting with 0xFXXXXXX.
unsigned long long pc = (unsigned long long)(uintptr_t)frame.mTagPtr;
snprintf(tagBuff, DYNAMIC_MAX_STRING, "%#llx", pc);
b.NameValue("location", tagBuff);
b.EndObject();
} else if (frame.mTagName == 'c') {
b.BeginObject();
b.NameValue("location", tagStringData);
readAheadPos = (framePos + incBy) % mEntrySize;
if (readAheadPos != mLastFlushPos &&
mEntries[readAheadPos].mTagName == 'n') {
- b.NameValue("line", mEntries[readAheadPos].mTagLine);
+ b.NameValue("line", mEntries[readAheadPos].mTagInt);
+ incBy++;
+ }
+ readAheadPos = (framePos + incBy) % mEntrySize;
+ if (readAheadPos != mLastFlushPos &&
+ mEntries[readAheadPos].mTagName == 'y') {
+ b.NameValue("category", mEntries[readAheadPos].mTagInt);
incBy++;
}
b.EndObject();
}
framePos = (framePos + incBy) % mEntrySize;
}
b.EndArray();
}
--- a/tools/profiler/ProfileEntry.h
+++ b/tools/profiler/ProfileEntry.h
@@ -56,17 +56,17 @@ private:
union {
const char* mTagData;
char mTagChars[sizeof(void*)];
void* mTagPtr;
ProfilerMarker* mTagMarker;
float mTagFloat;
Address mTagAddress;
uintptr_t mTagOffset;
- int mTagLine;
+ int mTagInt;
char mTagChar;
};
char mTagName;
};
#pragma pack(pop)
typedef void (*IterateTagsCallback)(const ProfileEntry& entry, const char* tagStringData);
--- a/tools/profiler/PseudoStack.h
+++ b/tools/profiler/PseudoStack.h
@@ -357,16 +357,17 @@ public:
}
volatile StackEntry &entry = mStack[mStackPointer];
// Make sure we increment the pointer after the name has
// been written such that mStack is always consistent.
entry.setLabel(aName);
entry.setCppFrame(aStackAddress, line);
+ MOZ_ASSERT(entry.flags() == js::ProfileEntry::IS_CPP_ENTRY);
uint32_t uint_category = static_cast<uint32_t>(aCategory);
MOZ_ASSERT(
uint_category >= static_cast<uint32_t>(js::ProfileEntry::Category::FIRST) &&
uint_category <= static_cast<uint32_t>(js::ProfileEntry::Category::LAST));
entry.setFlag(uint_category);
--- a/tools/profiler/TableTicker.cpp
+++ b/tools/profiler/TableTicker.cpp
@@ -369,19 +369,28 @@ void addProfileEntry(volatile StackEntry
aProfile.addTag(ProfileEntry('c', sampleLabel));
// XXX: Bug 1010578. Don't assume a CPP entry and try to get the
// line for js entries as well.
if (entry.isCpp()) {
lineno = entry.line();
}
}
+
if (lineno != -1) {
aProfile.addTag(ProfileEntry('n', lineno));
}
+
+ uint32_t category = entry.category();
+ MOZ_ASSERT(!(category & StackEntry::IS_CPP_ENTRY));
+ MOZ_ASSERT(!(category & StackEntry::FRAME_LABEL_COPY));
+
+ if (category) {
+ aProfile.addTag(ProfileEntry('y', (int)category));
+ }
}
#if defined(USE_NS_STACKWALK) || defined(USE_EHABI_STACKWALK)
typedef struct {
void** array;
void** sp_array;
size_t size;
size_t count;
--- a/tools/profiler/tests/gtest/ThreadProfileTest.cpp
+++ b/tools/profiler/tests/gtest/ThreadProfileTest.cpp
@@ -33,17 +33,17 @@ TEST(ThreadProfile, InsertTagsNoWrap) {
int test_size = 50;
for (int i = 0; i < test_size; i++) {
tp.addTag(ProfileEntry('t', i));
}
ASSERT_TRUE(tp.mEntries != nullptr);
int readPos = tp.mReadPos;
while (readPos != tp.mWritePos) {
ASSERT_TRUE(tp.mEntries[readPos].mTagName == 't');
- ASSERT_TRUE(tp.mEntries[readPos].mTagLine == readPos);
+ ASSERT_TRUE(tp.mEntries[readPos].mTagInt == readPos);
readPos = (readPos + 1) % tp.mEntrySize;
}
}
// See if wrapping works as it should in the basic case
TEST(ThreadProfile, InsertTagsWrap) {
PseudoStack stack;
Thread::tid_t tid = 1000;
@@ -56,14 +56,14 @@ TEST(ThreadProfile, InsertTagsWrap) {
tp.addTag(ProfileEntry('t', i));
}
ASSERT_TRUE(tp.mEntries != nullptr);
int readPos = tp.mReadPos;
int ctr = 0;
while (readPos != tp.mWritePos) {
ASSERT_TRUE(tp.mEntries[readPos].mTagName == 't');
// the first few tags were discarded when we wrapped
- ASSERT_TRUE(tp.mEntries[readPos].mTagLine == ctr + (test_size - tags));
+ ASSERT_TRUE(tp.mEntries[readPos].mTagInt == ctr + (test_size - tags));
ctr++;
readPos = (readPos + 1) % tp.mEntrySize;
}
}
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -201,16 +201,26 @@ AndroidBridge::Init(JNIEnv *jEnv)
jclass eglClass = getClassGlobalRef("com/google/android/gles_jni/EGLSurfaceImpl");
if (eglClass) {
jEGLSurfacePointerField = getField("mEGLSurface", "I");
} else {
jEGLSurfacePointerField = 0;
}
+ jChannels = getClassGlobalRef("java/nio/channels/Channels");
+ jChannelCreate = jEnv->GetStaticMethodID(jChannels, "newChannel", "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
+
+ jReadableByteChannel = getClassGlobalRef("java/nio/channels/ReadableByteChannel");
+ jByteBufferRead = jEnv->GetMethodID(jReadableByteChannel, "read", "(Ljava/nio/ByteBuffer;)I");
+
+ jInputStream = getClassGlobalRef("java/io/InputStream");
+ jClose = jEnv->GetMethodID(jInputStream, "close", "()V");
+ jAvailable = jEnv->GetMethodID(jInputStream, "available", "()I");
+
InitAndroidJavaWrappers(jEnv);
// jEnv should NOT be cached here by anything -- the jEnv here
// is not valid for the real gecko main thread, which is set
// at SetMainThread time.
return true;
}
@@ -2088,8 +2098,46 @@ AndroidBridge::RunDelayedTasks()
mDelayedTaskQueue.RemoveElementAt(0);
Task* task = nextTask->GetTask();
delete nextTask;
task->Run();
}
return -1;
}
+
+jobject AndroidBridge::ChannelCreate(jobject stream) {
+ JNIEnv *env = GetJNIForThread();
+ env->PushLocalFrame(1);
+ jobject channel = env->CallStaticObjectMethod(sBridge->jReadableByteChannel, sBridge->jChannelCreate, stream);
+ return env->PopLocalFrame(channel);
+}
+
+void AndroidBridge::InputStreamClose(jobject obj) {
+ JNIEnv *env = GetJNIForThread();
+ AutoLocalJNIFrame jniFrame(env, 1);
+ env->CallVoidMethod(obj, sBridge->jClose);
+}
+
+uint32_t AndroidBridge::InputStreamAvailable(jobject obj) {
+ JNIEnv *env = GetJNIForThread();
+ AutoLocalJNIFrame jniFrame(env, 1);
+ return env->CallIntMethod(obj, sBridge->jAvailable);
+}
+
+nsresult AndroidBridge::InputStreamRead(jobject obj, char *aBuf, uint32_t aCount, uint32_t *aRead) {
+ JNIEnv *env = GetJNIForThread();
+ AutoLocalJNIFrame jniFrame(env, 1);
+ jobject arr = env->NewDirectByteBuffer(aBuf, aCount);
+ jint read = env->CallIntMethod(obj, sBridge->jByteBufferRead, arr);
+
+ if (env->ExceptionCheck()) {
+ env->ExceptionClear();
+ return NS_ERROR_FAILURE;
+ }
+
+ if (read <= 0) {
+ *aRead = 0;
+ return NS_OK;
+ }
+ *aRead = read;
+ return NS_OK;
+}
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -112,17 +112,16 @@ public:
return mTask;
}
private:
Task* mTask;
TimeStamp mRunTime;
};
-
class AndroidBridge MOZ_FINAL : public mozilla::layers::GeckoContentController
{
public:
enum {
// Values for NotifyIME, in addition to values from the Gecko
// IMEMessage enum; use negative values here to prevent conflict
NOTIFY_IME_OPEN_VKB = -2,
NOTIFY_IME_REPLY_EVENT = -1,
@@ -339,16 +338,23 @@ public:
static jstring NewJavaString(AutoLocalJNIFrame* frame, const char* string);
static jstring NewJavaString(AutoLocalJNIFrame* frame, const nsACString& string);
static jclass GetClassGlobalRef(JNIEnv* env, const char* className);
static jfieldID GetFieldID(JNIEnv* env, jclass jClass, const char* fieldName, const char* fieldType);
static jfieldID GetStaticFieldID(JNIEnv* env, jclass jClass, const char* fieldName, const char* fieldType);
static jmethodID GetMethodID(JNIEnv* env, jclass jClass, const char* methodName, const char* methodType);
static jmethodID GetStaticMethodID(JNIEnv* env, jclass jClass, const char* methodName, const char* methodType);
+
+ static jobject ChannelCreate(jobject);
+
+ static void InputStreamClose(jobject obj);
+ static uint32_t InputStreamAvailable(jobject obj);
+ static nsresult InputStreamRead(jobject obj, char *aBuf, uint32_t aCount, uint32_t *aRead);
+
protected:
static StaticRefPtr<AndroidBridge> sBridge;
nsTArray<nsCOMPtr<nsIMobileMessageCallback> > mSmsRequests;
// the global JavaVM
JavaVM *mJavaVM;
// the JNIEnv for the main thread
@@ -373,16 +379,26 @@ protected:
bool mHasNativeBitmapAccess;
bool mHasNativeWindowAccess;
bool mHasNativeWindowFallback;
int mAPIVersion;
bool QueueSmsRequest(nsIMobileMessageCallback* aRequest, uint32_t* aRequestIdOut);
+ // intput stream
+ jclass jReadableByteChannel;
+ jclass jChannels;
+ jmethodID jChannelCreate;
+ jmethodID jByteBufferRead;
+
+ jclass jInputStream;
+ jmethodID jClose;
+ jmethodID jAvailable;
+
// other things
jmethodID jNotifyAppShellReady;
jmethodID jGetOutstandingDrawEvents;
jmethodID jPostToJavaThread;
jmethodID jCreateSurface;
jmethodID jShowSurface;
jmethodID jHideSurface;
jmethodID jDestroySurface;
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -1006,8 +1006,35 @@ nsJNIString::nsJNIString(jstring jstr, J
if (len <= 0) {
SetIsVoid(true);
} else {
Assign(reinterpret_cast<const char16_t*>(jCharPtr), len);
}
jni->ReleaseStringChars(jstr, jCharPtr);
}
+
+nsJNICString::nsJNICString(jstring jstr, JNIEnv *jenv)
+{
+ if (!jstr) {
+ SetIsVoid(true);
+ return;
+ }
+ JNIEnv *jni = jenv;
+ if (!jni) {
+ jni = AndroidBridge::GetJNIEnv();
+ }
+ const char* jCharPtr = jni->GetStringUTFChars(jstr, nullptr);
+
+ if (!jCharPtr) {
+ SetIsVoid(true);
+ return;
+ }
+
+ jsize len = jni->GetStringUTFLength(jstr);
+
+ if (len <= 0) {
+ SetIsVoid(true);
+ } else {
+ Assign(jCharPtr, len);
+ }
+ jni->ReleaseStringUTFChars(jstr, jCharPtr);
+}
--- a/widget/android/AndroidJavaWrappers.h
+++ b/widget/android/AndroidJavaWrappers.h
@@ -745,11 +745,17 @@ public:
};
class nsJNIString : public nsString
{
public:
nsJNIString(jstring jstr, JNIEnv *jenv);
};
+class nsJNICString : public nsCString
+{
+public:
+ nsJNICString(jstring jstr, JNIEnv *jenv);
+};
+
}
#endif
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -15,30 +15,33 @@ jclass GeckoAppShell::mGeckoAppShellClas
jmethodID GeckoAppShell::jAcknowledgeEvent = 0;
jmethodID GeckoAppShell::jAddPluginViewWrapper = 0;
jmethodID GeckoAppShell::jAlertsProgressListener_OnProgress = 0;
jmethodID GeckoAppShell::jCancelVibrate = 0;
jmethodID GeckoAppShell::jCheckURIVisited = 0;
jmethodID GeckoAppShell::jClearMessageList = 0;
jmethodID GeckoAppShell::jCloseCamera = 0;
jmethodID GeckoAppShell::jCloseNotification = 0;
+jmethodID GeckoAppShell::jConnectionGetMimeType = 0;
+jmethodID GeckoAppShell::jCreateInputStream = 0;
jmethodID GeckoAppShell::jCreateMessageListWrapper = 0;
jmethodID GeckoAppShell::jCreateShortcut = 0;
jmethodID GeckoAppShell::jDeleteMessageWrapper = 0;
jmethodID GeckoAppShell::jDisableBatteryNotifications = 0;
jmethodID GeckoAppShell::jDisableNetworkNotifications = 0;
jmethodID GeckoAppShell::jDisableScreenOrientationNotifications = 0;
jmethodID GeckoAppShell::jDisableSensor = 0;
jmethodID GeckoAppShell::jEnableBatteryNotifications = 0;
jmethodID GeckoAppShell::jEnableLocation = 0;
jmethodID GeckoAppShell::jEnableLocationHighAccuracy = 0;
jmethodID GeckoAppShell::jEnableNetworkNotifications = 0;
jmethodID GeckoAppShell::jEnableScreenOrientationNotifications = 0;
jmethodID GeckoAppShell::jEnableSensor = 0;
jmethodID GeckoAppShell::jGamepadAdded = 0;
+jmethodID GeckoAppShell::jGetConnection = 0;
jmethodID GeckoAppShell::jGetContext = 0;
jmethodID GeckoAppShell::jGetCurrentBatteryInformationWrapper = 0;
jmethodID GeckoAppShell::jGetCurrentNetworkInformationWrapper = 0;
jmethodID GeckoAppShell::jGetDensity = 0;
jmethodID GeckoAppShell::jGetDpiWrapper = 0;
jmethodID GeckoAppShell::jGetExtensionFromMimeTypeWrapper = 0;
jmethodID GeckoAppShell::jGetHandlersForMimeTypeWrapper = 0;
jmethodID GeckoAppShell::jGetHandlersForURLWrapper = 0;
@@ -97,30 +100,33 @@ void GeckoAppShell::InitStubs(JNIEnv *jE
jAcknowledgeEvent = getStaticMethod("acknowledgeEvent", "()V");
jAddPluginViewWrapper = getStaticMethod("addPluginView", "(Landroid/view/View;FFFFZ)V");
jAlertsProgressListener_OnProgress = getStaticMethod("alertsProgressListener_OnProgress", "(Ljava/lang/String;JJLjava/lang/String;)V");
jCancelVibrate = getStaticMethod("cancelVibrate", "()V");
jCheckURIVisited = getStaticMethod("checkUriVisited", "(Ljava/lang/String;)V");
jClearMessageList = getStaticMethod("clearMessageList", "(I)V");
jCloseCamera = getStaticMethod("closeCamera", "()V");
jCloseNotification = getStaticMethod("closeNotification", "(Ljava/lang/String;)V");
+ jConnectionGetMimeType = getStaticMethod("connectionGetMimeType", "(Ljava/net/URLConnection;)Ljava/lang/String;");
+ jCreateInputStream = getStaticMethod("createInputStream", "(Ljava/net/URLConnection;)Ljava/io/InputStream;");
jCreateMessageListWrapper = getStaticMethod("createMessageList", "(JJ[Ljava/lang/String;IIZI)V");
jCreateShortcut = getStaticMethod("createShortcut", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
jDeleteMessageWrapper = getStaticMethod("deleteMessage", "(II)V");
jDisableBatteryNotifications = getStaticMethod("disableBatteryNotifications", "()V");
jDisableNetworkNotifications = getStaticMethod("disableNetworkNotifications", "()V");
jDisableScreenOrientationNotifications = getStaticMethod("disableScreenOrientationNotifications", "()V");
jDisableSensor = getStaticMethod("disableSensor", "(I)V");
jEnableBatteryNotifications = getStaticMethod("enableBatteryNotifications", "()V");
jEnableLocation = getStaticMethod("enableLocation", "(Z)V");
jEnableLocationHighAccuracy = getStaticMethod("enableLocationHighAccuracy", "(Z)V");
jEnableNetworkNotifications = getStaticMethod("enableNetworkNotifications", "()V");
jEnableScreenOrientationNotifications = getStaticMethod("enableScreenOrientationNotifications", "()V");
jEnableSensor = getStaticMethod("enableSensor", "(I)V");
jGamepadAdded = getStaticMethod("gamepadAdded", "(II)V");
+ jGetConnection = getStaticMethod("getConnection", "(Ljava/lang/String;)Ljava/net/URLConnection;");
jGetContext = getStaticMethod("getContext", "()Landroid/content/Context;");
jGetCurrentBatteryInformationWrapper = getStaticMethod("getCurrentBatteryInformation", "()[D");
jGetCurrentNetworkInformationWrapper = getStaticMethod("getCurrentNetworkInformation", "()[D");
jGetDensity = getStaticMethod("getDensity", "()F");
jGetDpiWrapper = getStaticMethod("getDpi", "()I");
jGetExtensionFromMimeTypeWrapper = getStaticMethod("getExtensionFromMimeType", "(Ljava/lang/String;)Ljava/lang/String;");
jGetHandlersForMimeTypeWrapper = getStaticMethod("getHandlersForMimeType", "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;");
jGetHandlersForURLWrapper = getStaticMethod("getHandlersForURL", "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;");
@@ -290,16 +296,42 @@ void GeckoAppShell::CloseNotification(co
jstring j0 = AndroidBridge::NewJavaString(env, a0);
env->CallStaticVoidMethod(mGeckoAppShellClass, jCloseNotification, j0);
AndroidBridge::HandleUncaughtException(env);
env->PopLocalFrame(nullptr);
}
+jstring GeckoAppShell::ConnectionGetMimeType(jobject a0) {
+ JNIEnv *env = GetJNIForThread();
+ if (env->PushLocalFrame(2) != 0) {
+ AndroidBridge::HandleUncaughtException(env);
+ MOZ_CRASH("Exception should have caused crash.");
+ }
+
+ jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jConnectionGetMimeType, a0);
+ AndroidBridge::HandleUncaughtException(env);
+ jstring ret = static_cast<jstring>(env->PopLocalFrame(temp));
+ return ret;
+}
+
+jobject GeckoAppShell::CreateInputStream(jobject a0) {
+ JNIEnv *env = GetJNIForThread();
+ if (env->PushLocalFrame(2) != 0) {
+ AndroidBridge::HandleUncaughtException(env);
+ MOZ_CRASH("Exception should have caused crash.");
+ }
+
+ jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jCreateInputStream, a0);
+ AndroidBridge::HandleUncaughtException(env);
+ jobject ret = static_cast<jobject>(env->PopLocalFrame(temp));
+ return ret;
+}
+
void GeckoAppShell::CreateMessageListWrapper(int64_t a0, int64_t a1, jobjectArray a2, int32_t a3, int32_t a4, bool a5, int32_t a6) {
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (env->PushLocalFrame(1) != 0) {
AndroidBridge::HandleUncaughtException(env);
MOZ_CRASH("Exception should have caused crash.");
}
jvalue args[7];
@@ -473,16 +505,31 @@ void GeckoAppShell::GamepadAdded(int32_t
MOZ_CRASH("Exception should have caused crash.");
}
env->CallStaticVoidMethod(mGeckoAppShellClass, jGamepadAdded, a0, a1);
AndroidBridge::HandleUncaughtException(env);
env->PopLocalFrame(nullptr);
}
+jobject GeckoAppShell::GetConnection(const nsACString& a0) {
+ JNIEnv *env = GetJNIForThread();
+ if (env->PushLocalFrame(2) != 0) {
+ AndroidBridge::HandleUncaughtException(env);
+ MOZ_CRASH("Exception should have caused crash.");
+ }
+
+ jstring j0 = AndroidBridge::NewJavaString(env, a0);
+
+ jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jGetConnection, j0);
+ AndroidBridge::HandleUncaughtException(env);
+ jobject ret = static_cast<jobject>(env->PopLocalFrame(temp));
+ return ret;
+}
+
jobject GeckoAppShell::GetContext() {
JNIEnv *env = GetJNIForThread();
if (env->PushLocalFrame(1) != 0) {
AndroidBridge::HandleUncaughtException(env);
MOZ_CRASH("Exception should have caused crash.");
}
jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jGetContext);
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -22,30 +22,33 @@ public:
static void AcknowledgeEvent();
static void AddPluginViewWrapper(jobject a0, jfloat a1, jfloat a2, jfloat a3, jfloat a4, bool a5);
static void AlertsProgressListener_OnProgress(const nsAString& a0, int64_t a1, int64_t a2, const nsAString& a3);
static void CancelVibrate();
static void CheckURIVisited(const nsAString& a0);
static void ClearMessageList(int32_t a0);
static void CloseCamera();
static void CloseNotification(const nsAString& a0);
+ static jstring ConnectionGetMimeType(jobject a0);
+ static jobject CreateInputStream(jobject a0);
static void CreateMessageListWrapper(int64_t a0, int64_t a1, jobjectArray a2, int32_t a3, int32_t a4, bool a5, int32_t a6);
static void CreateShortcut(const nsAString& a0, const nsAString& a1, const nsAString& a2, const nsAString& a3);
static void DeleteMessageWrapper(int32_t a0, int32_t a1);
static void DisableBatteryNotifications();
static void DisableNetworkNotifications();
static void DisableScreenOrientationNotifications();
static void DisableSensor(int32_t a0);
static void EnableBatteryNotifications();
static void EnableLocation(bool a0);
static void EnableLocationHighAccuracy(bool a0);
static void EnableNetworkNotifications();
static void EnableScreenOrientationNotifications();
static void EnableSensor(int32_t a0);
static void GamepadAdded(int32_t a0, int32_t a1);
+ static jobject GetConnection(const nsACString& a0);
static jobject GetContext();
static jdoubleArray GetCurrentBatteryInformationWrapper();
static jdoubleArray GetCurrentNetworkInformationWrapper();
static jfloat GetDensity();
static int32_t GetDpiWrapper();
static jstring GetExtensionFromMimeTypeWrapper(const nsAString& a0);
static jobjectArray GetHandlersForMimeTypeWrapper(const nsAString& a0, const nsAString& a1);
static jobjectArray GetHandlersForURLWrapper(const nsAString& a0, const nsAString& a1);
@@ -103,30 +106,33 @@ protected:
static jmethodID jAcknowledgeEvent;
static jmethodID jAddPluginViewWrapper;
static jmethodID jAlertsProgressListener_OnProgress;
static jmethodID jCancelVibrate;
static jmethodID jCheckURIVisited;
static jmethodID jClearMessageList;
static jmethodID jCloseCamera;
static jmethodID jCloseNotification;
+ static jmethodID jConnectionGetMimeType;
+ static jmethodID jCreateInputStream;
static jmethodID jCreateMessageListWrapper;
static jmethodID jCreateShortcut;
static jmethodID jDeleteMessageWrapper;
static jmethodID jDisableBatteryNotifications;
static jmethodID jDisableNetworkNotifications;
static jmethodID jDisableScreenOrientationNotifications;
static jmethodID jDisableSensor;
static jmethodID jEnableBatteryNotifications;
static jmethodID jEnableLocation;
static jmethodID jEnableLocationHighAccuracy;
static jmethodID jEnableNetworkNotifications;
static jmethodID jEnableScreenOrientationNotifications;
static jmethodID jEnableSensor;
static jmethodID jGamepadAdded;
+ static jmethodID jGetConnection;
static jmethodID jGetContext;
static jmethodID jGetCurrentBatteryInformationWrapper;
static jmethodID jGetCurrentNetworkInformationWrapper;
static jmethodID jGetDensity;
static jmethodID jGetDpiWrapper;
static jmethodID jGetExtensionFromMimeTypeWrapper;
static jmethodID jGetHandlersForMimeTypeWrapper;
static jmethodID jGetHandlersForURLWrapper;
--- a/widget/android/moz.build
+++ b/widget/android/moz.build
@@ -22,16 +22,17 @@ SOURCES += [
'AndroidDirectTexture.cpp',
'AndroidGraphicBuffer.cpp',
'AndroidJavaWrappers.cpp',
'AndroidJNI.cpp',
'AndroidJNIWrapper.cpp',
'GeneratedJNIWrappers.cpp',
'GfxInfo.cpp',
'NativeJSContainer.cpp',
+ 'nsAndroidProtocolHandler.cpp',
'nsAppShell.cpp',
'nsClipboard.cpp',
'nsDeviceContextAndroid.cpp',
'nsIdleServiceAndroid.cpp',
'nsIMEPicker.cpp',
'nsLookAndFeel.cpp',
'nsPrintOptionsAndroid.cpp',
'nsScreenManagerAndroid.cpp',
@@ -46,15 +47,16 @@ LIBRARY_NAME = 'widget_android'
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/docshell/base',
'/dom/base',
'/dom/system/android',
+ '/netwerk/base/src',
'/netwerk/cache',
'/widget/android/android',
'/widget/shared',
'/widget/xpwidgets',
]
#DEFINES['DEBUG_WIDGETS'] = True
new file mode 100644
--- /dev/null
+++ b/widget/android/nsAndroidProtocolHandler.cpp
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et cin: */
+/* 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 "nsAndroidProtocolHandler.h"
+#include "nsCOMPtr.h"
+#include "nsIChannel.h"
+#include "nsIIOService.h"
+#include "nsNetUtil.h"
+#include "android/log.h"
+#include "nsBaseChannel.h"
+#include "AndroidBridge.h"
+#include "GeneratedJNIWrappers.h"
+
+using namespace mozilla;
+using namespace mozilla::widget::android;
+
+class AndroidInputStream : public nsIInputStream
+{
+public:
+ AndroidInputStream(jobject connection) {
+ JNIEnv *env = GetJNIForThread();
+ mBridgeInputStream = env->NewGlobalRef(GeckoAppShell::CreateInputStream(connection));
+ mBridgeChannel = env->NewGlobalRef(AndroidBridge::ChannelCreate(mBridgeInputStream));
+ }
+ virtual ~AndroidInputStream() {
+ JNIEnv *env = GetJNIForThread();
+ env->DeleteGlobalRef(mBridgeInputStream);
+ env->DeleteGlobalRef(mBridgeChannel);
+ }
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+
+ private:
+ jobject mBridgeInputStream;
+ jobject mBridgeChannel;
+};
+
+NS_IMPL_ISUPPORTS(AndroidInputStream, nsIInputStream)
+
+NS_IMETHODIMP AndroidInputStream::Close(void) {
+ mozilla::AndroidBridge::InputStreamClose(mBridgeInputStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP AndroidInputStream::Available(uint64_t *_retval) {
+ *_retval = mozilla::AndroidBridge::InputStreamAvailable(mBridgeInputStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP AndroidInputStream::Read(char *aBuf, uint32_t aCount, uint32_t *_retval) {
+ return mozilla::AndroidBridge::InputStreamRead(mBridgeChannel, aBuf, aCount, _retval);
+}
+
+NS_IMETHODIMP AndroidInputStream::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, uint32_t aCount, uint32_t *_retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP AndroidInputStream::IsNonBlocking(bool *_retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+
+class AndroidChannel : public nsBaseChannel
+{
+private:
+ AndroidChannel(nsIURI *aURI, jobject aConnection) {
+ JNIEnv *env = GetJNIForThread();
+ mConnection = env->NewGlobalRef(aConnection);
+ mURI = aURI;
+ nsCString type;
+ jstring jtype = GeckoAppShell::ConnectionGetMimeType(mConnection);
+ if (jtype)
+ SetContentType(nsJNICString(jtype, env));
+ }
+public:
+ static AndroidChannel* CreateChannel(nsIURI *aURI) {
+ nsCString spec;
+ aURI->GetSpec(spec);
+ jobject connection = GeckoAppShell::GetConnection(spec);
+ if (!connection)
+ return NULL;
+ return new AndroidChannel(aURI, connection);
+ }
+ ~AndroidChannel() {
+ JNIEnv *env = GetJNIForThread();
+ env->DeleteGlobalRef(mConnection);
+ }
+
+ virtual nsresult OpenContentStream(bool async, nsIInputStream **result,
+ nsIChannel** channel) {
+ nsCOMPtr<nsIInputStream> stream = new AndroidInputStream(mConnection);
+ NS_ADDREF(*result = stream);
+ return NS_OK;
+ }
+private:
+ jobject mConnection;
+};
+
+NS_IMPL_ISUPPORTS(nsAndroidProtocolHandler,
+ nsIProtocolHandler,
+ nsISupportsWeakReference)
+
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::GetScheme(nsACString &result)
+{
+ result.AssignLiteral("android");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::GetDefaultPort(int32_t *result)
+{
+ *result = -1; // no port for android: URLs
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+ *result = URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE | URI_NORELATIVE | URI_DANGEROUS_TO_LOAD;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset,
+ nsIURI *aBaseURI,
+ nsIURI **result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIStandardURL> surl(do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = surl->Init(nsIStandardURL::URLTYPE_STANDARD, -1, aSpec, aCharset, aBaseURI);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIURL> url(do_QueryInterface(surl, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ surl->SetMutable(false);
+
+ NS_ADDREF(*result = url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::NewChannel(nsIURI* aURI,
+ nsIChannel* *aResult)
+{
+ nsCOMPtr<nsIChannel> channel = AndroidChannel::CreateChannel(aURI);
+ if (!channel)
+ return NS_ERROR_FAILURE;
+ NS_ADDREF(*aResult = channel);
+ return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/widget/android/nsAndroidProtocolHandler.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 nsAndroidProtocolHandler_h___
+#define nsAndroidProtocolHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+
+#define NS_ANDROIDPROTOCOLHANDLER_CID \
+{ /* e9cd2b7f-8386-441b-aaf5-0b371846bfd0 */ \
+ 0xe9cd2b7f, \
+ 0x8386, \
+ 0x441b, \
+ {0x0b, 0x37, 0x18, 0x46, 0xbf, 0xd0} \
+}
+
+class nsAndroidProtocolHandler MOZ_FINAL : public nsIProtocolHandler,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIProtocolHandler methods:
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ // nsAndroidProtocolHandler methods:
+ nsAndroidProtocolHandler() {}
+ ~nsAndroidProtocolHandler() {}
+};
+
+#endif /* nsAndroidProtocolHandler_h___ */
--- a/widget/android/nsWidgetFactory.cpp
+++ b/widget/android/nsWidgetFactory.cpp
@@ -20,29 +20,31 @@
#include "nsClipboardHelper.h"
#include "nsTransferable.h"
#include "nsPrintOptionsAndroid.h"
#include "nsPrintSession.h"
#include "nsDeviceContextAndroid.h"
#include "nsHTMLFormatConverter.h"
#include "nsIMEPicker.h"
#include "nsXULAppAPI.h"
+#include "nsAndroidProtocolHandler.h"
NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindow)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerAndroid)
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceAndroid, nsIdleServiceAndroid::GetInstance)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboard)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsAndroid, Init)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSession, Init)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecAndroid)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsIMEPicker)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsAndroidBridge)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAndroidProtocolHandler)
#include "GfxInfo.h"
namespace mozilla {
namespace widget {
// This constructor should really be shared with all platforms.
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
}
}
@@ -57,16 +59,17 @@ NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_CID);
NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID);
NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID);
NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID);
NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID);
NS_DEFINE_NAMED_CID(NS_IMEPICKER_CID);
NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
NS_DEFINE_NAMED_CID(NS_ANDROIDBRIDGE_CID);
+NS_DEFINE_NAMED_CID(NS_ANDROIDPROTOCOLHANDLER_CID);
static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
{ &kNS_WINDOW_CID, false, nullptr, nsWindowConstructor },
{ &kNS_CHILD_CID, false, nullptr, nsWindowConstructor },
{ &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor },
{ &kNS_SCREENMANAGER_CID, false, nullptr, nsScreenManagerAndroidConstructor },
{ &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceAndroidConstructor },
{ &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor },
@@ -74,16 +77,17 @@ static const mozilla::Module::CIDEntry k
{ &kNS_CLIPBOARDHELPER_CID, false, nullptr, nsClipboardHelperConstructor },
{ &kNS_PRINTSETTINGSSERVICE_CID, false, nullptr, nsPrintOptionsAndroidConstructor },
{ &kNS_PRINTSESSION_CID, false, nullptr, nsPrintSessionConstructor },
{ &kNS_DEVICE_CONTEXT_SPEC_CID, false, nullptr, nsDeviceContextSpecAndroidConstructor },
{ &kNS_HTMLFORMATCONVERTER_CID, false, nullptr, nsHTMLFormatConverterConstructor },
{ &kNS_IMEPICKER_CID, false, nullptr, nsIMEPickerConstructor },
{ &kNS_GFXINFO_CID, false, nullptr, mozilla::widget::GfxInfoConstructor },
{ &kNS_ANDROIDBRIDGE_CID, false, nullptr, nsAndroidBridgeConstructor },
+ { &kNS_ANDROIDPROTOCOLHANDLER_CID, false, nullptr, nsAndroidProtocolHandlerConstructor },
{ nullptr }
};
static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
{ "@mozilla.org/widgets/window/android;1", &kNS_WINDOW_CID },
{ "@mozilla.org/widgets/child_window/android;1", &kNS_CHILD_CID },
{ "@mozilla.org/widget/appshell/android;1", &kNS_APPSHELL_CID },
{ "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID },
@@ -93,16 +97,17 @@ static const mozilla::Module::ContractID
{ "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID },
{ "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID },
{ "@mozilla.org/gfx/printsession;1", &kNS_PRINTSESSION_CID },
{ "@mozilla.org/gfx/devicecontextspec;1", &kNS_DEVICE_CONTEXT_SPEC_CID },
{ "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID },
{ "@mozilla.org/imepicker;1", &kNS_IMEPICKER_CID },
{ "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
{ "@mozilla.org/android/bridge;1", &kNS_ANDROIDBRIDGE_CID },
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "android", &kNS_ANDROIDPROTOCOLHANDLER_CID },
{ nullptr }
};
static void
nsWidgetAndroidModuleDtor()
{
nsLookAndFeel::Shutdown();
nsAppShellShutdown();